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,314 @@
1
+ """Kotlin backend spec + program assembly.
2
+
3
+ Kotlin/Native compiles to a native shared library that exports C ABI
4
+ functions via `@CName`. Interops with C/C++/Rust/Zig/Go/C# through the
5
+ C ABI boundary. Best for Android, Kotlin Multiplatform, and JVM-adjacent code.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import ast
10
+
11
+ from .base import Emitter, Spec
12
+ from ..analyzer import FuncUnit
13
+
14
+ SPEC = Spec(
15
+ name="kotlin",
16
+ types={"int": "Long", "float": "Double", "bool": "Boolean", "str": "String", "None": "Unit"},
17
+ list_type="MutableList<Long>",
18
+ list_param_type="MutableList<Long>",
19
+ list_elem_type="Long",
20
+ borrow_list_arg=False,
21
+ range_call="({lo}..{hi})",
22
+ range_step_call="({lo}..{hi} step {step})",
23
+ len_call="{x}.size.toLong()",
24
+ list_len="{x}.size.toLong()",
25
+ print_int='println({v})',
26
+ print_float='println({v})',
27
+ print_str='println({v})',
28
+ print_bool='println({v})',
29
+ print_generic='println({v})',
30
+ int_cast="{x}.toLong()",
31
+ float_cast="{x}.toDouble()",
32
+ float_div="({l}.toDouble() / {r}.toDouble())",
33
+ floor_div="({l} / {r})",
34
+ sum_call="{it}.sum()",
35
+ abs_int="abs({x})",
36
+ abs_float="abs({x})",
37
+ min_call="{it}.minOrNull()!!",
38
+ max_call="{it}.maxOrNull()!!",
39
+ pow_call="Math.pow({l}.toDouble(), {r}.toDouble()).toLong()",
40
+ append_call="{x}.add({v})",
41
+ index_call="{x}[{i}.toInt()]",
42
+ comment="//",
43
+ fn_template="{sig} {{\n{body}\n}}",
44
+ main_template="",
45
+ var_decl_template="var {target}: {nt} = {val};",
46
+ ffi_prefix="@CName(\"{name}\")\n",
47
+ indent=" ",
48
+ str_concat="{l} + {r}",
49
+ str_len="{x}.length.toLong()",
50
+ str_index="{x}[{i}.toInt()].toLong()",
51
+ str_slice="{x}.substring({start}.toInt(), {end}.toInt())",
52
+ str_slice_start="{x}.substring({start}.toInt())",
53
+ str_slice_end="{x}.substring(0, {end}.toInt())",
54
+ list_concat="run {{ val t = mutableListOf<Long>(); t.addAll({l}); t.addAll({r}); t }}",
55
+ foreach_template="for ({var} in {iter})",
56
+ try_template="try {{\n{body}\n}} catch (e: Exception) {{\n{handler}\n}}",
57
+ struct_template="data class {name} {{\n{fields}\n}}",
58
+ struct_field_template=" val {type}: {name},",
59
+ struct_new_template="fun {name}_new({params}): {name} {{\n{body}\n}}",
60
+ dict_type="HashMap<String, {V}>",
61
+ dict_get="{d}[{k}]",
62
+ dict_set="{d}[{k}] = {v}",
63
+ dict_contains="{d}.containsKey({k})",
64
+ tuple_type="Pair<{T}, {T}>",
65
+ tuple_get="{t}.{field}",
66
+ )
67
+
68
+
69
+ class KotlinEmitter(Emitter):
70
+ def expr(self, node): # type: ignore[override]
71
+ # Add L suffix to integer literals for Long compatibility
72
+ # Note: bool is a subclass of int in Python, so exclude it explicitly
73
+ if isinstance(node, ast.Constant) and isinstance(node.value, int) and not isinstance(node.value, bool):
74
+ return f"{node.value}L"
75
+ return super().expr(node)
76
+
77
+ def _try_stmt(self, node, ind):
78
+ """Kotlin try/catch syntax."""
79
+ self.lines.append(f"{ind}try {{")
80
+ self.indent_lvl += 1
81
+ for s in node.body:
82
+ self.stmt(s)
83
+ self.indent_lvl -= 1
84
+ if node.handlers:
85
+ self.lines.append(f"{ind}}} catch (e: Exception) {{")
86
+ self.indent_lvl += 1
87
+ for s in node.handlers[0].body:
88
+ self.stmt(s)
89
+ self.indent_lvl -= 1
90
+ self.lines.append(f"{ind}}}")
91
+ else:
92
+ self.lines.append(f"{ind}}} catch (e: Exception) {{")
93
+ self.lines.append(f"{ind}}}")
94
+ if node.finalbody:
95
+ self.lines.append(f"{ind}finally {{")
96
+ self.indent_lvl += 1
97
+ for s in node.finalbody:
98
+ self.stmt(s)
99
+ self.indent_lvl -= 1
100
+ self.lines.append(f"{ind}}}")
101
+
102
+ def emit(self, unit: FuncUnit) -> str:
103
+ """Override emit to make mutable copies of parameters that are reassigned."""
104
+ # Find mutated parameters by scanning the body
105
+ param_names = {p[0] for p in unit.params}
106
+ mutated_params = set()
107
+ for node in ast.walk(unit.body):
108
+ if isinstance(node, ast.Assign):
109
+ for t in node.targets:
110
+ if isinstance(t, ast.Name) and t.id in param_names:
111
+ mutated_params.add(t.id)
112
+ elif isinstance(node, ast.AugAssign):
113
+ if isinstance(node.target, ast.Name) and node.target.id in param_names:
114
+ mutated_params.add(node.target.id)
115
+ result = super().emit(unit)
116
+ if mutated_params:
117
+ # Insert `var n_ = n` copies at the start of the function body
118
+ import re
119
+ lines = result.split("\n")
120
+ for i, line in enumerate(lines):
121
+ if "{" in line and "fun" in lines[max(0, i)]:
122
+ for j, pname in enumerate(sorted(mutated_params)):
123
+ lines.insert(i + 1 + j, f" var {pname}_ = {pname}")
124
+ break
125
+ result = "\n".join(lines)
126
+ # Replace param references with mutable version
127
+ for pname in mutated_params:
128
+ result = re.sub(r'\b' + pname + r'\b', pname + "_", result)
129
+ # Fix the signature
130
+ result = result.replace(f"{pname}_: Long", f"{pname}: Long")
131
+ # Fix the copy line
132
+ result = result.replace(f"var {pname}_ = {pname}_", f"var {pname}_ = {pname}")
133
+ return result
134
+
135
+ def for_loop(self, node, ind): # type: ignore[override]
136
+ var = node.target.id if isinstance(node.target, ast.Name) else "_"
137
+ it = node.iter
138
+ if isinstance(it, ast.Call) and getattr(it.func, "id", None) == "range":
139
+ args = it.args
140
+ if len(args) == 1:
141
+ lo, hi = "0", self.expr(args[0])
142
+ else:
143
+ lo, hi = self.expr(args[0]), self.expr(args[1])
144
+ self.var_types[var] = "int"
145
+ # Kotlin 'until' is exclusive (like Python range), '..' is inclusive
146
+ self.lines.append(f"{ind}for ({var} in {lo} until {hi}) {{")
147
+ else:
148
+ iter_s = self.expr(it)
149
+ self.var_types[var] = "long"
150
+ self.lines.append(f"{ind}for ({var} in {iter_s}) {{")
151
+ self.indent_lvl += 1
152
+ for s in node.body:
153
+ self.stmt(s)
154
+ self.indent_lvl -= 1
155
+ self.lines.append(f"{ind}}}")
156
+
157
+ def signature(self, unit: FuncUnit) -> str:
158
+ emit_name = unit.name.replace(".", "_")
159
+ params = []
160
+ for pname, ptype in unit.params:
161
+ nt = self.param_native_type(ptype)
162
+ params.append(f"{pname}: {nt}")
163
+ ret = self.py_to_native(unit.ret_type) if unit.ret_type != "None" else "Unit"
164
+ param_str = ", ".join(params)
165
+ if ret == "Unit":
166
+ return f"fun {emit_name}({param_str})"
167
+ return f"fun {emit_name}({param_str}): {ret}"
168
+
169
+
170
+ def _entry_unit(units: list[FuncUnit], entry: str | None) -> FuncUnit:
171
+ if entry:
172
+ for u in units:
173
+ if u.name == entry:
174
+ return u
175
+ for u in units:
176
+ if u.name == "main":
177
+ return u
178
+ return units[0]
179
+
180
+
181
+ def _emit_structs_kotlin(classes: list) -> str:
182
+ """Emit Kotlin data class definitions and constructors for ClassUnits."""
183
+ out: list[str] = []
184
+ for cls in classes:
185
+ fields_str = ""
186
+ for fname, ftype in cls.fields:
187
+ nt = SPEC.types.get(ftype, SPEC.types.get("float"))
188
+ if ftype == "list":
189
+ nt = "LongArray"
190
+ elif ftype == "None" or not nt:
191
+ nt = "Long"
192
+ fields_str += f" var {fname}: {nt},\n"
193
+ out.append(f"data class {cls.name}(\n{fields_str})\n")
194
+ if cls.constructor_params:
195
+ params_str = ", ".join(
196
+ f"{pname}: {SPEC.types.get(ptype, 'Double')}"
197
+ for pname, ptype in cls.constructor_params
198
+ )
199
+ init_args = ", ".join(fname for fname, _ in cls.constructor_params)
200
+ out.append(f"fun {cls.name}_new({params_str}): {cls.name} {{\n"
201
+ f" return {cls.name}({init_args})\n}}\n")
202
+ else:
203
+ out.append(f"fun {cls.name}_new(): {cls.name} {{\n"
204
+ f" return {cls.name}()\n}}\n")
205
+ return "\n".join(out)
206
+
207
+
208
+ def emit_kotlin(units: list[FuncUnit], entry: str | None,
209
+ library_mode: bool = False,
210
+ extern_fns: list[FuncUnit] | None = None,
211
+ classes: list | None = None,
212
+ constants: dict | None = None,
213
+ preamble: dict | None = None) -> tuple[str, dict[str, str]]:
214
+ """Return (full_program_source, {func_name: emitted_source}).
215
+
216
+ In library_mode: emit @CName exports for C ABI FFI.
217
+ extern_fns: functions from other backends that this Kotlin code calls.
218
+ """
219
+ emitter = KotlinEmitter(SPEC)
220
+ emitter.library_mode = library_mode
221
+ emitter.constants = constants or {}
222
+ emitted: dict[str, str] = {}
223
+ fns: list[str] = []
224
+
225
+ # register class names
226
+ if classes:
227
+ for cls in classes:
228
+ emitter.class_names.add(cls.name)
229
+ emitter.class_fields[cls.name] = cls.fields
230
+ emitter.class_bases[cls.name] = cls.bases
231
+ emitter.class_properties[cls.name] = cls.properties
232
+ emitter.class_static_methods[cls.name] = cls.static_methods
233
+
234
+ prelude = (
235
+ "import kotlin.math.*\n\n"
236
+ )
237
+
238
+ # emit external function declarations for cross-backend calls
239
+ if extern_fns:
240
+ decls = []
241
+ for u in extern_fns:
242
+ if not u.supported:
243
+ continue
244
+ params = ", ".join(
245
+ f"{'LongArray' if ptype == 'list' else SPEC.types.get(ptype, 'Double')} {pname}"
246
+ for pname, ptype in u.params)
247
+ ret = SPEC.types.get(u.ret_type, "Unit") if u.ret_type != "None" else "Unit"
248
+ decls.append(f"external fun {u.name}({params}): {ret}")
249
+ prelude += "\n// cross-backend declarations\n" + "\n".join(decls) + "\n\n"
250
+
251
+ # emit struct definitions
252
+ if classes:
253
+ struct_code = _emit_structs_kotlin(classes)
254
+ if struct_code:
255
+ prelude += struct_code + "\n"
256
+
257
+ # build function return type lookup for type inference
258
+ for u in units:
259
+ if u.supported:
260
+ emitter.func_return_types[u.name] = u.ret_type
261
+
262
+ if library_mode:
263
+ for u in units:
264
+ if not u.supported:
265
+ continue
266
+ if u.is_method and u.name.endswith(".__init__"):
267
+ continue
268
+ code = emitter.emit(u)
269
+ if emitter.unsupported_emissions:
270
+ u.supported = False
271
+ u.unsupported_reasons.extend(emitter.unsupported_emissions)
272
+ continue
273
+ # prefix with @CName for C ABI export
274
+ emit_name = u.name.replace(".", "_")
275
+ code = code.replace(f"fun {emit_name}(", f'@CName("{emit_name}")\nfun {emit_name}(')
276
+ emitted[u.name] = code
277
+ fns.append(code)
278
+ return prelude + "\n".join(fns) + "\n", emitted
279
+
280
+ entry_u = _entry_unit(units, entry)
281
+ for u in units:
282
+ if u.is_method and u.name.endswith(".__init__"):
283
+ continue
284
+ code = emitter.emit(u)
285
+ if emitter.unsupported_emissions:
286
+ u.supported = False
287
+ u.unsupported_reasons.extend(emitter.unsupported_emissions)
288
+ continue
289
+ emitted[u.name] = code
290
+ fns.append(code)
291
+
292
+ if entry_u.name != "main":
293
+ if entry_u.ret_type == "None":
294
+ wrapper = "fun main() {\n " + entry_u.name + "()\n}\n"
295
+ else:
296
+ wrapper = f"fun main() {{\n {entry_u.name}()\n}}\n"
297
+ fns.append(wrapper)
298
+ else:
299
+ # Kotlin main() must return Unit (void)
300
+ # Rename the user's main() to __ge_main() and add a wrapper
301
+ if entry_u.ret_type != "None":
302
+ fns[-1] = fns[-1].replace(f"fun {entry_u.name}(", "fun __ge_main(")
303
+ wrapper = "fun main() {\n __ge_main()\n}\n"
304
+ fns.append(wrapper)
305
+ else:
306
+ fns[-1] = fns[-1].replace(f"fun {entry_u.name}(", "fun main(")
307
+
308
+ program = prelude + "\n".join(fns) + "\n"
309
+ # inject stdlib runtime (after imports, before functions)
310
+ from ..stdlib import get_runtime
311
+ runtime = get_runtime("kotlin")
312
+ if runtime:
313
+ program = prelude + runtime + "\n" + "\n".join(fns) + "\n"
314
+ return program, emitted
@@ -0,0 +1,314 @@
1
+ """Rust backend spec + program assembly."""
2
+ from __future__ import annotations
3
+
4
+ import ast
5
+
6
+ from .base import Emitter, Spec
7
+ from ..analyzer import FuncUnit
8
+
9
+ SPEC = Spec(
10
+ name="rust",
11
+ types={"int": "i64", "float": "f64", "bool": "bool", "str": "String", "None": "()"},
12
+ list_type="Vec<{T}>",
13
+ list_param_type="&[i64]",
14
+ list_elem_type="i64",
15
+ borrow_list_arg=True,
16
+ range_call="({lo}..{hi})",
17
+ range_step_call="({lo}..{hi}).step_by({step} as usize)",
18
+ len_call="{x}.len() as i64",
19
+ list_len="{x}.len() as i64",
20
+ print_int='println!("{{}}", {v})',
21
+ print_float='println!("{{}}", {v})',
22
+ print_str='println!("{{}}", {v})',
23
+ print_bool='println!("{{}}", {v})',
24
+ print_generic='println!("{{:?}}", {v})',
25
+ int_cast="{x} as i64",
26
+ float_cast="{x} as f64",
27
+ float_div="({l} as f64 / {r} as f64)",
28
+ floor_div="({l} / {r})",
29
+ sum_call="{it}.iter().sum::<i64>()",
30
+ abs_int="{x}.abs()",
31
+ abs_float="{x}.abs()",
32
+ min_call="{it}.iter().min().unwrap()",
33
+ max_call="{it}.iter().max().unwrap()",
34
+ pow_call="{l}.pow({r} as u32)",
35
+ append_call="{x}.push({v})",
36
+ index_call="{x}[{i} as usize]",
37
+ comment="//",
38
+ fn_template="{sig} {{\n{body}\n}}",
39
+ main_template="",
40
+ ffi_prefix="#[no_mangle]\npub extern \"C\" ",
41
+ str_concat="{l} + &{r}",
42
+ str_len="{x}.len() as i64",
43
+ str_index="{x}.as_bytes()[{i} as usize] as i64",
44
+ str_slice="{x}[{start}..{end}].to_string()",
45
+ str_slice_start="{x}[{start}..].to_string()",
46
+ str_slice_end="{x}[..{end}].to_string()",
47
+ list_concat="{{ let mut t = {l}; t.extend({r}.iter()); t }}",
48
+ condition_parens=False,
49
+ foreach_template="for {var} in {iter}.iter()",
50
+ try_template="// try/except not natively supported in Rust\n// {body}\n// {handler}",
51
+ struct_template="#[derive(Clone)]\nstruct {name} {{\n{fields}\n}}",
52
+ struct_field_template=" {type}: {name},",
53
+ struct_new_template="fn {name}_new({params}) -> {name} {{\n{body}\n}}",
54
+ dict_type="std::collections::HashMap<String, {V}>",
55
+ dict_get="{d}.get(&{k}).copied().unwrap_or(0)",
56
+ dict_set="{d}.insert({k}, {v})",
57
+ dict_contains="{d}.contains_key(&{k})",
58
+ tuple_type="({T}, {T})",
59
+ tuple_get="{t}.{i}",
60
+ )
61
+
62
+
63
+ def _entry_unit(units: list[FuncUnit], entry: str | None) -> FuncUnit:
64
+ if entry:
65
+ for u in units:
66
+ if u.name == entry:
67
+ return u
68
+ for u in units:
69
+ if u.name == "main":
70
+ return u
71
+ return units[0]
72
+
73
+
74
+ def _emit_structs(classes: list) -> str:
75
+ """Emit Rust struct definitions and constructors for ClassUnits."""
76
+ out: list[str] = []
77
+ for cls in classes:
78
+ # struct definition
79
+ fields_str = ""
80
+ for fname, ftype in cls.fields:
81
+ nt = SPEC.types.get(ftype, SPEC.types.get("float"))
82
+ if ftype == "list":
83
+ nt = SPEC.list_type.format(T=SPEC.list_elem_type)
84
+ elif ftype not in SPEC.types:
85
+ nt = f64_default = SPEC.types.get("float")
86
+ fields_str += f" {fname}: {nt},\n"
87
+ out.append(f"#[derive(Clone)]\nstruct {cls.name} {{\n{fields_str}}}\n")
88
+ # constructor: ClassName_new(params) -> ClassName
89
+ if cls.constructor_params:
90
+ params_str = ", ".join(
91
+ f"{pname}: {SPEC.types.get(ptype, SPEC.types['float'])}"
92
+ for pname, ptype in cls.constructor_params
93
+ )
94
+ init_lines = []
95
+ for fname, ftype in cls.fields:
96
+ init_lines.append(f" {fname}: {fname},")
97
+ body = "\n".join(init_lines)
98
+ out.append(f"fn {cls.name}_new({params_str}) -> {cls.name} {{\n"
99
+ f" {cls.name} {{\n{body}\n }}\n}}\n")
100
+ else:
101
+ field_inits = "\n".join(
102
+ f" {fname}: Default::default()," for fname, _ in cls.fields
103
+ )
104
+ out.append(f"fn {cls.name}_new() -> {cls.name} {{\n"
105
+ f" {cls.name} {{\n{field_inits}\n }}\n}}\n")
106
+ return "\n".join(out)
107
+
108
+
109
+ class RustEmitter(Emitter):
110
+ # Rust keywords that are legal GE identifiers; r# escapes them.
111
+ _KEYWORDS = {"as", "break", "const", "continue", "crate", "dyn", "else",
112
+ "enum", "extern", "false", "fn", "for", "if", "impl", "in",
113
+ "let", "loop", "match", "mod", "move", "mut", "pub", "ref",
114
+ "return", "self", "Self", "static", "struct", "super",
115
+ "trait", "true", "type", "unsafe", "use", "where", "while",
116
+ "async", "await", "abstract", "become", "box", "do",
117
+ "final", "macro", "override", "priv", "try", "typeof",
118
+ "unsized", "virtual", "yield"}
119
+
120
+ def _ident(self, name: str) -> str:
121
+ """Escape Rust keywords with the r# form."""
122
+ if name == "self":
123
+ return "_self"
124
+ from ..idents import escape_local
125
+ return escape_local(name, "rust")
126
+ """Rust-specific emitter that handles try/except using catch_unwind."""
127
+
128
+ def _try_stmt(self, node, ind):
129
+ """Lower try/except to std::panic::catch_unwind."""
130
+ # Check if try body contains a return statement
131
+ has_return = any(isinstance(s, ast.Return) for s in ast.walk(ast.Module(body=node.body, type_ignores=[])))
132
+ if has_return:
133
+ # Can't use catch_unwind with return inside closure
134
+ # Fall back to direct emission with a comment
135
+ self.lines.append(f"{ind}// try/except: body contains return, cannot use catch_unwind")
136
+ for s in node.body:
137
+ self.stmt(s)
138
+ if node.handlers:
139
+ self.lines.append(f"{ind}// except handler (not emitted — return in try body):")
140
+ for s in node.handlers[0].body:
141
+ save = self.lines
142
+ self.lines = []
143
+ self.stmt(s)
144
+ for line in self.lines:
145
+ save.append(f"{ind}// {line.strip()}")
146
+ self.lines = save
147
+ if node.finalbody:
148
+ self.lines.append(f"{ind}// finally:")
149
+ for s in node.finalbody:
150
+ self.stmt(s)
151
+ return
152
+
153
+ # Use catch_unwind to catch panics
154
+ self.lines.append(f"{ind}let __result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {{")
155
+ self.indent_lvl += 1
156
+ for s in node.body:
157
+ self.stmt(s)
158
+ self.indent_lvl -= 1
159
+ self.lines.append(f"{ind}}}));")
160
+ if node.handlers:
161
+ self.lines.append(f"{ind}if __result.is_err() {{")
162
+ self.indent_lvl += 1
163
+ for s in node.handlers[0].body:
164
+ self.stmt(s)
165
+ self.indent_lvl -= 1
166
+ self.lines.append(f"{ind}}}")
167
+ if node.finalbody:
168
+ for s in node.finalbody:
169
+ self.stmt(s)
170
+
171
+
172
+ def emit_rust(units: list[FuncUnit], entry: str | None,
173
+ library_mode: bool = False,
174
+ extern_fns: list[FuncUnit] | None = None,
175
+ export_fns: list[str] | None = None,
176
+ classes: list | None = None,
177
+ constants: dict | None = None,
178
+ preamble: dict | None = None) -> tuple[str, dict[str, str]]:
179
+ """Return (full_program_source, {func_name: emitted_source}).
180
+
181
+ In library_mode: emit C-ABI exports (no main), for FFI consumption.
182
+ extern_fns: functions from other backends (C++) that this Rust code calls.
183
+ Emits `extern "C"` declarations so the linker can resolve them.
184
+ export_fns: names of Rust functions that are called from other backends (C++).
185
+ These functions are marked `#[no_mangle] pub extern "C"` so the
186
+ linker can resolve cross-backend calls.
187
+ classes: list of ClassUnit objects to emit as struct definitions.
188
+ constants: module-level constants to inline (name -> value).
189
+ """
190
+ emitter = RustEmitter(SPEC)
191
+ emitter.library_mode = library_mode
192
+ emitter.export_names = set(export_fns) if export_fns else set()
193
+ emitter.constants = constants or {}
194
+ emitted: dict[str, str] = {}
195
+ fns: list[str] = []
196
+
197
+ # register class names so constructor calls work
198
+ if classes:
199
+ for cls in classes:
200
+ emitter.class_names.add(cls.name)
201
+ emitter.class_fields[cls.name] = cls.fields
202
+ emitter.class_bases[cls.name] = cls.bases
203
+ emitter.class_properties[cls.name] = cls.properties
204
+ emitter.class_static_methods[cls.name] = cls.static_methods
205
+
206
+ # pre-scan all units for mutated list params (so call sites can pass &mut)
207
+ # also build function return type lookup for type inference
208
+ for u in units:
209
+ if not u.supported:
210
+ continue
211
+ emitter.func_return_types[u.name] = u.ret_type
212
+ param_names = {p[0]: i for i, p in enumerate(u.params)}
213
+ mutated = set()
214
+ for node in ast.walk(u.body):
215
+ if isinstance(node, ast.Assign):
216
+ for target in node.targets:
217
+ if isinstance(target, ast.Name) and target.id in param_names:
218
+ mutated.add(param_names[target.id])
219
+ elif isinstance(target, ast.Subscript) and isinstance(target.value, ast.Name) and target.value.id in param_names:
220
+ mutated.add(param_names[target.value.id])
221
+ elif isinstance(node, ast.AugAssign):
222
+ if isinstance(node.target, ast.Name) and node.target.id in param_names:
223
+ mutated.add(param_names[node.target.id])
224
+ elif isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute):
225
+ if node.func.attr == "append" and isinstance(node.func.value, ast.Name):
226
+ if node.func.value.id in param_names:
227
+ mutated.add(param_names[node.func.value.id])
228
+ emitter.func_mutated_params[u.name] = mutated
229
+
230
+ # emit struct definitions before functions
231
+ if classes:
232
+ struct_code = _emit_structs(classes)
233
+ if struct_code:
234
+ fns.append(struct_code)
235
+
236
+ # inject user preamble (file-scope code from ge_preamble("rust", "..."))
237
+ # placed before functions so preamble code (e.g. Win32 FFI) is visible
238
+ if preamble and "rust" in preamble:
239
+ fns.append("// === user preamble (ge_preamble) ===\n" + preamble["rust"])
240
+
241
+ # emit extern "C" declarations for cross-backend calls
242
+ # these are C++ functions that Rust code calls — declared as unsafe externs
243
+ extern_names: set[str] = set()
244
+ if extern_fns:
245
+ for u in extern_fns:
246
+ if not u.supported:
247
+ continue
248
+ ext_params = []
249
+ for pname, ptype in u.params:
250
+ if ptype == "list":
251
+ ext_params.append(f"{pname}: {SPEC.list_param_type}")
252
+ else:
253
+ nt = SPEC.types.get(ptype, "f64")
254
+ ext_params.append(f"{pname}: {nt}")
255
+ ret = SPEC.types.get(u.ret_type, "()") if u.ret_type != "None" else "()"
256
+ ext_sig = ", ".join(ext_params)
257
+ fns.append(f'extern "C" {{\n fn {u.name}({ext_sig}) -> {ret};\n}}\n')
258
+ extern_names.add(u.name)
259
+ emitter.extern_names = extern_names
260
+
261
+ if library_mode:
262
+ for u in units:
263
+ if not u.supported:
264
+ continue
265
+ if u.is_method and u.name.endswith(".__init__"):
266
+ continue # constructor emitted by _emit_structs
267
+ code = emitter.emit(u)
268
+ if emitter.unsupported_emissions:
269
+ u.supported = False
270
+ u.unsupported_reasons.extend(emitter.unsupported_emissions)
271
+ continue
272
+ emitted[u.name] = code
273
+ fns.append(code)
274
+ return "\n".join(fns) + "\n", emitted
275
+
276
+ entry_u = _entry_unit(units, entry)
277
+ for u in units:
278
+ if u.is_method and u.name.endswith(".__init__"):
279
+ continue # constructor emitted by _emit_structs
280
+ if u is entry_u and u.name == "main":
281
+ # Rust main() must return (). If the source main() returns int,
282
+ # strip the return value by overriding the signature and adding a suffix.
283
+ if u.ret_type == "None":
284
+ code = emitter.emit(u, sig_override="fn main()")
285
+ else:
286
+ # Emit with a wrapper that calls main() and ignores the return
287
+ code = emitter.emit(u, sig_override="fn __ge_main() -> i64")
288
+ else:
289
+ code = emitter.emit(u)
290
+ if emitter.unsupported_emissions:
291
+ u.supported = False
292
+ u.unsupported_reasons.extend(emitter.unsupported_emissions)
293
+ continue
294
+ emitted[u.name] = code
295
+ fns.append(code)
296
+
297
+ if entry_u.name != "main":
298
+ if entry_u.ret_type == "None":
299
+ wrapper = f"fn main() {{\n {entry_u.name}();\n}}\n"
300
+ else:
301
+ wrapper = f"fn main() {{\n let _ = {entry_u.name}();\n}}\n"
302
+ fns.append(wrapper)
303
+ elif entry_u.ret_type != "None":
304
+ # main() returns int — we renamed it to __ge_main(), add a wrapper
305
+ wrapper = f"fn main() {{\n let _ = __ge_main();\n}}\n"
306
+ fns.append(wrapper)
307
+
308
+ program = "\n".join(fns) + "\n"
309
+ # inject stdlib runtime
310
+ from ..stdlib import get_runtime
311
+ runtime = get_runtime("rust")
312
+ if runtime:
313
+ program = runtime + "\n" + program
314
+ return program, emitted