Major changes since last commit: - FiveSql2 SQL:1999 engine (10,458 LOC) — 43/43 ALL PASS - 21 compiler/runtime bugs fixed (short-circuit AND/OR, FOR LOOP, etc.) - @byref pass-by-reference via RefCell pattern - Mutable closure capture (EnsureLocalRef + RefCell sharing) - RTL: 400 → 479 functions (+79: file, string, datetime, hash, UTF-8) - DateTime/Timestamp fully working (hb_DateTime, hb_Hour/Min/Sec, display) - Reserved word guard (39 keywords blocked from function calls) - AEval arg order fix (element before index) - Closure capture redecl fix (unique _cap_ names per block) - Hash/string indexing in ArrayPush/ArrayPop - Harbour compat test suite: 51/51 - 4 docs: Porting Report, Implementation Plan, Optimization Plan, Commercialization Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
180 lines
4.8 KiB
Go
180 lines
4.8 KiB
Go
// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com)
|
|
// All rights reserved.
|
|
|
|
// CLASS code generation for Five.
|
|
// Generates Go code that registers classes with hbrt.ClassDef.
|
|
package gengo
|
|
|
|
import (
|
|
"five/compiler/ast"
|
|
"five/compiler/token"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// emitClassDecl generates class registration code.
|
|
// CLASS Person
|
|
// DATA cName INIT ""
|
|
// DATA nAge INIT 0
|
|
// METHOD New(cName, nAge)
|
|
// ENDCLASS
|
|
// →
|
|
// func init() { hbrt.NewClassDef("Person").AddData(...).Register() }
|
|
func (g *Generator) emitClassDecl(cls *ast.ClassDecl) {
|
|
className := strings.ToUpper(cls.Name)
|
|
varName := "_cls_" + className
|
|
|
|
g.writeln(fmt.Sprintf("var %s uint16", varName))
|
|
g.writeln("")
|
|
g.writeln("func init() {")
|
|
g.indent++
|
|
g.writeln(fmt.Sprintf("_def := hbrt.NewClassDef(%q)", cls.Name))
|
|
|
|
// Parent
|
|
if cls.ParentName != "" {
|
|
g.writeln(fmt.Sprintf("_def.InheritFrom(%q)", cls.ParentName))
|
|
}
|
|
|
|
// DATA fields
|
|
for _, m := range cls.Members {
|
|
if dd, ok := m.(*ast.DataDecl); ok {
|
|
initVal := "hbrt.MakeNil()"
|
|
if dd.Init != nil {
|
|
initVal = g.exprToGoLiteral(dd.Init)
|
|
}
|
|
g.writeln(fmt.Sprintf("_def.AddData(%q, %s)", strings.ToUpper(dd.Name), initVal))
|
|
}
|
|
}
|
|
|
|
// METHOD declarations (link to Go functions)
|
|
for _, m := range cls.Members {
|
|
if md, ok := m.(*ast.MethodDecl); ok {
|
|
upperName := strings.ToUpper(md.Name)
|
|
goFuncName := fmt.Sprintf("HB_%s_%s", className, upperName)
|
|
|
|
if md.IsSetGet {
|
|
// SETGET: register as both getter and setter
|
|
// Getter = method name, Setter = _name
|
|
g.writeln(fmt.Sprintf("_def.AddMethod(%q, %s)", upperName, goFuncName))
|
|
g.writeln(fmt.Sprintf("_def.AddMethod(%q, %s)", "_"+upperName, goFuncName))
|
|
} else if md.IsAccess {
|
|
// ACCESS propName METHOD getterName
|
|
g.writeln(fmt.Sprintf("_def.AddMethod(%q, %s)", strings.ToUpper(md.AccessName), goFuncName))
|
|
} else if md.IsAssign {
|
|
// ASSIGN propName METHOD setterName
|
|
g.writeln(fmt.Sprintf("_def.AddMethod(%q, %s)", "_"+strings.ToUpper(md.AccessName), goFuncName))
|
|
} else {
|
|
g.writeln(fmt.Sprintf("_def.AddMethod(%q, %s)", upperName, goFuncName))
|
|
}
|
|
}
|
|
}
|
|
|
|
g.writeln(fmt.Sprintf("%s = _def.Register()", varName))
|
|
g.indent--
|
|
g.writeln("}")
|
|
g.writeln("")
|
|
|
|
// Also need a constructor function: Person() returns new object
|
|
// This is called as Person():New(...)
|
|
g.writeln(fmt.Sprintf("func HB_%s_CTOR(t *hbrt.Thread) {", className))
|
|
g.indent++
|
|
g.writeln("t.Frame(0, 0)")
|
|
g.writeln("defer t.EndProc()")
|
|
g.writeln(fmt.Sprintf("t.PushValue(hbrt.NewObject(%s))", varName))
|
|
g.writeln("t.RetValue()")
|
|
g.indent--
|
|
g.writeln("}")
|
|
g.writeln("")
|
|
|
|
// Constructor symbol already added in Generate() symbol collection phase
|
|
}
|
|
|
|
// emitMethodDeclStandalone generates a standalone METHOD ... CLASS ... implementation.
|
|
func (g *Generator) emitMethodDeclStandalone(md *ast.MethodDecl) {
|
|
if md.ClassName == "" {
|
|
return // in-class method declaration only (no body)
|
|
}
|
|
|
|
className := strings.ToUpper(md.ClassName)
|
|
methodName := strings.ToUpper(md.Name)
|
|
goFuncName := fmt.Sprintf("HB_%s_%s", className, methodName)
|
|
|
|
nParams := len(md.Params)
|
|
nLocals := 0
|
|
for _, d := range md.Decls {
|
|
if vd, ok := d.(*ast.VarDecl); ok {
|
|
nLocals += len(vd.Vars)
|
|
}
|
|
}
|
|
|
|
g.writeln(fmt.Sprintf("func %s(t *hbrt.Thread) {", goFuncName))
|
|
g.indent++
|
|
g.writeln(fmt.Sprintf("t.Frame(%d, %d)", nParams, nLocals))
|
|
g.writeln("defer t.EndProc()")
|
|
g.writeln("")
|
|
|
|
// Build local map
|
|
localMap := make(localMap)
|
|
idx := 1
|
|
for _, p := range md.Params {
|
|
localMap[strings.ToUpper(p.Name)] = idx
|
|
idx++
|
|
}
|
|
for _, d := range md.Decls {
|
|
if vd, ok := d.(*ast.VarDecl); ok {
|
|
for _, v := range vd.Vars {
|
|
if v.Init != nil {
|
|
g.emitExpr(v.Init)
|
|
g.writeln(fmt.Sprintf("t.PopLocalFast(%d)", idx))
|
|
}
|
|
localMap[strings.ToUpper(v.Name)] = idx
|
|
idx++
|
|
}
|
|
}
|
|
}
|
|
|
|
g.curLocals = localMap
|
|
|
|
// Emit body
|
|
for _, stmt := range md.Body {
|
|
g.emitStmt(stmt, localMap)
|
|
}
|
|
|
|
g.indent--
|
|
g.writeln("}")
|
|
g.writeln("")
|
|
}
|
|
|
|
// exprToGoLiteral converts a simple AST expression to a Go literal string.
|
|
// Used for DATA INIT values.
|
|
func (g *Generator) exprToGoLiteral(expr ast.Expr) string {
|
|
switch e := expr.(type) {
|
|
case *ast.LiteralExpr:
|
|
switch e.Kind {
|
|
case token.INT:
|
|
return fmt.Sprintf("hbrt.MakeInt(%s)", e.Value)
|
|
case token.DOUBLE:
|
|
return fmt.Sprintf("hbrt.MakeDoubleAuto(%s)", e.Value)
|
|
case token.STRING:
|
|
return fmt.Sprintf("hbrt.MakeString(%q)", e.Value)
|
|
case token.TRUE:
|
|
return "hbrt.MakeBool(true)"
|
|
case token.FALSE:
|
|
return "hbrt.MakeBool(false)"
|
|
case token.NIL_LIT:
|
|
return "hbrt.MakeNil()"
|
|
}
|
|
case *ast.ArrayLitExpr:
|
|
// {} empty array or {1,2,3}
|
|
if len(e.Items) == 0 {
|
|
return "hbrt.MakeArray(0)"
|
|
}
|
|
// Non-empty arrays need runtime construction — fall through to nil
|
|
case *ast.HashLitExpr:
|
|
if len(e.Keys) == 0 {
|
|
return "hbrt.MakeHash()"
|
|
}
|
|
}
|
|
return "hbrt.MakeNil()"
|
|
}
|