// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com) // All rights reserved. package hbrt import "strings" // pendingCall stores the symbol for the next Function/Do call. // This avoids storing Go pointers in Value.data (which GC can't trace). // PushSymbol records the function symbol for the next call. // The actual symbol is stored in Thread, not on the eval stack. // A marker NIL is pushed to keep stack positions correct. // Harbour: hb_xvmPushSymbol func (t *Thread) PushSymbol(sym *Symbol) { t.pushPendingSym(sym) t.push(MakeNil()) // placeholder for symbol position } // Function calls the function with nArgs arguments. // Stack layout before: [sym_placeholder] [nil/self] [arg1] ... [argN] // Stack after: [retval] // Harbour: hb_xvmFunction func (t *Thread) Function(nArgs int) { sym := t.popPendingSym() if sym == nil { panic(t.runtimeError("no function symbol for call")) } // Resolve function fn := sym.Func if fn == nil && t.vm != nil { found := t.vm.FindSymbol(strings.ToUpper(sym.Name)) if found != nil { fn = found.Func } } if fn == nil { panic(t.runtimeError("undefined function: " + sym.Name)) } // Collect args from stack args := make([]Value, nArgs) for i := nArgs - 1; i >= 0; i-- { args[i] = t.pop() } t.pop() // pop NIL/self placeholder t.pop() // pop symbol placeholder // Push args back for Frame() to pick up for _, arg := range args { t.push(arg) } // Set pending params count and symbol for Frame() t.pendingParams = nArgs t.pendingCallSym = sym // Call fn(t) // Push return value t.push(t.retVal) } // Do calls the function but discards the return value. // Harbour: hb_xvmDo func (t *Thread) Do(nArgs int) { t.Function(nArgs) t.pop() // discard return value }