- Compiler: PP → Lexer → Parser → Analyzer → Gengo pipeline - Parser: 232/236 (98%) Harbour compatibility, registry-based dispatch - RTL: 351 Harbour-compatible functions - RDD: DBF/NTX/CDX engines with Rushmore bitmap optimization - Go Interop: IMPORT + pkg.Func() + obj:Method() with FastPath (15M calls/sec) - HB_FUNC API: Full Harbour C API compatible Go bridge - Concurrency: SPAWN/LAUNCH/GOROUTINE, <-, WATCH, PARALLEL FOR, ASYNC/AWAIT - Extensions: Multi-return, DEFER, Slice, f-string, Nil-safe ?:, CONST - Macro Compiler: Runtime AST parsing and evaluation - Debugger: TUI debugger with source display, breakpoints, stepping - FRB: Native + Pcode dual mode runtime binary - Tests: 13 packages ALL PASS Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
180 lines
4.7 KiB
Go
180 lines
4.7 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[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.PopLocal(%d)", idx))
|
|
}
|
|
localMap[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()"
|
|
}
|