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.
- package/CHANGELOG.md +65 -0
- package/LICENSE +21 -0
- package/README.md +535 -0
- package/bin/ge.js +112 -0
- package/package.json +62 -0
- package/python/pyeffic/__init__.py +9 -0
- package/python/pyeffic/__main__.py +6 -0
- package/python/pyeffic/analyzer.py +464 -0
- package/python/pyeffic/apisurface.py +238 -0
- package/python/pyeffic/autoselect.py +327 -0
- package/python/pyeffic/backends.py +87 -0
- package/python/pyeffic/bench.py +233 -0
- package/python/pyeffic/cli.py +184 -0
- package/python/pyeffic/compiler.py +421 -0
- package/python/pyeffic/config.py +383 -0
- package/python/pyeffic/dartgen.py +441 -0
- package/python/pyeffic/deploy.py +586 -0
- package/python/pyeffic/diagnostics.py +194 -0
- package/python/pyeffic/difftest.py +424 -0
- package/python/pyeffic/downloader.py +307 -0
- package/python/pyeffic/emitters/__init__.py +11 -0
- package/python/pyeffic/emitters/base.py +2359 -0
- package/python/pyeffic/emitters/cpp.py +266 -0
- package/python/pyeffic/emitters/csharp.py +342 -0
- package/python/pyeffic/emitters/dart.py +349 -0
- package/python/pyeffic/emitters/go.py +388 -0
- package/python/pyeffic/emitters/kotlin.py +314 -0
- package/python/pyeffic/emitters/rust.py +314 -0
- package/python/pyeffic/emitters/zig.py +411 -0
- package/python/pyeffic/ffi.py +49 -0
- package/python/pyeffic/frontends/__init__.py +94 -0
- package/python/pyeffic/frontends/hybrid.py +709 -0
- package/python/pyeffic/frontends/typescript.py +965 -0
- package/python/pyeffic/ge_cli.py +1148 -0
- package/python/pyeffic/golden.py +348 -0
- package/python/pyeffic/idents.py +206 -0
- package/python/pyeffic/modules.py +220 -0
- package/python/pyeffic/packer.py +222 -0
- package/python/pyeffic/pipeline.py +797 -0
- package/python/pyeffic/reactgen.py +966 -0
- package/python/pyeffic/researcher.py +177 -0
- package/python/pyeffic/scaffold.py +397 -0
- package/python/pyeffic/stdlib.py +246 -0
- package/python/pyeffic/styling.py +220 -0
- package/python/pyeffic/templates/desktop_gui/README.md +106 -0
- package/python/pyeffic/templates/desktop_gui/app/__init__.py +0 -0
- package/python/pyeffic/templates/desktop_gui/app/core/__init__.py +0 -0
- package/python/pyeffic/templates/desktop_gui/app/core/add.ge.py +13 -0
- package/python/pyeffic/templates/desktop_gui/app/core/factorial.ge.py +20 -0
- package/python/pyeffic/templates/desktop_gui/app/core/fibonacci.ge.py +25 -0
- package/python/pyeffic/templates/desktop_gui/app/core/gcd.ge.py +19 -0
- package/python/pyeffic/templates/desktop_gui/app/core/is_prime.ge.py +24 -0
- package/python/pyeffic/templates/desktop_gui/app/core/multiply.ge.py +13 -0
- package/python/pyeffic/templates/desktop_gui/app/core/power.ge.py +25 -0
- package/python/pyeffic/templates/desktop_gui/app/main.ge.py +49 -0
- package/python/pyeffic/templates/desktop_gui/app/memory/__init__.py +0 -0
- package/python/pyeffic/templates/desktop_gui/app/memory/buffer.ge.py +26 -0
- package/python/pyeffic/templates/desktop_gui/app/memory/limits.ge.py +47 -0
- package/python/pyeffic/templates/desktop_gui/app/memory/state.ge.py +44 -0
- package/python/pyeffic/templates/desktop_gui/app/ui/__init__.py +0 -0
- package/python/pyeffic/templates/desktop_gui/app/ui/layout.ge.py +64 -0
- package/python/pyeffic/templates/desktop_gui/app/ui/render.ge.py +87 -0
- package/python/pyeffic/templates/desktop_gui/app/ui/theme.ge.py +147 -0
- package/python/pyeffic/templates/desktop_gui/app/ui/widgets.ge.py +105 -0
- package/python/pyeffic/templates/desktop_gui/desktop/__init__.py +1 -0
- package/python/pyeffic/templates/desktop_gui/desktop/main.ge.py +258 -0
- package/python/pyeffic/templates/desktop_gui/ge.toml +16 -0
- package/python/pyeffic/templates/desktop_gui/tests/__init__.py +0 -0
- package/python/pyeffic/templates/desktop_gui/tests/ge_loader.py +76 -0
- package/python/pyeffic/templates/desktop_gui/tests/test_app.py +173 -0
- package/python/pyeffic/templates/web_react/README.md +115 -0
- package/python/pyeffic/templates/web_react/app/__init__.py +0 -0
- package/python/pyeffic/templates/web_react/app/core/__init__.py +0 -0
- package/python/pyeffic/templates/web_react/app/core/add.ge.py +9 -0
- package/python/pyeffic/templates/web_react/app/core/factorial.ge.py +16 -0
- package/python/pyeffic/templates/web_react/app/core/fibonacci.ge.py +21 -0
- package/python/pyeffic/templates/web_react/app/core/is_prime.ge.py +20 -0
- package/python/pyeffic/templates/web_react/app/core/multiply.ge.py +9 -0
- package/python/pyeffic/templates/web_react/app/main.ge.py +25 -0
- package/python/pyeffic/templates/web_react/app/memory/__init__.py +0 -0
- package/python/pyeffic/templates/web_react/app/memory/buffer.ge.py +25 -0
- package/python/pyeffic/templates/web_react/app/memory/limits.ge.py +51 -0
- package/python/pyeffic/templates/web_react/ge.toml +23 -0
- package/python/pyeffic/templates/web_react/tests/__init__.py +0 -0
- package/python/pyeffic/templates/web_react/tests/ge_loader.py +68 -0
- package/python/pyeffic/templates/web_react/tests/test_app.py +105 -0
- package/python/pyeffic/templates/web_react/ui/main.ge.ui +33 -0
- package/python/pyeffic/templates/web_react/web/__init__.py +0 -0
- package/python/pyeffic/templates/web_react/web/server.ge.py +78 -0
- package/python/pyeffic/ts2py.py +657 -0
- package/python/pyeffic/typecheck.py +232 -0
- package/python/pyeffic/ui.py +154 -0
- package/python/pyeffic/ui_dsl.py +618 -0
- package/python/pyeffic/widgets.py +87 -0
- package/scripts/README.md +42 -0
- 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
|