Files
five/compiler/gengo/gen_class.go
Charles KWON OhJun 59568f3301 Five v0.9 — Harbour + Go fusion language
- 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>
2026-03-31 09:41:50 +09:00

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()"
}