// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com) // All rights reserved. // macroeval.go — Full runtime macro compiler for Five. // // Implements &(expression) by reusing Five's own lexer/parser at runtime. // This is the key advantage of Five: since the compiler is in Go, // it can be embedded in the runtime for macro compilation. // // Harbour's macro compiler (src/macro/macro.y) is a separate YACC grammar. // Five simply reuses the same parser. // // Usage: // &cVar → simple variable lookup // &(cVar + "_name") → evaluate string expression, use result as name // &("Upper(cName)") → evaluate function call at runtime package hbrt import ( "five/compiler/ast" "five/compiler/parser" "five/compiler/token" "strconv" "strings" ) // MacroEval compiles and evaluates a Harbour expression string at runtime. // This is the full macro compiler — uses Five's parser to parse the expression, // then evaluates the AST directly. func (t *Thread) MacroEval(exprStr string) Value { exprStr = strings.TrimSpace(exprStr) if exprStr == "" { return MakeNil() } // Quick path: simple identifier → variable/function lookup if isSimpleIdent(exprStr) { return t.macroLookupIdent(exprStr) } // Full path: parse the expression and evaluate the AST source := "FUNCTION __macro__()\nRETURN " + exprStr + "\n" file, errs := parser.Parse("macro", source) if len(errs) > 0 || len(file.Decls) == 0 { // Parse failed — try as simple string return t.MacroCompile(exprStr) } fn, ok := file.Decls[0].(*ast.FuncDecl) if !ok || len(fn.Body) == 0 { return t.MacroCompile(exprStr) } // Get the RETURN expression ret, ok := fn.Body[0].(*ast.ReturnStmt) if !ok || ret.Value == nil { return t.MacroCompile(exprStr) } // Evaluate the AST expression return t.evalExpr(ret.Value) } // evalExpr evaluates an AST expression at runtime. func (t *Thread) evalExpr(expr ast.Expr) Value { switch e := expr.(type) { case *ast.LiteralExpr: return t.evalLiteral(e) case *ast.IdentExpr: return t.macroLookupIdent(e.Name) case *ast.BinaryExpr: left := t.evalExpr(e.Left) right := t.evalExpr(e.Right) return t.evalBinaryOp(e.Op, left, right) case *ast.UnaryExpr: x := t.evalExpr(e.X) return t.evalUnaryOp(e.Op, x) case *ast.CallExpr: return t.evalCall(e) case *ast.SendExpr: obj := t.evalExpr(e.Object) args := make([]Value, len(e.Args)) for i, a := range e.Args { args[i] = t.evalExpr(a) } t.push(obj) for _, a := range args { t.push(a) } t.Send(e.Method, len(args)) return t.pop() case *ast.IndexExpr: arr := t.evalExpr(e.X) idx := t.evalExpr(e.Index) if arr.IsArray() { items := arr.AsArray().Items i := idx.AsInt() if i >= 1 && i <= len(items) { return items[i-1] } } return MakeNil() case *ast.ArrayLitExpr: items := make([]Value, len(e.Items)) for i, item := range e.Items { items[i] = t.evalExpr(item) } return MakeArrayFrom(items) case *ast.HashLitExpr: h := &HbHash{} for i := range e.Keys { h.Set(t.evalExpr(e.Keys[i]), t.evalExpr(e.Values[i])) } return MakeHashFrom(h) case *ast.BlockExpr: // Return as code block body := e.Body return MakeBlock(func(bt *Thread) { result := bt.evalExpr(body) bt.push(result) bt.RetValue() }, len(e.Params)) case *ast.DotExpr: // pkg.Func — try GoCallFunc obj := t.evalExpr(e.X) results := GoCall(obj, e.Member) if len(results) > 0 { return results[0] } return MakeNil() case *ast.SelfExpr: return t.self case *ast.AliasExpr: // alias->field if ident, ok := e.Alias.(*ast.IdentExpr); ok { if field, ok := e.Field.(*ast.IdentExpr); ok { t.PushAliasField(ident.Name, field.Name) return t.pop() } } return MakeNil() case *ast.AssignExpr: val := t.evalExpr(e.Right) // Assignment in macro — store to memvar or local if ident, ok := e.Left.(*ast.IdentExpr); ok { t.macroStoreIdent(ident.Name, val) } return val default: return MakeNil() } } // evalLiteral converts an AST literal to a Value. func (t *Thread) evalLiteral(e *ast.LiteralExpr) Value { switch e.Kind { case token.NIL_LIT: return MakeNil() case token.TRUE: return MakeBool(true) case token.FALSE: return MakeBool(false) case token.INT: n, _ := strconv.ParseInt(e.Value, 10, 64) return MakeNumInt(n) case token.LONG: n, _ := strconv.ParseInt(e.Value, 10, 64) return MakeLong(n) case token.DOUBLE: f, _ := strconv.ParseFloat(e.Value, 64) return MakeDoubleAuto(f) case token.STRING: return MakeString(e.Value) default: return MakeString(e.Value) } } // evalBinaryOp evaluates a binary operation. func (t *Thread) evalBinaryOp(op token.Kind, left, right Value) Value { t.push(left) t.push(right) switch op { case token.PLUS: t.Plus() case token.MINUS: t.Minus() case token.STAR: t.Mult() case token.SLASH: t.Divide() case token.PERCENT: t.Modulus() case token.POWER: t.Power() case token.EQ, token.EXEQ: t.Equal() case token.NEQ: t.NotEqual() case token.LT: t.Less() case token.GT: t.Greater() case token.LTE: t.LessEqual() case token.GTE: t.GreaterEqual() case token.AND: t.And() case token.OR: t.Or() case token.DOLLAR: t.InString() default: return MakeNil() } return t.pop() } // evalUnaryOp evaluates a unary operation. func (t *Thread) evalUnaryOp(op token.Kind, x Value) Value { t.push(x) switch op { case token.MINUS: t.Negate() case token.NOT: t.Not() case token.INC: t.Inc() case token.DEC: t.Dec() default: return x } return t.pop() } // evalCall evaluates a function call expression. func (t *Thread) evalCall(e *ast.CallExpr) Value { // Get function name var funcName string if ident, ok := e.Func.(*ast.IdentExpr); ok { funcName = strings.ToUpper(ident.Name) } else { return MakeNil() } // Evaluate arguments args := make([]Value, len(e.Args)) for i, a := range e.Args { args[i] = t.evalExpr(a) } // Find and call function via VM sym := t.vm.FindSymbol(funcName) if sym == nil || sym.Func == nil { return MakeNil() } t.PushSymbol(sym) t.PushNil() for _, a := range args { t.push(a) } t.Function(len(args)) return t.pop() } // macroLookupIdent looks up a name: local → memvar → function. func (t *Thread) macroLookupIdent(name string) Value { upper := strings.ToUpper(name) // Try memvar first (PUBLIC/PRIVATE) if v, ok := t.Memvars.Get(upper); ok { return v } // Try as function sym := t.vm.FindSymbol(upper) if sym != nil && sym.Func != nil { return MakeString(name) // return name (function reference) } // Return as string (field name, unknown variable) return MakeString(name) } // macroStoreIdent stores a value to a named variable (memvar). func (t *Thread) macroStoreIdent(name string, val Value) { if !t.Memvars.Set(name, val) { t.Memvars.SetPrivate(name, val, t.callSP) } }