// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com) // All rights reserved. package gengo import ( "five/compiler/ast" "strings" ) // hasXBaseCommands checks if the file contains any xBase commands. func hasXBaseCommands(file *ast.File) bool { for _, d := range file.Decls { switch decl := d.(type) { case *ast.FuncDecl: if scanStmtsForXBase(decl.Body) { return true } case *ast.MethodDecl: if scanStmtsForXBase(decl.Body) { return true } } } return false } // xbaseFuncNames is the set of RTL functions that require the hbrdd import // in the generated Go code. When a PRG only uses these via function-call // syntax (no xBase commands like USE), the compiler still needs hbrdd. var xbaseFuncNames = map[string]bool{ "DBUSEAREA": true, "DBCREATE": true, "DBAPPEND": true, "DBSKIP": true, "DBGOTO": true, "DBGOTOP": true, "DBGOBOTTOM": true, "DBSEEK": true, "DBDELETE": true, "DBRECALL": true, "DBCLOSEAREA": true, "DBCLOSEALL": true, "DBSELECTAREA": true, "DBCOMMIT": true, "DBPACK": true, "DBZAP": true, "DBRLOCK": true, "DBRUNLOCK": true, "FLOCK": true, "DBUNLOCK": true, "FIELDGET": true, "FIELDPUT": true, "FIELDPOS": true, "FIELDNAME": true, "FCOUNT": true, "RECNO": true, "RECCOUNT": true, "EOF": true, "BOF": true, "FOUND": true, "DELETED": true, "LASTREC": true, "ALIAS": true, "ORDSETFOCUS": true, "ORDCOUNT": true, "ORDNAME": true, "ORDKEY": true, "USED": true, "SELECT": true, "SETDELETED": true, "FIVE_SQL": true, // FiveSql2 entry point also needs hbrdd } func scanExprForXBase(expr ast.Expr) bool { if expr == nil { return false } switch e := expr.(type) { case *ast.CallExpr: if ident, ok := e.Func.(*ast.IdentExpr); ok { if xbaseFuncNames[strings.ToUpper(ident.Name)] { return true } } for _, arg := range e.Args { if scanExprForXBase(arg) { return true } } case *ast.BinaryExpr: return scanExprForXBase(e.Left) || scanExprForXBase(e.Right) case *ast.AssignExpr: return scanExprForXBase(e.Right) case *ast.AliasExpr: // FIELD->NAME always needs hbrdd return true } return false } // countLocalsInStmts walks a statement list (recursively into IF / // FOR / WHILE / DO CASE / SWITCH / SEQUENCE / WITH bodies) and totals // every `LOCAL` declaration found. Function-entry frame allocation // must include these so a mid-function or nested LOCAL doesn't get // assigned a slot index past the runtime's allocated count — that // previously surfaced as silent NIL reads / out-of-range writes // (e.g. frb_demo Section 4 with `LOCAL ch := Channel(1)` inside an // IF block returning NIL on receive). func countLocalsInStmts(stmts []ast.Stmt) int { n := 0 for _, s := range stmts { switch v := s.(type) { case *ast.VarDecl: if v.Scope == ast.ScopeLocal { n += len(v.Vars) } case *ast.IfStmt: n += countLocalsInStmts(v.Body) for _, ei := range v.ElseIfs { n += countLocalsInStmts(ei.Body) } n += countLocalsInStmts(v.ElseBody) case *ast.ForStmt: n += countLocalsInStmts(v.Body) case *ast.ForEachStmt: n += countLocalsInStmts(v.Body) case *ast.DoWhileStmt: n += countLocalsInStmts(v.Body) case *ast.SeqStmt: n += countLocalsInStmts(v.Body) n += countLocalsInStmts(v.RecoverBody) case *ast.SwitchStmt: for _, c := range v.Cases { n += countLocalsInStmts(c.Body) } n += countLocalsInStmts(v.Otherwise) case *ast.WatchStmt: // `WATCH ... CASE ... CASE ... OTHERWISE ... END WATCH` // — each CASE body and OTHERWISE may declare LOCALs. for _, c := range v.Cases { n += countLocalsInStmts(c.Body) } n += countLocalsInStmts(v.Otherwise) case *ast.TimeoutStmt: // `WITH TIMEOUT n SECONDS ... END` — body may declare LOCALs. n += countLocalsInStmts(v.Body) case *ast.ParallelForStmt: // `PARALLEL FOR i := ... NEXT` — body may declare LOCALs. n += countLocalsInStmts(v.Body) } } return n } func scanStmtsForXBase(stmts []ast.Stmt) bool { for _, s := range stmts { switch v := s.(type) { case *ast.UseCmd, *ast.GoCmd, *ast.SkipCmd, *ast.SeekCmd, *ast.ReplaceCmd, *ast.AppendCmd, *ast.DeleteCmd, *ast.SelectCmd, *ast.IndexCmd, *ast.SetCmd: return true case *ast.ExprStmt: if scanExprForXBase(v.X) { return true } case *ast.IfStmt: if scanStmtsForXBase(v.Body) || scanStmtsForXBase(v.ElseBody) { return true } for _, ei := range v.ElseIfs { if scanStmtsForXBase(ei.Body) { return true } } case *ast.ForStmt: if scanStmtsForXBase(v.Body) { return true } case *ast.ForEachStmt: if scanStmtsForXBase(v.Body) { return true } case *ast.DoWhileStmt: if scanStmtsForXBase(v.Body) { return true } case *ast.SeqStmt: if scanStmtsForXBase(v.Body) || scanStmtsForXBase(v.RecoverBody) { return true } case *ast.SwitchStmt: for _, c := range v.Cases { if scanStmtsForXBase(c.Body) { return true } } } } return false }