Files
five/compiler/gengo/gengo.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

1611 lines
40 KiB
Go

// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com)
// All rights reserved.
// Go code generator for the Five language.
// Converts Five AST into Go source code that calls hbrt runtime functions.
//
// Design references:
// - Harbour: gencc.c — pcode → hb_xvm*() C function calls
// - tsgo: internal/printer/printer.go — AST → text via Writer interface
// - Pattern: AST node → Thread method call (t.PushInt, t.Plus, etc.)
//
// Generated code structure:
// package main
// import ("five/hbrt"; "five/hbrtl")
// var symbols = hbrt.NewModule(...)
// func HB_MAIN(t *hbrt.Thread) { ... }
// func main() { vm := hbrt.NewVM(); ... vm.Run("MAIN") }
package gengo
import (
"five/compiler/ast"
"five/compiler/token"
"fmt"
"path/filepath"
"strings"
)
// Generator produces Go source code from a Five AST.
type Generator struct {
buf strings.Builder
indent int
file *ast.File
symbols []symbolEntry
imports map[string]bool
importAlias map[string]string // path → alias ("_", "name", or "")
curLocals localMap // current function's local variable map
goFastFuncs []goFastEntry // Go functions to register as FastFunc
staticVars map[string]string // top-level STATIC: upper name → Go var name
IsLibrary bool // if true, no main() generated, symbols use unique name
Debug bool // if true, emit t.DebugLine() calls
}
type symbolEntry struct {
name string
scope string // "hbrt.FsPublic|hbrt.FsLocal" etc.
fn string // Go function name: "HB_MAIN"
}
// Generate converts an AST File into Go source code.
func Generate(file *ast.File) string {
g := &Generator{
file: file,
imports: map[string]bool{"five/hbrt": true, "five/hbrtl": true},
}
// Collect symbols from declarations
for _, d := range file.Decls {
switch decl := d.(type) {
case *ast.FuncDecl:
scope := "hbrt.FsPublic|hbrt.FsLocal"
if decl.Name == "Main" || decl.Name == "MAIN" {
scope += "|hbrt.FsFirst"
}
g.symbols = append(g.symbols, symbolEntry{
name: strings.ToUpper(decl.Name),
scope: scope,
fn: "HB_" + strings.ToUpper(decl.Name),
})
case *ast.ClassDecl:
// Class constructor symbol: Person() → HB_PERSON_CTOR
className := strings.ToUpper(decl.Name)
g.symbols = append(g.symbols, symbolEntry{
name: className,
scope: "hbrt.FsPublic|hbrt.FsLocal",
fn: "HB_" + className + "_CTOR",
})
}
}
// Check if xBase commands are used — if so, add RDD imports
if hasXBaseCommands(file) {
g.imports["five/hbrdd"] = true
g.imports["five/hbrdd/dbf"] = true
}
// Collect user imports
g.importAlias = make(map[string]string)
for _, imp := range file.Imports {
g.imports[imp.Path] = true
if imp.Alias != "" {
g.importAlias[imp.Path] = imp.Alias
}
}
// Generate file
g.emitHeader()
g.emitSymbols()
for _, d := range file.Decls {
g.emitDecl(d)
}
g.emitFastFuncRegistrations()
if !g.IsLibrary {
g.emitMain()
} else {
g.emitInitModule()
}
return g.buf.String()
}
// GenerateDebug generates Go code with debug line hooks enabled.
func (g *Generator) GenerateDebug(file *ast.File) string {
g.file = file
g.imports = map[string]bool{"five/hbrt": true, "five/hbrtl": true}
g.Debug = true
return Generate(file) // Generate uses package-level, need to thread debug flag through
}
// GenerateWithDebug is like Generate but includes DebugLine calls.
func GenerateWithDebug(file *ast.File) string {
g := &Generator{
file: file,
imports: map[string]bool{"five/hbrt": true, "five/hbrtl": true},
Debug: true,
}
// Same logic as Generate but with debug flag
for _, d := range file.Decls {
switch decl := d.(type) {
case *ast.FuncDecl:
scope := "hbrt.FsPublic|hbrt.FsLocal"
if decl.Name == "Main" || decl.Name == "MAIN" {
scope += "|hbrt.FsFirst"
}
g.symbols = append(g.symbols, symbolEntry{
name: strings.ToUpper(decl.Name),
scope: scope,
fn: "HB_" + strings.ToUpper(decl.Name),
})
case *ast.ClassDecl:
className := strings.ToUpper(decl.Name)
g.symbols = append(g.symbols, symbolEntry{
name: className,
scope: "hbrt.FsPublic|hbrt.FsLocal",
fn: "HB_" + className + "_CTOR",
})
}
}
if hasXBaseCommands(file) {
g.imports["five/hbrdd"] = true
g.imports["five/hbrdd/dbf"] = true
}
g.importAlias = make(map[string]string)
for _, imp := range file.Imports {
g.imports[imp.Path] = true
if imp.Alias != "" {
g.importAlias[imp.Path] = imp.Alias
}
}
g.emitHeader()
g.emitSymbols()
for _, d := range file.Decls {
g.emitDecl(d)
}
g.emitFastFuncRegistrations()
g.emitMain()
return g.buf.String()
}
// GenerateLibrary generates Go code without main() — for multi-PRG builds.
func GenerateLibrary(file *ast.File) string {
g := &Generator{
file: file,
imports: map[string]bool{"five/hbrt": true, "five/hbrtl": true},
IsLibrary: true,
}
for _, d := range file.Decls {
switch decl := d.(type) {
case *ast.FuncDecl:
scope := "hbrt.FsPublic|hbrt.FsLocal"
g.symbols = append(g.symbols, symbolEntry{
name: strings.ToUpper(decl.Name),
scope: scope,
fn: "HB_" + strings.ToUpper(decl.Name),
})
case *ast.ClassDecl:
className := strings.ToUpper(decl.Name)
g.symbols = append(g.symbols, symbolEntry{
name: className,
scope: "hbrt.FsPublic|hbrt.FsLocal",
fn: "HB_" + className + "_CTOR",
})
}
}
if hasXBaseCommands(file) {
g.imports["five/hbrdd"] = true
g.imports["five/hbrdd/dbf"] = true
}
g.importAlias = make(map[string]string)
for _, imp := range file.Imports {
g.imports[imp.Path] = true
if imp.Alias != "" {
g.importAlias[imp.Path] = imp.Alias
}
}
g.emitHeader()
g.emitSymbols()
for _, d := range file.Decls {
g.emitDecl(d)
}
g.emitFastFuncRegistrations()
g.emitInitModule()
return g.buf.String()
}
// --- Emit infrastructure ---
func (g *Generator) write(s string) {
g.buf.WriteString(s)
}
func (g *Generator) writef(format string, args ...interface{}) {
fmt.Fprintf(&g.buf, format, args...)
}
func (g *Generator) writeln(s string) {
g.writeIndent()
g.buf.WriteString(s)
g.buf.WriteByte('\n')
}
func (g *Generator) writeIndent() {
for i := 0; i < g.indent; i++ {
g.buf.WriteByte('\t')
}
}
// --- File structure ---
func (g *Generator) emitHeader() {
g.writeln("// Code generated by Five compiler. DO NOT EDIT.")
g.writeln(fmt.Sprintf("// Source: %s", g.file.Name))
g.writeln("")
g.writeln("package main")
g.writeln("")
// Imports
g.writeln("import (")
g.indent++
for imp := range g.imports {
if alias, ok := g.importAlias[imp]; ok {
g.writeln(fmt.Sprintf("%s %q", alias, imp))
} else {
g.writeln(fmt.Sprintf("%q", imp))
}
}
g.indent--
g.writeln(")")
g.writeln("")
// Ensure imports are used
g.writeln("var _ = hbrtl.RegisterRTL")
if g.imports["five/hbrdd"] {
g.writeln("var _ = hbrdd.NewWorkAreaManager")
g.writeln("var _ dbf.DBFDriver")
}
g.writeln("")
}
func (g *Generator) emitSymbols() {
varName := "symbols"
if g.IsLibrary {
// Unique variable name for library mode
safeName := strings.TrimSuffix(filepath.Base(g.file.Name), ".prg")
safeName = strings.Map(func(r rune) rune {
if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') {
return r
}
return '_'
}, safeName)
varName = "symbols_" + safeName
}
g.writeln(fmt.Sprintf("var %s = hbrt.NewModule(%q,", varName, strings.TrimSuffix(g.file.Name, ".prg")))
g.indent++
for _, sym := range g.symbols {
g.writeln(fmt.Sprintf("hbrt.Sym(%q, %s, %s),", sym.name, sym.scope, sym.fn))
}
g.indent--
g.writeln(")")
g.writeln("")
}
// emitFastFuncRegistrations emits var declarations for Go FastFunc registrations.
// These are pre-registered at package init time for 3-11x faster calls.
func (g *Generator) emitFastFuncRegistrations() {
if len(g.goFastFuncs) == 0 {
return
}
// Deduplicate
seen := map[string]bool{}
g.writeln("// Go FastFunc registrations (type-specialized, bypass reflect)")
g.writeln("var (")
g.indent++
for _, ff := range g.goFastFuncs {
if seen[ff.regName] {
continue
}
seen[ff.regName] = true
g.writeln(fmt.Sprintf("_ff_%s = hbrt.RegisterFastFunc(%q, %s)", ff.regName, ff.qualName, ff.qualName))
}
g.indent--
g.writeln(")")
g.writeln("")
}
func (g *Generator) emitMain() {
// init() runs before main() — set raw mode before ANY Go runtime I/O
g.writeln("func init() {")
g.indent++
g.writeln("hbrtl.InitRawTerminal()")
g.indent--
g.writeln("}")
g.writeln("")
g.writeln("func main() {")
g.indent++
g.writeln("vm := hbrt.NewVM()")
g.writeln("hbrtl.RegisterRTL(vm)")
g.writeln("vm.RegisterModule(symbols)")
// Find main function
mainName := "MAIN"
for _, sym := range g.symbols {
if strings.Contains(sym.scope, "FsFirst") {
mainName = sym.name
break
}
}
g.writeln(fmt.Sprintf("vm.Run(%q)", mainName))
g.indent--
g.writeln("}")
}
func (g *Generator) emitInitModule() {
safeName := strings.TrimSuffix(filepath.Base(g.file.Name), ".prg")
safeName = strings.Map(func(r rune) rune {
if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') {
return r
}
return '_'
}, safeName)
varName := "symbols_" + safeName
// Register this module's symbols into a global registry
// that main()'s vm.RegisterModule will pick up
g.writeln(fmt.Sprintf("func init() {"))
g.indent++
g.writeln(fmt.Sprintf("hbrt.RegisterLibModule(%s)", varName))
g.indent--
g.writeln("}")
}
// --- Declaration emission ---
func (g *Generator) emitDecl(d ast.Decl) {
switch decl := d.(type) {
case *ast.FuncDecl:
g.emitFuncDecl(decl)
case *ast.ClassDecl:
g.emitClassDecl(decl)
case *ast.MethodDecl:
g.emitMethodDeclStandalone(decl)
case *ast.VarDecl:
// Top-level STATIC → package-level var
if decl.Scope == ast.ScopeStatic {
g.emitTopLevelStatic(decl)
}
case *ast.GoDumpDecl:
// Inline Go code from #pragma BEGINDUMP ... ENDDUMP
if decl.Code != "" {
g.writeln("\n// --- Inline Go code (#pragma BEGINDUMP) ---")
g.write(decl.Code)
g.writeln("\n// --- End inline Go code ---\n")
}
}
}
// emitTopLevelStatic emits module-level STATIC variables as package-level Go vars.
func (g *Generator) emitTopLevelStatic(vd *ast.VarDecl) {
for _, v := range vd.Vars {
varName := "static_" + strings.ToUpper(v.Name)
initVal := "hbrt.MakeNil()"
if v.Init != nil {
initVal = g.exprToGoLiteral(v.Init)
}
g.writeln(fmt.Sprintf("var %s = %s", varName, initVal))
// Register in staticMap for lookup
if g.staticVars == nil {
g.staticVars = make(map[string]string)
}
g.staticVars[strings.ToUpper(v.Name)] = varName
}
g.writeln("")
}
func (g *Generator) emitFuncDecl(fn *ast.FuncDecl) {
goName := "HB_" + strings.ToUpper(fn.Name)
g.writeln(fmt.Sprintf("func %s(t *hbrt.Thread) {", goName))
g.indent++
// Count params and locals (including mid-function LOCALs in Body)
nParams := len(fn.Params)
nLocals := 0
for _, d := range fn.Decls {
if vd, ok := d.(*ast.VarDecl); ok && vd.Scope == ast.ScopeLocal {
nLocals += len(vd.Vars)
}
}
// Count mid-function LOCAL declarations in Body
for _, s := range fn.Body {
if vd, ok := s.(*ast.VarDecl); ok && vd.Scope == ast.ScopeLocal {
nLocals += len(vd.Vars)
}
}
g.writeln(fmt.Sprintf("t.Frame(%d, %d)", nParams, nLocals))
g.writeln("defer t.EndProc()")
g.writeln("")
// Build local map FIRST (needed for init expressions that reference params)
g.curLocals = g.buildLocalMap(fn)
// Emit LOCAL initializers
localIdx := nParams + 1 // 1-based, params come first
for _, d := range fn.Decls {
vd, ok := d.(*ast.VarDecl)
if !ok || vd.Scope != ast.ScopeLocal {
continue
}
for _, v := range vd.Vars {
if v.Init != nil {
g.emitExpr(v.Init)
g.writeln(fmt.Sprintf("t.PopLocal(%d)", localIdx))
}
localIdx++
}
}
// Emit body statements
for _, stmt := range fn.Body {
g.emitStmt(stmt, g.curLocals)
}
g.indent--
g.writeln("}")
g.writeln("")
}
type localMap map[string]int
func (g *Generator) buildLocalMap(fn *ast.FuncDecl) localMap {
m := make(localMap)
idx := 1
for _, p := range fn.Params {
m[p.Name] = idx
idx++
}
for _, d := range fn.Decls {
if vd, ok := d.(*ast.VarDecl); ok && vd.Scope == ast.ScopeLocal {
for _, v := range vd.Vars {
m[v.Name] = idx
idx++
}
}
}
return m
}
// --- Statement emission ---
func (g *Generator) emitStmt(stmt ast.Stmt, locals localMap) {
// Emit debug line hook
if g.Debug && stmt.Pos().Line > 0 {
g.writeln(fmt.Sprintf("t.DebugLine(%q, %d)", g.file.Name, stmt.Pos().Line))
}
switch s := stmt.(type) {
case *ast.ReturnStmt:
if len(s.Values) > 1 {
// Multi-return: RETURN a, b, c → push array of values
for _, v := range s.Values {
g.emitExpr(v)
}
g.writeln(fmt.Sprintf("t.ArrayGen(%d)", len(s.Values)))
g.writeln("t.RetValue()")
} else if s.Value != nil {
g.emitExpr(s.Value)
g.writeln("t.RetValue()")
} else {
g.writeln("t.RetNil()")
}
g.writeln("return") // Go return to exit function immediately
case *ast.QOutStmt:
g.emitQOut(s, locals)
case *ast.ExprStmt:
g.emitExprStmt(s, locals)
case *ast.IfStmt:
g.emitIf(s, locals)
case *ast.SwitchStmt:
g.emitSwitch(s, locals)
case *ast.DoWhileStmt:
g.emitDoWhile(s, locals)
case *ast.ForStmt:
g.emitFor(s, locals)
case *ast.ForEachStmt:
g.emitForEach(s, locals)
case *ast.ExitStmt:
g.writeln("break")
case *ast.LoopStmt:
g.writeln("continue")
case *ast.MultiAssignStmt:
g.emitMultiAssign(s, locals)
case *ast.DeferStmt:
g.emitDefer(s, locals)
case *ast.VarDecl:
// LOCAL in mid-function or PRIVATE/PUBLIC
g.emitMidVarDecl(s, locals)
// xBase commands — generate calls to hbrdd WorkAreaManager
case *ast.UseCmd:
g.emitUseCmd(s, locals)
case *ast.GoCmd:
g.emitGoCmd(s)
case *ast.SkipCmd:
g.emitSkipCmd(s, locals)
case *ast.SeekCmd:
g.emitSeekCmd(s, locals)
case *ast.ReplaceCmd:
g.emitReplaceCmd(s, locals)
case *ast.AppendCmd:
g.writeln("{ _wa := t.WA.(*hbrdd.WorkAreaManager)")
g.writeln("if _area := _wa.Current(); _area != nil { _area.Append() } }")
case *ast.DeleteCmd:
g.writeln("{ _wa := t.WA.(*hbrdd.WorkAreaManager)")
g.writeln("if _area := _wa.Current(); _area != nil { _area.Delete() } }")
case *ast.SelectCmd:
g.emitExpr(s.Area)
g.writeln("{ _wa := t.WA.(*hbrdd.WorkAreaManager); _v := t.Pop2()")
g.writeln("if _v.IsNumeric() { _wa.Select(int(_v.AsNumInt())) } else { _wa.Select(_v.AsString()) } }")
case *ast.IndexCmd:
g.writeln("{")
g.indent++
g.writeln("wa := t.WA.(*hbrdd.WorkAreaManager)")
g.writeln("if area := wa.Current(); area != nil {")
g.indent++
g.writeln("if idx, ok := area.(hbrdd.Indexer); ok {")
g.indent++
// Key expression: stringify ident (field name) or use string literal
keyStr := exprToString(s.KeyExpr)
g.writeln(fmt.Sprintf("_keyExpr := %q", keyStr))
fileStr := exprToString(s.File)
g.writeln(fmt.Sprintf("_file := %q", fileStr))
forExpr := `""`
if s.ForCond != nil {
forExpr = fmt.Sprintf("%q", exprToString(s.ForCond))
}
g.writeln(fmt.Sprintf("idx.OrderCreate(hbrdd.OrderCreateParams{KeyExpr: _keyExpr, FilePath: _file, ForExpr: %s, Unique: %v, Descending: %v})",
forExpr, s.Unique, s.Descending))
g.indent--
g.writeln("}")
g.indent--
g.writeln("}")
g.indent--
g.writeln("}")
case *ast.SetCmd:
g.writeln("{")
g.indent++
g.writeln("wa := t.WA.(*hbrdd.WorkAreaManager)")
g.writeln("if area := wa.Current(); area != nil {")
g.indent++
upper := strings.ToUpper(s.Setting)
switch upper {
case "FILTER":
if s.Expr != nil {
g.writeln("if flt, ok := area.(hbrdd.Filterer); ok {")
g.indent++
g.emitExpr(s.Expr)
g.writeln(`flt.SetFilter(t.Pop2().AsString(), nil)`)
g.indent--
g.writeln("}")
} else {
g.writeln("if flt, ok := area.(hbrdd.Filterer); ok { flt.ClearFilter() }")
}
case "ORDER":
if s.Expr != nil {
g.writeln("if idx, ok := area.(hbrdd.Indexer); ok {")
g.indent++
g.emitExpr(s.Expr)
g.writeln(`idx.OrderListFocus(t.Pop2().AsString())`)
g.indent--
g.writeln("}")
}
case "INDEX":
if s.Expr != nil {
g.writeln("if idx, ok := area.(hbrdd.Indexer); ok {")
g.indent++
g.emitExpr(s.Expr)
g.writeln(`idx.OrderListAdd(t.Pop2().AsString())`)
g.indent--
g.writeln("}")
} else {
g.writeln("if idx, ok := area.(hbrdd.Indexer); ok { idx.OrderListClear() }")
}
default:
g.writeln(fmt.Sprintf("// SET %s: not yet implemented", upper))
}
g.indent--
g.writeln("}")
g.indent--
g.writeln("}")
case *ast.SeqStmt:
g.emitBeginSequence(s, locals)
case *ast.AtSayCmd:
g.emitAtSayCmd(s)
case *ast.AtGetCmd:
g.emitAtGetCmd(s, locals)
case *ast.AtSayGetCmd:
g.emitAtSayGetCmd(s, locals)
case *ast.ReadCmd:
g.emitReadCmd(s, locals)
default:
g.writeln(fmt.Sprintf("// TODO: unhandled stmt %T", stmt))
}
}
func (g *Generator) emitMidVarDecl(s *ast.VarDecl, locals localMap) {
// LOCAL declared in mid-function: allocate new local slots dynamically
// For now, emit as local variable with initialization
for _, v := range s.Vars {
// Find or assign local index
idx, found := locals[v.Name]
if !found {
// Assign next available slot
maxIdx := 0
for _, i := range locals {
if i > maxIdx {
maxIdx = i
}
}
idx = maxIdx + 1
locals[v.Name] = idx
}
if v.Init != nil {
g.emitExpr(v.Init)
g.writeln(fmt.Sprintf("t.PopLocal(%d)", idx))
}
}
}
func (g *Generator) emitQOut(s *ast.QOutStmt, locals localMap) {
sym := "QOUT"
if s.IsQQ {
sym = "QQOUT"
}
g.writeln(fmt.Sprintf("t.PushSymbol(t.VM().FindSymbol(%q))", sym))
g.writeln("t.PushNil()")
for _, expr := range s.Exprs {
g.emitExpr(expr)
}
g.writeln(fmt.Sprintf("t.Function(%d)", len(s.Exprs)))
}
func (g *Generator) emitExprStmt(s *ast.ExprStmt, locals localMap) {
// Check if it's an assignment
if assign, ok := s.X.(*ast.AssignExpr); ok {
g.emitAssign(assign, locals)
return
}
// Check if it's a function call (discard result)
if call, ok := s.X.(*ast.CallExpr); ok {
g.emitCallAsStmt(call, locals)
return
}
// Bare identifier as statement (e.g., CLS, CLEAR) — treat as zero-arg function call
if ident, ok := s.X.(*ast.IdentExpr); ok {
if _, found := locals[ident.Name]; !found {
g.writeln(fmt.Sprintf("t.PushSymbol(t.VM().FindSymbol(%q))", strings.ToUpper(ident.Name)))
g.writeln("t.PushNil()")
g.writeln("t.Do(0)")
return
}
}
// Postfix ++/--
if pf, ok := s.X.(*ast.PostfixExpr); ok {
// Local variable: n++
if ident, ok := pf.X.(*ast.IdentExpr); ok {
if idx, found := locals[ident.Name]; found {
if pf.Op == token.INC {
g.writeln(fmt.Sprintf("t.LocalAddInt(%d, 1)", idx))
} else {
g.writeln(fmt.Sprintf("t.LocalAddInt(%d, -1)", idx))
}
return
}
}
// Self field: ::field++
if send, ok := pf.X.(*ast.SendExpr); ok {
if _, isSelf := send.Object.(*ast.SelfExpr); isSelf {
fieldName := strings.ToUpper(send.Method)
g.writeln(fmt.Sprintf("t.PushSelfField(%q)", fieldName))
if pf.Op == token.INC {
g.writeln("t.PushInt(1)")
g.writeln("t.Plus()")
} else {
g.writeln("t.PushInt(1)")
g.writeln("t.Minus()")
}
g.writeln(fmt.Sprintf("t.SetSelfField(%q)", fieldName))
return
}
}
}
// General expression (result on stack, pop it)
g.emitExpr(s.X)
g.writeln("t.Pop()")
}
func (g *Generator) emitAssign(a *ast.AssignExpr, locals localMap) {
// Check for arr[idx] := value (array index assignment)
if idx, ok := a.Left.(*ast.IndexExpr); ok {
if a.Op == token.ASSIGN {
g.emitExpr(idx.X) // array
g.emitExpr(idx.Index) // index
g.emitExpr(a.Right) // value
g.writeln("t.ArrayPop()") // set array[index] = value
return
}
}
// Check for obj:field := value (object field assignment)
if send, ok := a.Left.(*ast.SendExpr); ok {
_, isSelf := send.Object.(*ast.SelfExpr)
if isSelf {
fieldName := strings.ToUpper(send.Method)
switch a.Op {
case token.ASSIGN:
g.emitExpr(a.Right)
g.writeln(fmt.Sprintf("t.SetSelfField(%q)", fieldName))
case token.PLUSEQ:
g.writeln(fmt.Sprintf("t.PushSelfField(%q)", fieldName))
g.emitExpr(a.Right)
g.writeln("t.Plus()")
g.writeln(fmt.Sprintf("t.SetSelfField(%q)", fieldName))
case token.MINUSEQ:
g.writeln(fmt.Sprintf("t.PushSelfField(%q)", fieldName))
g.emitExpr(a.Right)
g.writeln("t.Minus()")
g.writeln(fmt.Sprintf("t.SetSelfField(%q)", fieldName))
case token.STAREQ:
g.writeln(fmt.Sprintf("t.PushSelfField(%q)", fieldName))
g.emitExpr(a.Right)
g.writeln("t.Mult()")
g.writeln(fmt.Sprintf("t.SetSelfField(%q)", fieldName))
case token.SLASHEQ:
g.writeln(fmt.Sprintf("t.PushSelfField(%q)", fieldName))
g.emitExpr(a.Right)
g.writeln("t.Divide()")
g.writeln(fmt.Sprintf("t.SetSelfField(%q)", fieldName))
default:
g.emitExpr(a.Right)
g.writeln(fmt.Sprintf("t.SetSelfField(%q)", fieldName))
}
return
}
// Non-self: obj:field := value → obj:_FIELD(value)
if a.Op == token.ASSIGN {
g.emitExpr(send.Object)
g.emitExpr(a.Right)
g.writeln(fmt.Sprintf("t.Send(%q, 1)", "_"+strings.ToUpper(send.Method)))
g.writeln("t.Pop() // discard setter result")
return
}
}
if ident, ok := a.Left.(*ast.IdentExpr); ok {
if idx, found := locals[ident.Name]; found {
switch a.Op {
case token.ASSIGN:
g.emitExpr(a.Right)
g.writeln(fmt.Sprintf("t.PopLocal(%d)", idx))
case token.PLUSEQ:
g.emitExpr(a.Right)
g.writeln(fmt.Sprintf("t.LocalAdd(%d)", idx))
case token.MINUSEQ:
g.emitExpr(a.Right)
g.writeln("t.Negate()")
g.writeln(fmt.Sprintf("t.LocalAdd(%d)", idx))
default:
// General compound: push local, push right, op, pop local
g.writeln(fmt.Sprintf("t.PushLocal(%d)", idx))
g.emitExpr(a.Right)
g.emitBinaryOp(a.Op)
g.writeln(fmt.Sprintf("t.PopLocal(%d)", idx))
}
return
}
// Check module-level STATIC variable
upper := strings.ToUpper(ident.Name)
if goVar, found := g.staticVars[upper]; found {
g.emitExpr(a.Right)
g.writeln(fmt.Sprintf("%s = t.Pop2()", goVar))
return
}
}
// Fallback: general assignment via stack
g.emitExpr(a.Right)
g.writeln("// TODO: general assignment target")
g.writeln("t.Pop()")
}
func (g *Generator) emitCallAsStmt(call *ast.CallExpr, locals localMap) {
if ident, ok := call.Func.(*ast.IdentExpr); ok {
g.writeln(fmt.Sprintf("t.PushSymbol(t.VM().FindSymbol(%q))", strings.ToUpper(ident.Name)))
} else {
g.emitExpr(call.Func)
}
g.writeln("t.PushNil()")
for _, arg := range call.Args {
g.emitExpr(arg)
}
g.writeln(fmt.Sprintf("t.Do(%d)", len(call.Args)))
}
func (g *Generator) emitIf(s *ast.IfStmt, locals localMap) {
g.emitExpr(s.Cond)
g.writeln("if t.PopLogical() {")
g.indent++
for _, stmt := range s.Body {
g.emitStmt(stmt, locals)
}
g.indent--
for _, ei := range s.ElseIfs {
g.writeIndent()
g.write("} else {\n")
g.indent++
g.emitExpr(ei.Cond)
g.writeln("if t.PopLogical() {")
g.indent++
for _, stmt := range ei.Body {
g.emitStmt(stmt, locals)
}
g.indent--
}
if len(s.ElseBody) > 0 {
g.writeln("} else {")
g.indent++
for _, stmt := range s.ElseBody {
g.emitStmt(stmt, locals)
}
g.indent--
}
g.writeln("}")
// Close nested elseif braces
for range s.ElseIfs {
g.writeln("}")
}
}
func (g *Generator) emitDoWhile(s *ast.DoWhileStmt, locals localMap) {
g.writeln("for {")
g.indent++
g.emitExpr(s.Cond)
g.writeln("if !t.PopLogical() { break }")
for _, stmt := range s.Body {
g.emitStmt(stmt, locals)
}
g.indent--
g.writeln("}")
}
func (g *Generator) emitFor(s *ast.ForStmt, locals localMap) {
idx, found := locals[s.Var]
if !found {
g.writeln("// ERROR: FOR variable not found in locals")
return
}
// i := start
g.emitExpr(s.Start)
g.writeln(fmt.Sprintf("t.PopLocal(%d)", idx))
// Detect step direction for comparison
isNegStep := false
if s.Step != nil {
if lit, ok := s.Step.(*ast.LiteralExpr); ok {
if lit.Kind == token.INT && len(lit.Value) > 0 && lit.Value[0] == '-' {
isNegStep = true
}
}
if un, ok := s.Step.(*ast.UnaryExpr); ok && un.Op == token.MINUS {
isNegStep = true
}
}
g.writeln("for {")
g.indent++
// Comparison: ascending → i <= to, descending → i >= to
g.writeln(fmt.Sprintf("t.PushLocal(%d)", idx))
g.emitExpr(s.To)
if isNegStep {
g.writeln("t.GreaterEqual()")
} else {
g.writeln("t.LessEqual()")
}
g.writeln("if !t.PopLogical() { break }")
// body
for _, stmt := range s.Body {
g.emitStmt(stmt, locals)
}
// i += step (default 1)
if s.Step != nil {
g.emitExpr(s.Step)
g.writeln(fmt.Sprintf("t.LocalAdd(%d)", idx))
} else {
g.writeln(fmt.Sprintf("t.LocalAddInt(%d, 1)", idx))
}
g.indent--
g.writeln("}")
}
func (g *Generator) emitSwitch(s *ast.SwitchStmt, locals localMap) {
g.emitExpr(s.Expr)
g.writeln("_sw := t.Pop2()")
first := true
for _, c := range s.Cases {
if first {
g.emitExpr(c.Value)
g.writeln("if _sw.AsNumInt() == t.Pop2().AsNumInt() {")
first = false
} else {
g.emitExpr(c.Value)
g.writeln("} else if _sw.AsNumInt() == t.Pop2().AsNumInt() {")
}
g.indent++
for _, stmt := range c.Body {
g.emitStmt(stmt, locals)
}
g.indent--
}
if len(s.Otherwise) > 0 {
g.writeln("} else {")
g.indent++
for _, stmt := range s.Otherwise {
g.emitStmt(stmt, locals)
}
g.indent--
}
g.writeln("}")
}
func (g *Generator) emitBeginSequence(s *ast.SeqStmt, locals localMap) {
// BEGIN SEQUENCE → Go's panic/recover.
// Use a _seqBreak flag to signal Break() was called.
// Break() panics with *HbError, caught by our recover.
g.writeln("{ // BEGIN SEQUENCE")
g.indent++
g.writeln("_seqErr := func() (_recoverErr *hbrt.HbError) {")
g.indent++
g.writeln("defer func() {")
g.indent++
g.writeln("if r := recover(); r != nil {")
g.indent++
g.writeln("if hbErr, ok := r.(*hbrt.HbError); ok {")
g.writeln(" _recoverErr = hbErr")
g.writeln("} else { panic(r) }")
g.indent--
g.writeln("}")
g.indent--
g.writeln("}()")
// Body
for _, stmt := range s.Body {
g.emitStmt(stmt, locals)
}
g.writeln("return nil")
g.indent--
g.writeln("}()")
// RECOVER
if len(s.RecoverBody) > 0 {
g.writeln("if _seqErr != nil {")
g.indent++
if s.RecoverVar != "" {
if idx, found := locals[s.RecoverVar]; found {
g.writeln(fmt.Sprintf("t.SetLocal(%d, hbrt.MakeString(_seqErr.Error()))", idx))
}
}
for _, stmt := range s.RecoverBody {
g.emitStmt(stmt, locals)
}
g.indent--
g.writeln("}")
}
g.indent--
g.writeln("} // END SEQUENCE")
}
func (g *Generator) emitForEach(s *ast.ForEachStmt, locals localMap) {
varIdx, found := locals[s.Var]
if !found {
g.writeln("// ERROR: FOR EACH variable not in locals")
return
}
// Evaluate collection
g.emitExpr(s.Collection)
g.writeln("{ _feArr := t.Pop2()")
g.writeln("if _feArr.IsArray() {")
g.indent++
g.writeln("_feItems := _feArr.AsArray().Items")
g.writeln("for _feI := 0; _feI < len(_feItems); _feI++ {")
g.indent++
g.writeln(fmt.Sprintf("t.SetLocal(%d, _feItems[_feI])", varIdx))
for _, stmt := range s.Body {
g.emitStmt(stmt, locals)
}
g.indent--
g.writeln("}")
g.indent--
g.writeln("} }")
}
// --- Expression emission ---
// Each emitExpr leaves one value on the stack.
// emitMultiAssign: a, b := Func() or a, b := x, y
func (g *Generator) emitMultiAssign(s *ast.MultiAssignStmt, locals localMap) {
if len(s.Values) == 1 {
// Single RHS: a, b := Func() → call function, unpack array result
g.emitExpr(s.Values[0])
g.writeln("{")
g.indent++
g.writeln("_mr := t.Pop2()")
g.writeln("if _mr.IsArray() {")
g.indent++
g.writeln("_arr := _mr.AsArray()")
for i, name := range s.Targets {
if name == "_" {
continue
}
idx := locals[strings.ToUpper(name)]
if idx > 0 {
g.writeln(fmt.Sprintf("if %d < len(_arr.Items) { t.SetLocal(%d, _arr.Items[%d]) }", i, idx, i))
}
}
g.indent--
g.writeln("} else {")
g.indent++
// Not array — assign first target, rest get NIL
if s.Targets[0] != "_" {
idx := locals[strings.ToUpper(s.Targets[0])]
if idx > 0 {
g.writeln(fmt.Sprintf("t.SetLocal(%d, _mr)", idx))
}
}
g.indent--
g.writeln("}")
g.indent--
g.writeln("}")
} else {
// Multiple RHS: a, b := x, y (parallel assign)
// Evaluate all RHS first, then assign
for i, val := range s.Values {
g.emitExpr(val)
g.writeln(fmt.Sprintf("_mv%d := t.Pop2()", i))
}
for i, name := range s.Targets {
if name == "_" || i >= len(s.Values) {
continue
}
idx := locals[strings.ToUpper(name)]
if idx > 0 {
g.writeln(fmt.Sprintf("t.SetLocal(%d, _mv%d)", idx, i))
}
}
}
}
// emitDefer: DEFER expr → Go defer
func (g *Generator) emitDefer(s *ast.DeferStmt, locals localMap) {
g.writeln("defer func() {")
g.indent++
g.emitExpr(s.Call)
g.writeln("t.Pop() // discard defer result")
g.indent--
g.writeln("}()")
}
func (g *Generator) emitExpr(expr ast.Expr) {
switch e := expr.(type) {
case *ast.LiteralExpr:
g.emitLiteral(e)
case *ast.IdentExpr:
g.emitIdent(e)
case *ast.BinaryExpr:
g.emitExpr(e.Left)
g.emitExpr(e.Right)
g.emitBinaryOp(e.Op)
case *ast.UnaryExpr:
g.emitExpr(e.X)
g.emitUnaryOp(e.Op)
case *ast.AssignExpr:
g.emitExpr(e.Right)
g.writeln("t.Dup()")
g.writeln("// assign to: TODO")
case *ast.CallExpr:
g.emitCall(e)
case *ast.DotExpr:
// pkg.Member as value (rare — usually inside CallExpr)
g.writeln(fmt.Sprintf("t.PushValue(hbrt.WrapGo(%s.%s))", g.dotPkgName(e), e.Member))
case *ast.SendExpr:
g.emitSendExpr(e)
case *ast.IndexExpr:
g.emitExpr(e.X)
g.emitExpr(e.Index)
g.writeln("t.ArrayPush()")
case *ast.SelfExpr:
g.writeln("t.PushSelf()") // :: alone, rare
case *ast.ArrayLitExpr:
for _, item := range e.Items {
g.emitExpr(item)
}
g.writeln(fmt.Sprintf("t.ArrayGen(%d)", len(e.Items)))
case *ast.HashLitExpr:
for i := range e.Keys {
g.emitExpr(e.Keys[i])
g.emitExpr(e.Values[i])
}
g.writeln(fmt.Sprintf("t.HashGen(%d)", len(e.Keys)))
case *ast.BlockExpr:
g.emitBlock(e)
case *ast.SliceExpr:
// a[low:high] → hbrt.ArraySlice(array, low, high)
g.emitExpr(e.X)
if e.Low != nil {
g.emitExpr(e.Low)
} else {
g.writeln("t.PushInt(1)") // default: from start (1-based)
}
if e.High != nil {
g.emitExpr(e.High)
} else {
g.writeln("t.PushInt(-1)") // default: to end (-1 = all)
}
g.writeln("t.ArraySlice()")
case *ast.NilSafeExpr:
// obj?:Method() → if not nil, call; else push NIL
g.emitExpr(e.X)
g.writeln("{")
g.indent++
g.writeln("_ns := t.Pop2()")
g.writeln("if _ns.IsNil() {")
g.indent++
g.writeln("t.PushNil()")
g.indent--
g.writeln("} else {")
g.indent++
g.writeln("t.PushValue(_ns)")
for _, arg := range e.Args {
g.emitExpr(arg)
}
g.writeln(fmt.Sprintf("t.Send(%q, %d)", e.Method, len(e.Args)))
g.indent--
g.writeln("}")
g.indent--
g.writeln("}")
case *ast.InterpolatedString:
// Already converted to fmt.Sprintf CallExpr by parser
g.emitExpr(e.Parts[0]) // shouldn't reach here normally
case *ast.MacroExpr:
g.writeln("// MACRO: TODO - runtime compilation")
g.writeln("t.PushNil()")
case *ast.AliasExpr:
g.emitAliasExpr(e)
case *ast.RefExpr:
// @variable — pass by reference
// In Five, we push a ByRef wrapper that holds the local index
if ident, ok := e.X.(*ast.IdentExpr); ok {
if idx, found := g.curLocals[ident.Name]; found {
g.writeln(fmt.Sprintf("t.PushLocalRef(%d)", idx))
} else {
g.emitExpr(e.X) // fallback: push value
}
} else {
g.emitExpr(e.X)
}
case *ast.IIfExpr:
g.emitExpr(e.Cond)
g.writeln("if t.PopLogical() {")
g.indent++
g.emitExpr(e.True)
g.indent--
g.writeln("} else {")
g.indent++
g.emitExpr(e.False)
g.indent--
g.writeln("}")
case *ast.PostfixExpr:
g.emitExpr(e.X)
g.writeln("t.Dup()")
if e.Op == token.INC {
g.writeln("t.Inc()")
} else {
g.writeln("t.Dec()")
}
g.writeln("t.Pop() // keep original for postfix")
default:
g.writeln(fmt.Sprintf("t.PushNil() // TODO: unhandled expr %T", expr))
}
}
// exprToString extracts a string representation from an AST expression.
// Used for INDEX ON key and filename, where idents are field/file names, not variables.
func exprToString(expr ast.Expr) string {
switch e := expr.(type) {
case *ast.IdentExpr:
return e.Name
case *ast.LiteralExpr:
if e.Kind == token.STRING {
return `"` + e.Value + `"`
}
return e.Value
case *ast.BinaryExpr:
left := exprToString(e.Left)
right := exprToString(e.Right)
opStr := ""
switch e.Op {
case token.PLUS:
opStr = "+"
case token.MINUS:
opStr = "-"
case token.EQ:
opStr = "="
case token.EXEQ:
opStr = "=="
case token.NEQ:
opStr = "!="
case token.LT:
opStr = "<"
case token.GT:
opStr = ">"
case token.LTE:
opStr = "<="
case token.GTE:
opStr = ">="
case token.AND:
opStr = ".AND."
case token.OR:
opStr = ".OR."
}
if opStr != "" {
return left + " " + opStr + " " + right
}
case *ast.UnaryExpr:
if e.Op == token.NOT {
return "!" + exprToString(e.X)
}
case *ast.CallExpr:
if ident, ok := e.Func.(*ast.IdentExpr); ok {
args := ""
for i, a := range e.Args {
if i > 0 {
args += ","
}
args += exprToString(a)
}
return ident.Name + "(" + args + ")"
}
}
return ""
}
func (g *Generator) emitLiteral(e *ast.LiteralExpr) {
switch e.Kind {
case token.INT:
g.writeln(fmt.Sprintf("t.PushInt(%s)", e.Value))
case token.DOUBLE:
g.writeln(fmt.Sprintf("t.PushDouble(%s, 255, 255)", e.Value))
case token.STRING:
g.writeln(fmt.Sprintf("t.PushString(%q)", e.Value))
case token.TRUE:
g.writeln("t.PushBool(true)")
case token.FALSE:
g.writeln("t.PushBool(false)")
case token.NIL_LIT:
g.writeln("t.PushNil()")
default:
g.writeln(fmt.Sprintf("t.PushNil() // TODO: literal kind %v", e.Kind))
}
}
func (g *Generator) emitIdent(e *ast.IdentExpr) {
upper := strings.ToUpper(e.Name)
// Special: Self keyword → PushSelf
if upper == "SELF" {
g.writeln("t.PushSelf()")
return
}
if idx, found := g.curLocals[e.Name]; found {
g.writeln(fmt.Sprintf("t.PushLocal(%d)", idx))
} else if goVar, found := g.staticVars[upper]; found {
// Module-level STATIC variable
g.writeln(fmt.Sprintf("t.PushValue(%s)", goVar))
} else {
// Not a local — could be unresolved global variable or function ref
g.writeln(fmt.Sprintf("t.PushLocal(0) // UNRESOLVED: %q", e.Name))
}
}
func (g *Generator) emitCall(e *ast.CallExpr) {
// Check for Go package call: pkg.Func(args)
if dot, ok := e.Func.(*ast.DotExpr); ok {
if g.isGoPackage(dot) {
g.emitGoPackageCall(dot, e.Args)
return
}
}
if ident, ok := e.Func.(*ast.IdentExpr); ok {
g.writeln(fmt.Sprintf("t.PushSymbol(t.VM().FindSymbol(%q))", strings.ToUpper(ident.Name)))
} else {
g.emitExpr(e.Func)
}
g.writeln("t.PushNil()")
for _, arg := range e.Args {
g.emitExpr(arg)
}
g.writeln(fmt.Sprintf("t.Function(%d)", len(e.Args)))
}
// isGoPackage checks if a DotExpr refers to an imported Go package.
func (g *Generator) isGoPackage(dot *ast.DotExpr) bool {
if ident, ok := dot.X.(*ast.IdentExpr); ok {
pkgName := ident.Name
// Check against imported package names
for path := range g.imports {
// "database/sql" → last segment "sql"
parts := strings.Split(path, "/")
name := parts[len(parts)-1]
if alias, ok := g.importAlias[path]; ok && alias != "_" && alias != "" {
name = alias
}
if name == pkgName {
return true
}
}
}
return false
}
// dotPkgName extracts the package identifier from a DotExpr.
func (g *Generator) dotPkgName(dot *ast.DotExpr) string {
if ident, ok := dot.X.(*ast.IdentExpr); ok {
return ident.Name
}
return "unknown"
}
// emitGoPackageCall generates direct Go function call with auto type bridging.
// PRG: result := sql.Open("sqlite", ":memory:")
// Go: { _r := hbrt.GoCallFunc(sql.Open, args...); t.PushValue(_r[0]) }
func (g *Generator) emitGoPackageCall(dot *ast.DotExpr, args []ast.Expr) {
pkg := g.dotPkgName(dot)
fn := dot.Member
qualName := pkg + "." + fn
regName := pkg + "_" + fn // safe Go variable name
// Register FastFunc in init block
g.goFastFuncs = append(g.goFastFuncs, goFastEntry{regName: regName, qualName: qualName})
// Build arg list
g.writeln("{")
g.indent++
argNames := make([]string, len(args))
for i, arg := range args {
argName := fmt.Sprintf("_a%d", i)
argNames[i] = argName
g.emitExpr(arg)
g.writeln(fmt.Sprintf("%s := t.Pop2()", argName))
}
argsStr := ""
for i, name := range argNames {
if i > 0 {
argsStr += ", "
}
argsStr += name
}
// Use FastPath (type-specialized, 3-11x faster than reflect)
g.writeln(fmt.Sprintf("_results := hbrt.GoCallFast(_ff_%s, %s)", regName, argsStr))
g.writeln("if len(_results) > 0 { t.PushValue(_results[0]) } else { t.PushNil() }")
g.indent--
g.writeln("}")
}
type goFastEntry struct {
regName string // Go variable: strings_ToUpper
qualName string // Go call: strings.ToUpper
}
func (g *Generator) emitAliasExpr(e *ast.AliasExpr) {
// alias->field or (expr)->field
// Push alias, then field name, call runtime FieldGet by name
if ident, ok := e.Alias.(*ast.IdentExpr); ok {
// Static alias: customers->name
g.writeln(fmt.Sprintf(`t.PushAliasField(%q, %q)`, ident.Name, g.fieldName(e.Field)))
} else {
// Dynamic: (cAlias)->field
g.emitExpr(e.Alias)
g.writeln(fmt.Sprintf(`t.PushDynAliasField(t.Pop2().AsString(), %q)`, g.fieldName(e.Field)))
}
}
func (g *Generator) fieldName(expr ast.Expr) string {
if ident, ok := expr.(*ast.IdentExpr); ok {
return ident.Name
}
return ""
}
func (g *Generator) emitSendExpr(e *ast.SendExpr) {
// Self access: ::field (no parens) → PushSelfField
// Self method: ::method() (has parens) → Send on Self
if _, isSelf := e.Object.(*ast.SelfExpr); isSelf {
if !e.HasParens && len(e.Args) == 0 {
// ::field (getter, no parentheses)
g.writeln(fmt.Sprintf("t.PushSelfField(%q)", strings.ToUpper(e.Method)))
return
}
// ::method() or ::method(args) — method call on Self
g.writeln("t.PushSelf()")
for _, arg := range e.Args {
g.emitExpr(arg)
}
g.writeln(fmt.Sprintf("t.Send(%q, %d)", e.Method, len(e.Args)))
return
}
// General: obj:method(args) or obj:field
// Check at runtime: if Go object → GoCall, else Harbour Send
g.emitExpr(e.Object)
g.writeln("{")
g.indent++
g.writeln("_obj := t.Pop2()")
// Push args and capture them
argNames := make([]string, len(e.Args))
for i, arg := range e.Args {
argNames[i] = fmt.Sprintf("_sa%d", i)
g.emitExpr(arg)
g.writeln(fmt.Sprintf("%s := t.Pop2()", argNames[i]))
}
g.writeln("if hbrt.IsGoObject(_obj) {")
g.indent++
// Go object: use reflect bridge
argsStr := ""
for i, name := range argNames {
if i > 0 {
argsStr += ", "
}
argsStr += name
}
g.writeln(fmt.Sprintf("_gr := hbrt.GoCallCached(_obj, %q, %s)", e.Method, argsStr))
g.writeln("if len(_gr) > 0 { t.PushValue(_gr[0]) } else { t.PushNil() }")
g.indent--
g.writeln("} else {")
g.indent++
// Harbour object: use Send
g.writeln("t.PushValue(_obj)")
for _, name := range argNames {
g.writeln(fmt.Sprintf("t.PushValue(%s)", name))
}
g.writeln(fmt.Sprintf("t.Send(%q, %d)", e.Method, len(e.Args)))
g.indent--
g.writeln("}")
g.indent--
g.writeln("}")
}
func (g *Generator) emitBlock(e *ast.BlockExpr) {
// Code block: {|params| body}
// The block function receives the SAME thread (t), not a new one.
// Block params are passed via Frame() from Eval/AEval.
nParams := len(e.Params)
g.writeln(fmt.Sprintf("t.PushBlock(func(t *hbrt.Thread) {"))
g.indent++
g.writeln(fmt.Sprintf("t.Frame(%d, 0)", nParams))
g.writeln("defer t.EndProc()")
// Build local map for block params
oldLocals := g.curLocals
blockLocals := make(localMap)
for i, p := range e.Params {
blockLocals[p] = i + 1
}
g.curLocals = blockLocals
g.emitExpr(e.Body)
g.writeln("t.RetValue()")
g.curLocals = oldLocals
g.indent--
g.writeln(fmt.Sprintf("}, %d)", 0))
}
func (g *Generator) emitBinaryOp(op token.Kind) {
switch op {
case token.PLUS:
g.writeln("t.Plus()")
case token.MINUS:
g.writeln("t.Minus()")
case token.STAR:
g.writeln("t.Mult()")
case token.SLASH:
g.writeln("t.Divide()")
case token.PERCENT:
g.writeln("t.Modulus()")
case token.POWER:
g.writeln("t.Power()")
case token.EQ, token.EXEQ:
g.writeln("t.Equal()")
case token.NEQ:
g.writeln("t.NotEqual()")
case token.LT:
g.writeln("t.Less()")
case token.GT:
g.writeln("t.Greater()")
case token.LTE:
g.writeln("t.LessEqual()")
case token.GTE:
g.writeln("t.GreaterEqual()")
case token.AND:
g.writeln("t.And()")
case token.OR:
g.writeln("t.Or()")
case token.DOLLAR:
g.writeln("t.InString()") // $ operator
// Compound assign ops (shouldn't reach here normally)
case token.PLUSEQ:
g.writeln("t.Plus()")
case token.MINUSEQ:
g.writeln("t.Minus()")
case token.STAREQ:
g.writeln("t.Mult()")
case token.SLASHEQ:
g.writeln("t.Divide()")
default:
g.writeln(fmt.Sprintf("// TODO: binary op %v", op))
}
}
func (g *Generator) emitUnaryOp(op token.Kind) {
switch op {
case token.MINUS:
g.writeln("t.Negate()")
case token.NOT:
g.writeln("t.Not()")
case token.INC:
g.writeln("t.Inc()")
case token.DEC:
g.writeln("t.Dec()")
default:
g.writeln(fmt.Sprintf("// TODO: unary op %v", op))
}
}