fix(gengo): hoist #pragma BEGINDUMP imports + wire HB_FUNC registration
Two bugs blocked Five's own inline-Go feature: 1. Inline Go blocks placed mid-file couldn't carry an `import` list because Go rejects declarations before imports in the same file. examples/godump_demo.prg and friends (real Five demos) hit "syntax error: imports must appear before other declarations" during compile of the generated Go. hoistGoImports parses the raw dump body for `import (...)` blocks and single-form `import "path"` lines, registers each path into the generator's imports map, and returns the body with those directives stripped. The top-of-file import block then carries everything the dump needs. 2. HB_FUNC() calls inside the inline block's init() enqueue registrations into hbrt.dynamicFuncs, but the VM only promotes them to its symbol table when RegisterLibModules() is called. gengo's generated main() skipped that step, so dispatch on the inline-defined names panicked with "no function symbol for call". Emit vm.RegisterLibModules() after RegisterModule(symbols). Verified: examples/godump_demo.prg builds and runs; the inline GoUpper / GoFib / GoGCD / GoSplit / GoSquare / GoTypeOf functions all dispatch. Matches the feature's original design intent. 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:
@@ -800,6 +800,10 @@ func (g *Generator) emitMain() {
|
|||||||
g.writeln("vm := hbrt.NewVM()")
|
g.writeln("vm := hbrt.NewVM()")
|
||||||
g.writeln("hbrtl.RegisterRTL(vm)")
|
g.writeln("hbrtl.RegisterRTL(vm)")
|
||||||
g.writeln("vm.RegisterModule(symbols)")
|
g.writeln("vm.RegisterModule(symbols)")
|
||||||
|
// Pick up any HB_FUNC() registrations from #pragma BEGINDUMP blocks
|
||||||
|
// — their package init() queues them into hbrt.dynamicFuncs, and
|
||||||
|
// RegisterLibModules pulls them into this VM.
|
||||||
|
g.writeln("vm.RegisterLibModules()")
|
||||||
// Find main function
|
// Find main function
|
||||||
mainName := "MAIN"
|
mainName := "MAIN"
|
||||||
for _, sym := range g.symbols {
|
for _, sym := range g.symbols {
|
||||||
@@ -848,15 +852,76 @@ func (g *Generator) emitDecl(d ast.Decl) {
|
|||||||
g.emitTopLevelStatic(decl)
|
g.emitTopLevelStatic(decl)
|
||||||
}
|
}
|
||||||
case *ast.GoDumpDecl:
|
case *ast.GoDumpDecl:
|
||||||
// Inline Go code from #pragma BEGINDUMP ... ENDDUMP
|
// Inline Go code from #pragma BEGINDUMP ... ENDDUMP. Extract
|
||||||
|
// any `import` directives inside the block and fold them into
|
||||||
|
// the generator's own imports map — Go insists imports live at
|
||||||
|
// the top of the file, so we can't just splat the raw dump in
|
||||||
|
// the middle of the declarations.
|
||||||
if decl.Code != "" {
|
if decl.Code != "" {
|
||||||
|
body := hoistGoImports(decl.Code, g.imports)
|
||||||
g.writeln("\n// --- Inline Go code (#pragma BEGINDUMP) ---")
|
g.writeln("\n// --- Inline Go code (#pragma BEGINDUMP) ---")
|
||||||
g.write(decl.Code)
|
g.write(body)
|
||||||
g.writeln("\n// --- End inline Go code ---\n")
|
g.writeln("\n// --- End inline Go code ---\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hoistGoImports scans a raw Go source block for top-level `import`
|
||||||
|
// statements, registers each imported path in `imports`, and returns
|
||||||
|
// the body with those directives stripped. Handles both forms:
|
||||||
|
//
|
||||||
|
// import "path"
|
||||||
|
// import (
|
||||||
|
// "path1"
|
||||||
|
// alias "path2"
|
||||||
|
// _ "path3"
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// Imports in the middle of the dump would violate Go's "imports first"
|
||||||
|
// rule. Five's own top-of-file import block will already include
|
||||||
|
// whatever the hoisted set adds, so the end result links correctly.
|
||||||
|
func hoistGoImports(code string, imports map[string]bool) string {
|
||||||
|
lines := strings.Split(code, "\n")
|
||||||
|
var out []string
|
||||||
|
i := 0
|
||||||
|
for i < len(lines) {
|
||||||
|
trim := strings.TrimSpace(lines[i])
|
||||||
|
// Block form: `import (`
|
||||||
|
if trim == "import (" || trim == "import(" {
|
||||||
|
i++
|
||||||
|
for i < len(lines) {
|
||||||
|
t := strings.TrimSpace(lines[i])
|
||||||
|
if t == ")" {
|
||||||
|
i++
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// Parse `path`, `alias path`, `_ path` — each with
|
||||||
|
// quoted path as the final token.
|
||||||
|
if start := strings.Index(t, `"`); start >= 0 {
|
||||||
|
if end := strings.LastIndex(t, `"`); end > start {
|
||||||
|
imports[t[start+1:end]] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Single form: `import "path"` or `import alias "path"`.
|
||||||
|
if strings.HasPrefix(trim, "import ") || strings.HasPrefix(trim, "import\t") {
|
||||||
|
if start := strings.Index(trim, `"`); start >= 0 {
|
||||||
|
if end := strings.LastIndex(trim, `"`); end > start {
|
||||||
|
imports[trim[start+1:end]] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out = append(out, lines[i])
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return strings.Join(out, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
// emitTopLevelStatic emits module-level STATIC variables as package-level Go vars.
|
// emitTopLevelStatic emits module-level STATIC variables as package-level Go vars.
|
||||||
func (g *Generator) emitTopLevelStatic(vd *ast.VarDecl) {
|
func (g *Generator) emitTopLevelStatic(vd *ast.VarDecl) {
|
||||||
for _, v := range vd.Vars {
|
for _, v := range vd.Vars {
|
||||||
|
|||||||
Reference in New Issue
Block a user