gelang 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/LICENSE +21 -0
  3. package/README.md +535 -0
  4. package/bin/ge.js +112 -0
  5. package/package.json +62 -0
  6. package/python/pyeffic/__init__.py +9 -0
  7. package/python/pyeffic/__main__.py +6 -0
  8. package/python/pyeffic/analyzer.py +464 -0
  9. package/python/pyeffic/apisurface.py +238 -0
  10. package/python/pyeffic/autoselect.py +327 -0
  11. package/python/pyeffic/backends.py +87 -0
  12. package/python/pyeffic/bench.py +233 -0
  13. package/python/pyeffic/cli.py +184 -0
  14. package/python/pyeffic/compiler.py +421 -0
  15. package/python/pyeffic/config.py +383 -0
  16. package/python/pyeffic/dartgen.py +441 -0
  17. package/python/pyeffic/deploy.py +586 -0
  18. package/python/pyeffic/diagnostics.py +194 -0
  19. package/python/pyeffic/difftest.py +424 -0
  20. package/python/pyeffic/downloader.py +307 -0
  21. package/python/pyeffic/emitters/__init__.py +11 -0
  22. package/python/pyeffic/emitters/base.py +2359 -0
  23. package/python/pyeffic/emitters/cpp.py +266 -0
  24. package/python/pyeffic/emitters/csharp.py +342 -0
  25. package/python/pyeffic/emitters/dart.py +349 -0
  26. package/python/pyeffic/emitters/go.py +388 -0
  27. package/python/pyeffic/emitters/kotlin.py +314 -0
  28. package/python/pyeffic/emitters/rust.py +314 -0
  29. package/python/pyeffic/emitters/zig.py +411 -0
  30. package/python/pyeffic/ffi.py +49 -0
  31. package/python/pyeffic/frontends/__init__.py +94 -0
  32. package/python/pyeffic/frontends/hybrid.py +709 -0
  33. package/python/pyeffic/frontends/typescript.py +965 -0
  34. package/python/pyeffic/ge_cli.py +1148 -0
  35. package/python/pyeffic/golden.py +348 -0
  36. package/python/pyeffic/idents.py +206 -0
  37. package/python/pyeffic/modules.py +220 -0
  38. package/python/pyeffic/packer.py +222 -0
  39. package/python/pyeffic/pipeline.py +797 -0
  40. package/python/pyeffic/reactgen.py +966 -0
  41. package/python/pyeffic/researcher.py +177 -0
  42. package/python/pyeffic/scaffold.py +397 -0
  43. package/python/pyeffic/stdlib.py +246 -0
  44. package/python/pyeffic/styling.py +220 -0
  45. package/python/pyeffic/templates/desktop_gui/README.md +106 -0
  46. package/python/pyeffic/templates/desktop_gui/app/__init__.py +0 -0
  47. package/python/pyeffic/templates/desktop_gui/app/core/__init__.py +0 -0
  48. package/python/pyeffic/templates/desktop_gui/app/core/add.ge.py +13 -0
  49. package/python/pyeffic/templates/desktop_gui/app/core/factorial.ge.py +20 -0
  50. package/python/pyeffic/templates/desktop_gui/app/core/fibonacci.ge.py +25 -0
  51. package/python/pyeffic/templates/desktop_gui/app/core/gcd.ge.py +19 -0
  52. package/python/pyeffic/templates/desktop_gui/app/core/is_prime.ge.py +24 -0
  53. package/python/pyeffic/templates/desktop_gui/app/core/multiply.ge.py +13 -0
  54. package/python/pyeffic/templates/desktop_gui/app/core/power.ge.py +25 -0
  55. package/python/pyeffic/templates/desktop_gui/app/main.ge.py +49 -0
  56. package/python/pyeffic/templates/desktop_gui/app/memory/__init__.py +0 -0
  57. package/python/pyeffic/templates/desktop_gui/app/memory/buffer.ge.py +26 -0
  58. package/python/pyeffic/templates/desktop_gui/app/memory/limits.ge.py +47 -0
  59. package/python/pyeffic/templates/desktop_gui/app/memory/state.ge.py +44 -0
  60. package/python/pyeffic/templates/desktop_gui/app/ui/__init__.py +0 -0
  61. package/python/pyeffic/templates/desktop_gui/app/ui/layout.ge.py +64 -0
  62. package/python/pyeffic/templates/desktop_gui/app/ui/render.ge.py +87 -0
  63. package/python/pyeffic/templates/desktop_gui/app/ui/theme.ge.py +147 -0
  64. package/python/pyeffic/templates/desktop_gui/app/ui/widgets.ge.py +105 -0
  65. package/python/pyeffic/templates/desktop_gui/desktop/__init__.py +1 -0
  66. package/python/pyeffic/templates/desktop_gui/desktop/main.ge.py +258 -0
  67. package/python/pyeffic/templates/desktop_gui/ge.toml +16 -0
  68. package/python/pyeffic/templates/desktop_gui/tests/__init__.py +0 -0
  69. package/python/pyeffic/templates/desktop_gui/tests/ge_loader.py +76 -0
  70. package/python/pyeffic/templates/desktop_gui/tests/test_app.py +173 -0
  71. package/python/pyeffic/templates/web_react/README.md +115 -0
  72. package/python/pyeffic/templates/web_react/app/__init__.py +0 -0
  73. package/python/pyeffic/templates/web_react/app/core/__init__.py +0 -0
  74. package/python/pyeffic/templates/web_react/app/core/add.ge.py +9 -0
  75. package/python/pyeffic/templates/web_react/app/core/factorial.ge.py +16 -0
  76. package/python/pyeffic/templates/web_react/app/core/fibonacci.ge.py +21 -0
  77. package/python/pyeffic/templates/web_react/app/core/is_prime.ge.py +20 -0
  78. package/python/pyeffic/templates/web_react/app/core/multiply.ge.py +9 -0
  79. package/python/pyeffic/templates/web_react/app/main.ge.py +25 -0
  80. package/python/pyeffic/templates/web_react/app/memory/__init__.py +0 -0
  81. package/python/pyeffic/templates/web_react/app/memory/buffer.ge.py +25 -0
  82. package/python/pyeffic/templates/web_react/app/memory/limits.ge.py +51 -0
  83. package/python/pyeffic/templates/web_react/ge.toml +23 -0
  84. package/python/pyeffic/templates/web_react/tests/__init__.py +0 -0
  85. package/python/pyeffic/templates/web_react/tests/ge_loader.py +68 -0
  86. package/python/pyeffic/templates/web_react/tests/test_app.py +105 -0
  87. package/python/pyeffic/templates/web_react/ui/main.ge.ui +33 -0
  88. package/python/pyeffic/templates/web_react/web/__init__.py +0 -0
  89. package/python/pyeffic/templates/web_react/web/server.ge.py +78 -0
  90. package/python/pyeffic/ts2py.py +657 -0
  91. package/python/pyeffic/typecheck.py +232 -0
  92. package/python/pyeffic/ui.py +154 -0
  93. package/python/pyeffic/ui_dsl.py +618 -0
  94. package/python/pyeffic/widgets.py +87 -0
  95. package/scripts/README.md +42 -0
  96. package/scripts/check-toolchains.py +85 -0
@@ -0,0 +1,388 @@
1
+ """Go backend spec + program assembly.
2
+
3
+ Go compiles to a shared library via cgo. Exports C ABI functions with
4
+ `//export` directive. Interops with C/C++/Rust/Zig via the C ABI.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import ast
9
+
10
+ from .base import Emitter, Spec
11
+ from ..analyzer import FuncUnit
12
+
13
+ SPEC = Spec(
14
+ name="go",
15
+ types={"int": "int64", "float": "float64", "bool": "bool", "str": "string", "None": ""},
16
+ list_type="[]int64",
17
+ list_param_type="[]int64",
18
+ list_elem_type="int64",
19
+ borrow_list_arg=False,
20
+ range_call="({lo}..{hi})",
21
+ range_step_call="({lo}..{hi})",
22
+ len_call="int64(len({x}))",
23
+ list_len="int64(len({x}))",
24
+ print_int='fmt.Println({v})',
25
+ print_float='fmt.Println({v})',
26
+ print_str='fmt.Println({v})',
27
+ print_bool='fmt.Println({v})',
28
+ print_generic='fmt.Println({v})',
29
+ int_cast="int64({x})",
30
+ float_cast="float64({x})",
31
+ float_div="(float64({l}) / float64({r}))",
32
+ floor_div="({l} / {r})",
33
+ sum_call="func() int64 {{ var s int64; for _, v := range {it} {{ s += v }}; return s }}()",
34
+ abs_int="abs({x})",
35
+ abs_float="math.Abs({x})",
36
+ min_call="func() int64 {{ var m int64; for i, v := range {it} {{ if i == 0 || v < m {{ m = v }} }}; return m }}()",
37
+ max_call="func() int64 {{ var m int64; for i, v := range {it} {{ if i == 0 || v > m {{ m = v }} }}; return m }}()",
38
+ pow_call="int64(math.Pow(float64({l}), float64({r})))",
39
+ append_call="{x} = append({x}, {v})",
40
+ index_call="{x}[{i}]",
41
+ comment="//",
42
+ fn_template="{sig} {{\n{body}\n}}",
43
+ main_template="",
44
+ var_decl_template="var {target} {nt} = {val}",
45
+ ffi_prefix="//export ",
46
+ indent="\t",
47
+ str_concat="{l} + {r}",
48
+ str_len="int64(len({x}))",
49
+ str_index="int64({x}[{i}])",
50
+ str_slice="{x}[{start}:{end}]",
51
+ str_slice_start="{x}[{start}:]",
52
+ str_slice_end="{x}[:{end}]",
53
+ list_concat="append({l}, {r}...)",
54
+ condition_parens=False,
55
+ foreach_template="for _, {var} := range {iter}",
56
+ try_template="// try/except not supported in Go\n// {body}\n// {handler}",
57
+ struct_template="type {name} struct {{\n{fields}\n}}",
58
+ struct_field_template="\t{type} {name}",
59
+ struct_new_template="func {name}_New({params}) {name} {{\n{body}\n}}",
60
+ dict_type="map[string]{V}",
61
+ dict_get="{d}[{k}]",
62
+ dict_set="{d}[{k}] = {v}",
63
+ dict_contains="dictContains({d}, {k})",
64
+ tuple_type="struct {{ a {T}; b {T} }}",
65
+ tuple_get="{t}.{field}",
66
+ )
67
+
68
+
69
+ class GoEmitter(Emitter):
70
+ def _try_stmt(self, node, ind):
71
+ """Go doesn't have try/catch — emit body directly, comment handler."""
72
+ self.lines.append(f"{ind}// try/except: Go has no exceptions")
73
+ for s in node.body:
74
+ self.stmt(s)
75
+ if node.handlers:
76
+ self.lines.append(f"{ind}// except handler (not emitted in Go):")
77
+ for s in node.handlers[0].body:
78
+ save = self.lines
79
+ self.lines = []
80
+ self.stmt(s)
81
+ for line in self.lines:
82
+ save.append(f"{ind}// {line.strip()}")
83
+ self.lines = save
84
+ if node.finalbody:
85
+ for s in node.finalbody:
86
+ self.stmt(s)
87
+
88
+ def _is_mutated_list_param(self, name: str) -> bool:
89
+ """Check if a name is a mutated list parameter."""
90
+ return name in self.mutated_params and self.var_types.get(name) == "list"
91
+
92
+ def expr(self, node: ast.AST) -> str: # type: ignore[override]
93
+ # Go: dereference mutated list params when used as append target
94
+ if isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute):
95
+ if node.func.attr == "append" and isinstance(node.func.value, ast.Name):
96
+ base_name = node.func.value.id
97
+ if self._is_mutated_list_param(base_name):
98
+ val = self.expr(node.args[0]) if node.args else ""
99
+ return f"*{base_name} = append(*{base_name}, {val})"
100
+ # Go: pass &var for mutated list params at call sites
101
+ if isinstance(node, ast.Call) and isinstance(node.func, ast.Name):
102
+ fname = node.func.id
103
+ mutated_indices = self.func_mutated_params.get(fname, set())
104
+ if mutated_indices:
105
+ args = []
106
+ for i, a in enumerate(node.args):
107
+ if i in mutated_indices and isinstance(a, ast.Name):
108
+ args.append(f"&{self.expr(a)}")
109
+ else:
110
+ args.append(self.expr(a))
111
+ return f"{fname}({', '.join(args)})"
112
+ # Go: dereference mutated list params for subscript access
113
+ if isinstance(node, ast.Subscript) and isinstance(node.value, ast.Name):
114
+ base_name = node.value.id
115
+ if self._is_mutated_list_param(base_name):
116
+ idx = self.expr(node.slice)
117
+ return f"(*{base_name})[{idx}]"
118
+ # Go: dereference mutated list params for len()
119
+ if isinstance(node, ast.Call) and getattr(node.func, "id", None) == "len":
120
+ if node.args and isinstance(node.args[0], ast.Name):
121
+ base_name = node.args[0].id
122
+ if self._is_mutated_list_param(base_name):
123
+ return f"int64(len(*{base_name}))"
124
+ return super().expr(node)
125
+
126
+ def for_loop(self, node, ind): # type: ignore[override]
127
+ var = node.target.id if isinstance(node.target, ast.Name) else "_"
128
+ it = node.iter
129
+ is_tuple_target = isinstance(node.target, ast.Tuple)
130
+ if isinstance(it, ast.Call) and getattr(it.func, "id", None) == "range":
131
+ args = it.args
132
+ if len(args) == 1:
133
+ lo, hi = "int64(0)", self.expr(args[0])
134
+ else:
135
+ lo, hi = "int64(" + self.expr(args[0]) + ")", self.expr(args[1])
136
+ self.var_types[var] = "int64"
137
+ self.lines.append(f"{ind}for {var} := {lo}; {var} < {hi}; {var}++ {{")
138
+ elif isinstance(it, ast.Call) and getattr(it.func, "id", None) == "enumerate" and is_tuple_target:
139
+ # for i, x in enumerate(items):
140
+ target = node.target
141
+ idx_var = target.elts[0].id
142
+ val_var = target.elts[1].id
143
+ inner_it = self.expr(it.args[0])
144
+ self.var_types[idx_var] = "int64"
145
+ self.var_types[val_var] = "int64"
146
+ self.lines.append(f"{ind}for {idx_var}, {val_var} := range {inner_it} {{")
147
+ elif isinstance(it, ast.Call) and getattr(it.func, "id", None) == "zip" and is_tuple_target:
148
+ # for a, b in zip(x, y):
149
+ target = node.target
150
+ var_a = target.elts[0].id
151
+ var_b = target.elts[1].id
152
+ it_a = self.expr(it.args[0])
153
+ it_b = self.expr(it.args[1])
154
+ self.var_types[var_a] = "int64"
155
+ self.var_types[var_b] = "int64"
156
+ self.lines.append(f"{ind}for __i := int64(0); __i < int64(len({it_a})) && __i < int64(len({it_b})); __i++ {{")
157
+ self.lines.append(f"{ind} {var_a} := {it_a}[__i]; {var_b} := {it_b}[__i]")
158
+ else:
159
+ iter_s = self.expr(it)
160
+ self.var_types[var] = "long"
161
+ self.lines.append(f"{ind}for _, {var} := range {iter_s} {{")
162
+ self.indent_lvl += 1
163
+ for s in node.body:
164
+ self.stmt(s)
165
+ self.indent_lvl -= 1
166
+ self.lines.append(f"{ind}}}")
167
+
168
+ def signature(self, unit: FuncUnit) -> str:
169
+ emit_name = unit.name.replace(".", "_")
170
+ params = []
171
+ for pname, ptype in unit.params:
172
+ nt = self.param_native_type(ptype)
173
+ # Go: mutated list params need *[]int64 (pointer to slice) for append to propagate
174
+ if ptype == "list" and pname in self.mutated_params:
175
+ nt = "*[]int64"
176
+ params.append(f"{pname} {nt}")
177
+ ret = self.py_to_native(unit.ret_type) if unit.ret_type != "None" else ""
178
+ param_str = ", ".join(params)
179
+ if ret:
180
+ return f"func {emit_name}({param_str}) {ret}"
181
+ return f"func {emit_name}({param_str})"
182
+
183
+ def stmt(self, node: ast.AST) -> None:
184
+ # Go uses `var name type = value` for typed declarations
185
+ if isinstance(node, ast.AnnAssign) and isinstance(node.target, ast.Name):
186
+ name = node.target.id
187
+ ann_type = self._ann_type(node.annotation)
188
+ nt = self.py_to_native(ann_type)
189
+ if node.value is not None:
190
+ val = self.expr(node.value)
191
+ self.lines.append(f"{self.spec.indent * self.indent_lvl}var {name} {nt} = {val}")
192
+ self.var_types[name] = ann_type
193
+ self.declared.add(name)
194
+ else:
195
+ self.lines.append(f"{self.spec.indent * self.indent_lvl}var {name} {nt}")
196
+ self.var_types[name] = ann_type
197
+ self.declared.add(name)
198
+ return
199
+ # Go uses `for` instead of `while`
200
+ if isinstance(node, ast.While):
201
+ ind = self.spec.indent * self.indent_lvl
202
+ self.lines.append(f"{ind}for {self._condition(node.test)} {{")
203
+ self.indent_lvl += 1
204
+ for s in node.body:
205
+ self.stmt(s)
206
+ self.indent_lvl -= 1
207
+ self.lines.append(f"{ind}}}")
208
+ return
209
+ super().stmt(node)
210
+
211
+ def _ann_type(self, node: ast.AST) -> str:
212
+ if isinstance(node, ast.Name):
213
+ t = node.id
214
+ if t in ("int", "float", "bool", "str", "list", "dict", "tuple"):
215
+ return t
216
+ if t in self.class_names:
217
+ return t
218
+ return "any"
219
+ if isinstance(node, ast.Subscript):
220
+ return "list"
221
+ return "any"
222
+
223
+
224
+ def _entry_unit(units: list[FuncUnit], entry: str | None) -> FuncUnit:
225
+ if entry:
226
+ for u in units:
227
+ if u.name == entry:
228
+ return u
229
+ for u in units:
230
+ if u.name == "main":
231
+ return u
232
+ return units[0]
233
+
234
+
235
+ def _emit_structs_go(classes: list) -> str:
236
+ """Emit Go struct definitions and constructors for ClassUnits."""
237
+ out: list[str] = []
238
+ for cls in classes:
239
+ fields_str = ""
240
+ for fname, ftype in cls.fields:
241
+ nt = SPEC.types.get(ftype, SPEC.types.get("float"))
242
+ if ftype == "list":
243
+ nt = "[]int64"
244
+ elif ftype == "None" or not nt:
245
+ nt = "int64"
246
+ elif ftype not in SPEC.types and ftype not in ("int", "float", "bool", "str"):
247
+ nt = ftype # class type
248
+ # Go puts field name before type
249
+ fields_str += f"\t{fname} {nt}\n"
250
+ out.append(f"type {cls.name} struct {{\n{fields_str}}}\n")
251
+ if cls.constructor_params:
252
+ params_str = ", ".join(
253
+ f"{pname} {SPEC.types.get(ptype, 'float64')}"
254
+ for pname, ptype in cls.constructor_params
255
+ )
256
+ init_lines = "\n".join(
257
+ f" {fname}: {fname}," for fname, _ in cls.constructor_params
258
+ )
259
+ out.append(f"func {cls.name}_new({params_str}) {cls.name} {{\n"
260
+ f" return {cls.name}{{\n{init_lines}\n }}\n}}\n")
261
+ else:
262
+ out.append(f"func {cls.name}_new() {cls.name} {{\n"
263
+ f" return {cls.name}{{}}\n}}\n")
264
+ return "\n".join(out)
265
+
266
+
267
+ def emit_go(units: list[FuncUnit], entry: str | None,
268
+ library_mode: bool = False,
269
+ extern_fns: list[FuncUnit] | None = None,
270
+ classes: list | None = None,
271
+ constants: dict | None = None,
272
+ preamble: dict | None = None) -> tuple[str, dict[str, str]]:
273
+ """Return (full_program_source, {func_name: emitted_source}).
274
+
275
+ In library_mode: emit //export functions for C ABI FFI.
276
+ extern_fns: functions from other backends that this Go code calls.
277
+ """
278
+ emitter = GoEmitter(SPEC)
279
+ emitter.library_mode = library_mode
280
+ emitter.constants = constants or {}
281
+ emitted: dict[str, str] = {}
282
+ fns: list[str] = []
283
+
284
+ # register class names
285
+ if classes:
286
+ for cls in classes:
287
+ emitter.class_names.add(cls.name)
288
+ emitter.class_fields[cls.name] = cls.fields
289
+ emitter.class_bases[cls.name] = cls.bases
290
+ emitter.class_properties[cls.name] = cls.properties
291
+ emitter.class_static_methods[cls.name] = cls.static_methods
292
+
293
+ prelude = (
294
+ "package main\n\n"
295
+ 'import "fmt"\n'
296
+ 'import "strings"\n'
297
+ 'import "sort"\n'
298
+ 'import "os"\n'
299
+ 'import "math"\n'
300
+ )
301
+ # add dictContains helper for dict membership tests
302
+ prelude += "\nfunc dictContains(m map[string]int64, k string) bool {\n _, ok := m[k]\n return ok\n}\n"
303
+ # add strContains helper for string membership tests (uses strings import)
304
+ prelude += 'func strContains(s, sub string) bool {\n return strings.Contains(s, sub)\n}\n'
305
+ prelude += 'var _ = strContains\n'
306
+ prelude += 'var _ = sort.Slice\n'
307
+ prelude += 'var _ = os.ReadFile\n'
308
+ prelude += 'var _ = math.Sqrt\n\n'
309
+ # only add import "C" in library mode (cgo needed for //export)
310
+ if library_mode:
311
+ prelude += 'import "C"\n\n'
312
+ else:
313
+ prelude += "\n"
314
+
315
+ # emit extern declarations for cross-backend calls
316
+ if extern_fns:
317
+ decls = []
318
+ for u in extern_fns:
319
+ if not u.supported:
320
+ continue
321
+ ret = SPEC.types.get(u.ret_type, "") if u.ret_type != "None" else ""
322
+ decls.append(f"// {u.name} — from other backend (linked at runtime)")
323
+ prelude += "\n// cross-backend declarations\n" + "\n".join(decls) + "\n\n"
324
+
325
+ # emit struct definitions
326
+ if classes:
327
+ struct_code = _emit_structs_go(classes)
328
+ if struct_code:
329
+ prelude += struct_code + "\n"
330
+
331
+ # build function return type lookup for type inference
332
+ for u in units:
333
+ if u.supported:
334
+ emitter.func_return_types[u.name] = u.ret_type
335
+
336
+ if library_mode:
337
+ for u in units:
338
+ if not u.supported:
339
+ continue
340
+ if u.is_method and u.name.endswith(".__init__"):
341
+ continue
342
+ code = emitter.emit(u)
343
+ if emitter.unsupported_emissions:
344
+ u.supported = False
345
+ u.unsupported_reasons.extend(emitter.unsupported_emissions)
346
+ continue
347
+ # prefix with //export for C ABI
348
+ emit_name = u.name.replace(".", "_")
349
+ code = code.replace(f"func {emit_name}(", f"//export {emit_name}\nfunc {emit_name}(")
350
+ emitted[u.name] = code
351
+ fns.append(code)
352
+ return prelude + "\n".join(fns) + "\n", emitted
353
+
354
+ entry_u = _entry_unit(units, entry)
355
+ for u in units:
356
+ if u.is_method and u.name.endswith(".__init__"):
357
+ continue
358
+ code = emitter.emit(u)
359
+ if emitter.unsupported_emissions:
360
+ u.supported = False
361
+ u.unsupported_reasons.extend(emitter.unsupported_emissions)
362
+ continue
363
+ emitted[u.name] = code
364
+ fns.append(code)
365
+
366
+ if entry_u.name != "main":
367
+ if entry_u.ret_type == "None":
368
+ wrapper = "func main() {\n\t" + entry_u.name + "()\n}\n"
369
+ else:
370
+ wrapper = f"func main() {{\n\t_ = {entry_u.name}()\n}}\n"
371
+ fns.append(wrapper)
372
+ else:
373
+ # Go main() must have no arguments and no return values
374
+ # Rename the user's main() to __ge_main() and add a wrapper
375
+ if entry_u.ret_type != "None":
376
+ fns[-1] = fns[-1].replace(f"func {entry_u.name}(", "func __ge_main(")
377
+ wrapper = "func main() {\n\t__ge_main()\n}\n"
378
+ fns.append(wrapper)
379
+ else:
380
+ fns[-1] = fns[-1].replace(f"func {entry_u.name}(", "func main(")
381
+
382
+ program = prelude + "\n".join(fns) + "\n"
383
+ # inject stdlib runtime (after package/imports, before functions)
384
+ from ..stdlib import get_runtime
385
+ runtime = get_runtime("go")
386
+ if runtime:
387
+ program = prelude + runtime + "\n" + "\n".join(fns) + "\n"
388
+ return program, emitted