// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com) // All rights reserved. // Runtime macro compiler for Five. // Implements &variable and &(expression) — runtime code compilation. // // Harbour has a full macro compiler (src/macro/macro.y) that parses // and compiles expressions at runtime. Five uses a simplified approach: // parse the expression string, then evaluate it using the existing // lexer/parser/evaluator infrastructure. // // Usage: // LOCAL cField := "salary" // ? &cField → evaluates variable named "salary" // ? &(cField + "_new") → evaluates variable named "salary_new" // // Reference: /mnt/d/harbour-core/src/macro/ package hbrt import ( "strconv" "strings" ) // MacroCompile compiles and evaluates a macro expression string. // Returns the result value. // // For simple variable references (&cVar): // Looks up the variable name in memvars/locals. // // For complex expressions (&(expr)): // Would need full expression parser — simplified for now. func (t *Thread) MacroCompile(expr string) Value { expr = strings.TrimSpace(expr) if expr == "" { return MakeNil() } // Simple case: expression is a variable name // Look up in memvars first, then try as function call if isSimpleIdent(expr) { // Try calling as a function (memvar lookup deferred to MacroEval) sym := t.vm.FindSymbol(strings.ToUpper(expr)) if sym != nil && sym.Func != nil { t.PushSymbol(sym) t.PushNil() t.Function(0) return t.pop() } return MakeString(expr) // return as string if not found } // Complex expression: try parsing as number, then as function call // Full runtime expression parser would be needed for complete macro support. // This handles common patterns: &("literal"), &(numericExpr) // Try numeric (use stdlib strconv) if len(expr) > 0 && (expr[0] >= '0' && expr[0] <= '9' || expr[0] == '-' || expr[0] == '+') { if strings.Contains(expr, ".") { if f, err := strconv.ParseFloat(expr, 64); err == nil { return MakeDoubleAuto(f) } } else { if n, err := strconv.ParseInt(expr, 10, 64); err == nil { return MakeNumInt(n) } } } // Try string literal if len(expr) >= 2 && (expr[0] == '"' && expr[len(expr)-1] == '"' || expr[0] == '\'' && expr[len(expr)-1] == '\'') { return MakeString(expr[1 : len(expr)-1]) } // Try .T./.F. upper := strings.ToUpper(expr) if upper == ".T." { return MakeBool(true) } if upper == ".F." { return MakeBool(false) } // Return as string (field name, variable name, etc.) return MakeString(expr) } // macroEvalHook is installed by hbrtl at init time. It handles the // real work: parse the source via compiler/parser, compile to pcode // via compiler/genpc, execute with ExecPcode. We can't call those // directly from hbrt because genpc imports hbrt — the hook pattern // keeps hbrt's core independent of the compiler packages. // // Stack contract matches MacroPush: pops the source string value, // pushes the evaluated result. var macroEvalHook func(*Thread) // SetMacroEvalHook wires in the full macro evaluator. Called by // hbrtl.init(). Without the hook installed, MacroPush falls back to // the legacy stub that only resolves bare identifier names. func SetMacroEvalHook(fn func(*Thread)) { macroEvalHook = fn } // MacroPush compiles a macro and pushes the result on stack. // Harbour: HB_P_MACROPUSH // // Stack: [sourceString] → [result]. The caller emits the expression // that yields the source string first — gengo produces // `emitExpr(e.Expr); t.MacroPush()` for &. func (t *Thread) MacroPush() { if macroEvalHook != nil { macroEvalHook(t) return } // Fallback: legacy simple-ident lookup. Kept so hbrt tests (which // don't init hbrtl) still function for trivial cases. exprVal := t.pop() result := t.MacroCompile(exprVal.AsString()) t.push(result) } // parseFloat and parseInt64 removed — using strconv.ParseFloat/ParseInt instead. // isSimpleIdent checks if string is a valid simple identifier. func isSimpleIdent(s string) bool { if len(s) == 0 { return false } ch := s[0] if !((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_') { return false } for i := 1; i < len(s); i++ { ch = s[i] if !((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch == '_') { return false } } return true }