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