From 2a662525b3b95a178be41cc8f0b79f1d603ae00e Mon Sep 17 00:00:00 2001 From: CharlesKWON Date: Sat, 18 Apr 2026 16:33:09 +0900 Subject: [PATCH] =?UTF-8?q?feat(rtl):=20DO(xTarget,=20[args...])=20?= =?UTF-8?q?=E2=80=94=20dynamic=20dispatch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Harbour's DO() accepts a string (looked up as a function name), a code block (evaluated with args), or a symbol, and invokes it. Used for plugin systems and dynamic dispatch idioms like `DO(cHandler, oRequest)`. Five already had stmtDo rewrite `DO(...)` at statement-level to a function-call expression, so callers in expression position just work — but gengo refused to emit DO as a function call because it was on the reserved-word guard list (which existed to catch stray ENDIF/ENDDO from bad IF nesting). Remove DO from that list; the statement form is still handled upstream by parseDoProc, so the guard loses nothing. rtlDo implements the dispatch: - String target → VM.FindSymbol + t.Function - Block target → EvalBlock path (same as Eval) - Anything else → NIL Tested (/tmp/test_do.prg): DO("Greet", "World") → "hello, World" DO({|x,y| x*y+1}, 5, 6) → 31 DO(NIL) → NIL (ValType "U") FiveSql2 43/43, Harbour compat 56/56, Go test ALL PASS. Co-Authored-By: Claude Opus 4.7 (1M context) --- compiler/gengo/gengo.go | 2 +- hbrtl/register.go | 65 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/compiler/gengo/gengo.go b/compiler/gengo/gengo.go index 4d43076..be36dfe 100644 --- a/compiler/gengo/gengo.go +++ b/compiler/gengo/gengo.go @@ -2736,7 +2736,7 @@ func (g *Generator) emitAssignExpr(e *ast.AssignExpr) { func isReservedWord(name string) bool { switch name { case "IF", "ELSE", "ELSEIF", "ENDIF", - "DO", "WHILE", "ENDDO", + "WHILE", "ENDDO", "FOR", "NEXT", "TO", "STEP", "RETURN", "FUNCTION", "PROCEDURE", "LOCAL", "STATIC", "PRIVATE", "PUBLIC", diff --git a/hbrtl/register.go b/hbrtl/register.go index 2bc872f..daace7f 100644 --- a/hbrtl/register.go +++ b/hbrtl/register.go @@ -6,8 +6,9 @@ package hbrtl import ( - "five/hbrt" "five/hbrdd" + "five/hbrt" + "strings" ) // RegisterRTL registers all standard library functions with the VM. @@ -87,6 +88,7 @@ func RegisterRTL(vm *hbrt.VM) { // Eval hbrt.Sym("EVAL", hbrt.FsPublic, rtlEval), + hbrt.Sym("DO", hbrt.FsPublic, rtlDo), // String (new) hbrt.Sym("AT", hbrt.FsPublic, At), @@ -698,6 +700,67 @@ func rtlQQOut(t *hbrt.Thread) { t.RetNil() } +// rtlDo — Harbour DO(xTarget, [arg1, ...]) → xResult. +// +// Dispatches the named function, code block, or symbol dynamically. +// String target: looked up in the VM symbol table (uppercased, like +// compile-time calls) and invoked. Block target: evaluated with the +// remaining args just like Eval. Anything else returns NIL. +// +// Reference: harbour-core/src/rtl/do.c HB_FUNC( DO ). +func rtlDo(t *hbrt.Thread) { + nParams := t.ParamCount() + t.Frame(nParams, 0) + defer t.EndProc() + + if nParams < 1 { + t.RetNil() + return + } + + target := t.Local(1) + nArgs := nParams - 1 + + switch { + case target.IsString(): + name := strings.ToUpper(target.AsString()) + vm := t.VM() + if vm == nil { + t.RetNil() + return + } + sym := vm.FindSymbol(name) + if sym == nil { + panic(&hbrt.HbError{ + Description: "DO: unknown function " + target.AsString(), + Operation: "DO", + SubSystem: "BASE", + }) + } + t.PushSymbol(sym) + t.PushNil() // self placeholder + for i := 2; i <= nParams; i++ { + t.PushValue(t.Local(i)) + } + t.Function(nArgs) + // Function() leaves the callee's return value on the stack. + t.RetValue() + + case target.IsBlock(): + blk := target.AsBlock() + for i := 2; i <= nParams; i++ { + t.PushValue(t.Local(i)) + } + t.PendingParams2(nArgs) + blk.Fn(t) + t.PushValue(t.GetRetValue()) + t.RetValue() + + default: + t.RetNil() + } +} + // rtlEval evaluates a code block. // Harbour: Eval(bBlock, [xArg1, ...]) → xResult func rtlEval(t *hbrt.Thread) {