feat(rtl): DO(xTarget, [args...]) — dynamic dispatch

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) <noreply@anthropic.com>
This commit is contained in:
2026-04-18 16:33:09 +09:00
parent 327f75bb45
commit 2a662525b3
2 changed files with 65 additions and 2 deletions

View File

@@ -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",

View File

@@ -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) {