// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com) // All rights reserved. // xBase command code generation for Five. // Generates Go code that calls hbrdd WorkAreaManager methods. package gengo import ( "five/compiler/ast" "five/compiler/token" "fmt" "strings" ) func (g *Generator) emitUseCmd(s *ast.UseCmd, locals localMap) { if s.File == nil { // USE without args = close current g.writeln("{") g.indent++ g.writeln("wa := t.WA.(*hbrdd.WorkAreaManager)") g.writeln("wa.Close()") g.indent-- g.writeln("}") return } g.writeln("{") g.indent++ g.writeln("wa := t.WA.(*hbrdd.WorkAreaManager)") g.emitExpr(s.File) g.writeln("_path := t.Pop2().AsString()") via := "DBFNTX" // default if s.Via != "" { via = s.Via } shared := "false" if s.Shared { shared = "true" } readOnly := "false" if s.ReadOnly { readOnly = "true" } if s.AliasExpr != nil { // Dynamic alias: ALIAS (expr) g.emitExpr(s.AliasExpr) g.writeln("_alias := t.Pop2().AsString()") g.writeln(fmt.Sprintf("_, _err := wa.Open(%q, _path, _alias, %s, %s)", via, shared, readOnly)) } else { g.writeln(fmt.Sprintf("_, _err := wa.Open(%q, _path, %q, %s, %s)", via, s.Alias, shared, readOnly)) } g.writeln("if _err != nil { panic(&hbrt.HbError{Description: _err.Error(), Operation: \"USE\", SubSystem: \"BASE\"}) }") g.indent-- g.writeln("}") } func (g *Generator) emitGoCmd(s *ast.GoCmd) { // Use hoisted area if available areaVar := "_area" if g.hoistedDW || g.hoistedFields != nil { areaVar = g.hoistedAreaVar() g.writeln(fmt.Sprintf("if %s != nil {", areaVar)) g.indent++ } else { g.writeln("{") g.indent++ g.writeln("wa := t.WA.(*hbrdd.WorkAreaManager)") g.writeln(fmt.Sprintf("if %s := wa.Current(); %s != nil {", areaVar, areaVar)) g.indent++ } switch s.Direction { case "TOP": g.writeln(fmt.Sprintf("%s.GoTop()", areaVar)) case "BOTTOM": g.writeln(fmt.Sprintf("%s.GoBottom()", areaVar)) default: if s.RecNo != nil { if lit, ok := s.RecNo.(*ast.LiteralExpr); ok && lit.Kind == token.INT { g.writeln(fmt.Sprintf("%s.GoTo(uint32(%s))", areaVar, lit.Value)) } else { g.emitExpr(s.RecNo) g.writeln(fmt.Sprintf("%s.GoTo(uint32(t.Pop2().AsNumInt()))", areaVar)) } } } g.indent-- g.writeln("}") if !g.hoistedDW && g.hoistedFields == nil { g.indent-- g.writeln("}") } } func (g *Generator) emitSkipCmd(s *ast.SkipCmd, locals localMap) { areaVar := "_area" if g.hoistedDW || g.hoistedFields != nil { areaVar = g.hoistedAreaVar() g.writeln(fmt.Sprintf("if %s != nil {", areaVar)) g.indent++ } else { g.writeln("{") g.indent++ g.writeln("wa := t.WA.(*hbrdd.WorkAreaManager)") g.writeln(fmt.Sprintf("if %s := wa.Current(); %s != nil {", areaVar, areaVar)) g.indent++ } if s.Count != nil { if lit, ok := s.Count.(*ast.LiteralExpr); ok && lit.Kind == token.INT { g.writeln(fmt.Sprintf("%s.Skip(%s)", areaVar, lit.Value)) } else if unary, ok := s.Count.(*ast.UnaryExpr); ok { if lit2, ok2 := unary.X.(*ast.LiteralExpr); ok2 && lit2.Kind == token.INT { g.writeln(fmt.Sprintf("%s.Skip(-%s)", areaVar, lit2.Value)) } else { g.emitExpr(s.Count) g.writeln(fmt.Sprintf("%s.Skip(t.Pop2().AsNumInt())", areaVar)) } } else { g.emitExpr(s.Count) g.writeln(fmt.Sprintf("%s.Skip(t.Pop2().AsNumInt())", areaVar)) } } else { g.writeln(fmt.Sprintf("%s.Skip(1)", areaVar)) } g.indent-- g.writeln("}") if !g.hoistedDW && g.hoistedFields == nil { g.indent-- g.writeln("}") } } // hoistedAreaVar returns the area variable name for the current hoisting context. func (g *Generator) hoistedAreaVar() string { if g.hoistedFields != nil { return "_rarea" } return "_darea" } func (g *Generator) emitSeekCmd(s *ast.SeekCmd, locals localMap) { areaVar := "area" if g.hoistedDW || g.hoistedFields != nil { areaVar = g.hoistedAreaVar() g.writeln(fmt.Sprintf("if %s != nil {", areaVar)) g.indent++ } else { g.writeln("{") g.indent++ g.writeln("wa := t.WA.(*hbrdd.WorkAreaManager)") g.writeln(fmt.Sprintf("if %s := wa.Current(); %s != nil {", areaVar, areaVar)) g.indent++ } g.emitExpr(s.Key) g.writeln("_key := t.Pop2()") g.writeln(fmt.Sprintf("if _idx, ok := %s.(hbrdd.Indexer); ok {", areaVar)) g.indent++ if s.SoftSeek { g.writeln("_found, _ := _idx.Seek(_key, true, false)") } else { g.writeln("_found, _ := _idx.Seek(_key, hbrtl.GetSetSoftSeek(), false)") } g.writeln("_ = _found") g.indent-- g.writeln("}") g.indent-- g.writeln("}") if !g.hoistedDW && g.hoistedFields == nil { g.indent-- g.writeln("}") } } func (g *Generator) emitReplaceCmd(s *ast.ReplaceCmd, locals localMap) { // Check if we're inside a hoisted FOR loop if g.hoistedFields != nil { g.emitReplaceCmdHoisted(s, locals) return } g.writeln("{") g.indent++ g.writeln("wa := t.WA.(*hbrdd.WorkAreaManager)") g.writeln("if _area := wa.Current(); _area != nil {") g.indent++ g.writeln("_dbf, _ := _area.(*dbf.DBFArea)") g.writeln("if _dbf != nil {") g.indent++ for _, rf := range s.Fields { if ident, ok := rf.Field.(*ast.IdentExpr); ok { g.emitExpr(rf.Value) g.writeln(fmt.Sprintf("_dbf.PutValue(_dbf.FieldIndex(%q), t.Pop2())", ident.Name)) } } g.indent-- g.writeln("}") g.indent-- g.writeln("}") g.indent-- g.writeln("}") } // emitReplaceCmdHoisted emits REPLACE using pre-hoisted _rdbf and _rfiN variables. func (g *Generator) emitReplaceCmdHoisted(s *ast.ReplaceCmd, locals localMap) { g.writeln("if _rdbf != nil {") g.indent++ for _, rf := range s.Fields { if ident, ok := rf.Field.(*ast.IdentExpr); ok { // Find cached field index variable fiVar := "" for i, fname := range g.hoistedFields { if strings.EqualFold(fname, ident.Name) { fiVar = fmt.Sprintf("_rfi%d", i) break } } if fiVar != "" { g.emitExpr(rf.Value) g.writeln(fmt.Sprintf("_rdbf.PutValue(%s, t.Pop2())", fiVar)) } else { // Field not in hoisted set — fallback g.emitExpr(rf.Value) g.writeln(fmt.Sprintf("_rdbf.PutValue(_rdbf.FieldIndex(%q), t.Pop2())", ident.Name)) } } } g.indent-- g.writeln("}") } // --- @ SAY / GET / READ commands --- func (g *Generator) emitAtSayCmd(s *ast.AtSayCmd) { // DevPos(row, col) g.writeln(fmt.Sprintf("t.PushSymbol(t.VM().FindSymbol(\"DEVPOS\"))")) g.writeln("t.PushNil()") g.emitExpr(s.Row) g.emitExpr(s.Col) g.writeln("t.Do(2)") if s.Picture != nil { // DevOutPict(expr, pic) g.writeln(fmt.Sprintf("t.PushSymbol(t.VM().FindSymbol(\"DEVOUTPICT\"))")) g.writeln("t.PushNil()") g.emitExpr(s.SayExpr) g.emitExpr(s.Picture) g.writeln("t.Do(2)") } else { // DevOut(expr) g.writeln(fmt.Sprintf("t.PushSymbol(t.VM().FindSymbol(\"DEVOUT\"))")) g.writeln("t.PushNil()") g.emitExpr(s.SayExpr) g.writeln("t.Do(1)") } } func (g *Generator) emitAtGetCmd(s *ast.AtGetCmd, locals localMap) { // AAdd(GetList, GetNew(row, col, {|_1| IIF(_1==NIL, var, var:=_1)}, "varname" [, pic] [, {valid}] [, {when}])) g.writeln(fmt.Sprintf("t.PushSymbol(t.VM().FindSymbol(\"AADD\"))")) g.writeln("t.PushNil()") // Push GetList variable g.emitIdentByName("GetList", locals) // GetNew(row, col, block, name, ...) g.writeln(fmt.Sprintf("t.PushSymbol(t.VM().FindSymbol(\"GETNEW\"))")) g.writeln("t.PushNil()") g.emitExpr(s.Row) g.emitExpr(s.Col) // GET/SET block: {|_1| IIF(_1 == NIL, var, var := _1)} g.emitGetSetBlock(s.Var, s.VarName, locals) // Variable name as string g.writeln(fmt.Sprintf("t.PushString(%q)", s.VarName)) nArgs := 4 if s.Picture != nil { g.emitExpr(s.Picture) nArgs++ } if s.Valid != nil { if s.Picture == nil { g.writeln("t.PushNil()") // placeholder for pic nArgs++ } g.emitExpr(s.Valid) nArgs++ } if s.When != nil { if s.Picture == nil && s.Valid == nil { g.writeln("t.PushNil()") // placeholder for pic g.writeln("t.PushNil()") // placeholder for valid nArgs += 2 } else if s.Valid == nil { g.writeln("t.PushNil()") // placeholder for valid nArgs++ } g.emitExpr(s.When) nArgs++ } g.writeln(fmt.Sprintf("t.Function(%d)", nArgs)) // AAdd(GetList, getObj) — 2 args g.writeln("t.Do(2)") // ATail(GetList):Display() g.writeln(fmt.Sprintf("t.PushSymbol(t.VM().FindSymbol(\"ATAIL\"))")) g.writeln("t.PushNil()") g.emitIdentByName("GetList", locals) g.writeln("t.Function(1)") g.writeln(fmt.Sprintf("t.Send(\"DISPLAY\", 0)")) g.writeln("t.Pop() // discard Display result") } func (g *Generator) emitAtSayGetCmd(s *ast.AtSayGetCmd, locals localMap) { // First: @ row, col SAY expr g.emitAtSayCmd(&ast.AtSayCmd{AtPos: s.AtPos, Row: s.Row, Col: s.Col, SayExpr: s.SayExpr}) // Then: @ Row(), Col()+1 GET var ... g.emitAtGetCmd(&ast.AtGetCmd{ AtPos: s.AtPos, Row: &ast.CallExpr{Func: &ast.IdentExpr{Name: "Row"}, Args: nil}, Col: &ast.BinaryExpr{Left: &ast.CallExpr{Func: &ast.IdentExpr{Name: "Col"}, Args: nil}, Op: token.PLUS, Right: &ast.LiteralExpr{Kind: token.INT, Value: "1"}}, // Col()+1 Var: s.Var, VarName: s.VarName, Picture: s.Picture, Valid: s.Valid, When: s.When, }, locals) } func (g *Generator) emitReadCmd(s *ast.ReadCmd, locals localMap) { // ReadModal(GetList) g.writeln(fmt.Sprintf("t.PushSymbol(t.VM().FindSymbol(\"READMODAL\"))")) g.writeln("t.PushNil()") g.emitIdentByName("GetList", locals) g.writeln("t.Do(1)") if !s.Save { // GetList := {} g.writeln("t.PushValue(hbrt.MakeArray(0))") g.emitPopByName("GetList", locals) } } // emitGetSetBlock generates a {|_1| IIF(_1 == NIL, var, var := _1)} code block. // Uses captured frame base + local index to access the outer variable correctly // even when the block is called from a different call depth (e.g., Eval inside GetNew). func (g *Generator) emitGetSetBlock(varExpr ast.Expr, varName string, locals localMap) { if idx, found := locals[strings.ToUpper(varName)]; found { // Capture the frame's localBase and index at block creation time g.writeln(fmt.Sprintf("{ // GET/SET block for %s", varName)) g.indent++ g.writeln(fmt.Sprintf("_getIdx := %d", idx)) g.writeln("_getFrame := t.CurFrame()") g.writeln("_getLocals := t.LocalsSlice()") g.writeln("t.PushBlock(func(t2 *hbrt.Thread) {") g.indent++ g.writeln("t2.Frame(1, 0)") g.writeln("defer t2.EndProc()") g.writeln("if t2.Local(1).IsNil() {") g.indent++ g.writeln("t2.PushValue(_getFrame.GetLocal(_getIdx, _getLocals))") g.writeln("t2.RetValue()") g.indent-- g.writeln("} else {") g.indent++ g.writeln("_getFrame.SetLocal(_getIdx, t2.Local(1), _getLocals)") g.writeln("t2.PushValue(t2.Local(1))") g.writeln("t2.RetValue()") g.indent-- g.writeln("}") g.indent-- g.writeln("}, 0)") g.indent-- g.writeln("}") } else { // Fallback: push NIL block g.writeln("t.PushNil() // GET block for unresolved var") } } // emitIdentByName pushes a variable by name onto the stack func (g *Generator) emitIdentByName(name string, locals localMap) { if idx, found := locals[strings.ToUpper(name)]; found { g.writeln(fmt.Sprintf("t.PushLocalFast(%d)", idx)) } else if goVar, found := g.staticVars[strings.ToUpper(name)]; found { g.writeln(fmt.Sprintf("t.PushValue(%s)", goVar)) } else { // Unresolved → runtime memvar lookup (returns NIL if missing). g.writeln(fmt.Sprintf("t.PushMemvar(%q) // unresolved", name)) } } // emitPopByName pops stack into a variable by name func (g *Generator) emitPopByName(name string, locals localMap) { if idx, found := locals[strings.ToUpper(name)]; found { g.writeln(fmt.Sprintf("t.PopLocalFast(%d)", idx)) } else if goVar, found := g.staticVars[strings.ToUpper(name)]; found { g.writeln(fmt.Sprintf("%s = t.Pop2()", goVar)) } else { // Unresolved → runtime memvar store (auto-creates PRIVATE). g.writeln(fmt.Sprintf("t.PopMemvar(%q) // unresolved", name)) } }