diff --git a/compiler/gengo/gengo.go b/compiler/gengo/gengo.go index b4b958f..f5ba26f 100644 --- a/compiler/gengo/gengo.go +++ b/compiler/gengo/gengo.go @@ -800,6 +800,10 @@ func (g *Generator) emitMain() { g.writeln("vm := hbrt.NewVM()") g.writeln("hbrtl.RegisterRTL(vm)") 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 mainName := "MAIN" for _, sym := range g.symbols { @@ -848,15 +852,76 @@ func (g *Generator) emitDecl(d ast.Decl) { g.emitTopLevelStatic(decl) } 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 != "" { + body := hoistGoImports(decl.Code, g.imports) g.writeln("\n// --- Inline Go code (#pragma BEGINDUMP) ---") - g.write(decl.Code) + g.write(body) 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. func (g *Generator) emitTopLevelStatic(vd *ast.VarDecl) { for _, v := range vd.Vars {