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,266 @@
|
|
|
1
|
+
"""C++ 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="cpp",
|
|
11
|
+
types={"int": "int64_t", "float": "double", "bool": "bool", "str": "std::string", "None": "void"},
|
|
12
|
+
list_type="std::vector<{T}>",
|
|
13
|
+
list_param_type="std::vector<int64_t>&",
|
|
14
|
+
list_elem_type="int64_t",
|
|
15
|
+
borrow_list_arg=False,
|
|
16
|
+
range_call="for_range({lo}, {hi})",
|
|
17
|
+
range_step_call="for_range({lo}, {hi}, {step})",
|
|
18
|
+
len_call="static_cast<int64_t>({x}.size())",
|
|
19
|
+
list_len="static_cast<int64_t>({x}.size())",
|
|
20
|
+
print_int='std::cout << ({v}) << std::endl',
|
|
21
|
+
print_float='std::cout << ({v}) << std::endl',
|
|
22
|
+
print_str='std::cout << ({v}) << std::endl',
|
|
23
|
+
print_bool='std::cout << ({v}) << std::endl',
|
|
24
|
+
print_generic='std::cout << ({v}) << std::endl',
|
|
25
|
+
int_cast="static_cast<int64_t>({x})",
|
|
26
|
+
float_cast="static_cast<double>({x})",
|
|
27
|
+
float_div="(static_cast<double>({l}) / static_cast<double>({r}))",
|
|
28
|
+
floor_div="({l} / {r})",
|
|
29
|
+
sum_call="std::accumulate({it}.begin(), {it}.end(), 0LL)",
|
|
30
|
+
abs_int="std::abs({x})",
|
|
31
|
+
abs_float="std::fabs({x})",
|
|
32
|
+
min_call="*std::min_element({it}.begin(), {it}.end())",
|
|
33
|
+
max_call="*std::max_element({it}.begin(), {it}.end())",
|
|
34
|
+
pow_call="std::pow({l}, {r})",
|
|
35
|
+
append_call="{x}.push_back({v})",
|
|
36
|
+
index_call="{x}[{i}]",
|
|
37
|
+
comment="//",
|
|
38
|
+
fn_template="{sig} {{\n{body}\n}}",
|
|
39
|
+
main_template="",
|
|
40
|
+
ffi_prefix='extern "C" ',
|
|
41
|
+
str_concat="{l} + {r}",
|
|
42
|
+
str_len="static_cast<int64_t>({x}.length())",
|
|
43
|
+
str_index="static_cast<int64_t>({x}[{i}])",
|
|
44
|
+
str_slice="{x}.substr({start}, {len})",
|
|
45
|
+
str_slice_start="{x}.substr({start})",
|
|
46
|
+
str_slice_end="{x}.substr(0, {end})",
|
|
47
|
+
list_concat="[&]() {{ auto t = {l}; auto s = {r}; t.insert(t.end(), s.begin(), s.end()); return t; }}()",
|
|
48
|
+
foreach_template="for (auto {var} : {iter})",
|
|
49
|
+
try_template="try {{\n{body}\n}} catch (...) {{\n{handler}\n}}",
|
|
50
|
+
struct_template="struct {name} {{\n{fields}\n}};",
|
|
51
|
+
struct_field_template=" {type} {name};",
|
|
52
|
+
struct_new_template="{name} {name}_new({params}) {{\n{body}\n}}",
|
|
53
|
+
dict_type="std::map<std::string, {V}>",
|
|
54
|
+
dict_get="{d}.at({k})",
|
|
55
|
+
dict_set="{d}[{k}] = {v}",
|
|
56
|
+
dict_contains="({d}.find({k}) != {d}.end())",
|
|
57
|
+
tuple_type="std::tuple<{T}, {T}>",
|
|
58
|
+
tuple_get="std::get<{i}>({t})",
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class CppEmitter(Emitter):
|
|
63
|
+
def for_loop(self, node, ind): # type: ignore[override]
|
|
64
|
+
var = node.target.id if isinstance(node.target, ast.Name) else "_"
|
|
65
|
+
it = node.iter
|
|
66
|
+
if isinstance(it, ast.Call) and getattr(it.func, "id", None) == "range":
|
|
67
|
+
args = it.args
|
|
68
|
+
if len(args) == 1:
|
|
69
|
+
lo, hi, step = "0", self.expr(args[0]), "1"
|
|
70
|
+
elif len(args) == 2:
|
|
71
|
+
lo, hi, step = self.expr(args[0]), self.expr(args[1]), "1"
|
|
72
|
+
else:
|
|
73
|
+
lo, hi, step = self.expr(args[0]), self.expr(args[1]), self.expr(args[2])
|
|
74
|
+
self.var_types[var] = "int"
|
|
75
|
+
if step == "1":
|
|
76
|
+
self.lines.append(f"{ind}for (int64_t {var} = {lo}; {var} < {hi}; ++{var}) {{")
|
|
77
|
+
else:
|
|
78
|
+
self.lines.append(
|
|
79
|
+
f"{ind}for (int64_t {var} = {lo}; {var} < {hi}; {var} += {step}) {{"
|
|
80
|
+
)
|
|
81
|
+
else:
|
|
82
|
+
iter_s = self.expr(it)
|
|
83
|
+
self.var_types[var] = "int"
|
|
84
|
+
self.lines.append(f"{ind}for (auto& {var} : {iter_s}) {{")
|
|
85
|
+
self.indent_lvl += 1
|
|
86
|
+
for s in node.body:
|
|
87
|
+
self.stmt(s)
|
|
88
|
+
self.indent_lvl -= 1
|
|
89
|
+
self.lines.append(f"{ind}}}")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _entry_unit(units: list[FuncUnit], entry: str | None) -> FuncUnit:
|
|
93
|
+
if entry:
|
|
94
|
+
for u in units:
|
|
95
|
+
if u.name == entry:
|
|
96
|
+
return u
|
|
97
|
+
for u in units:
|
|
98
|
+
if u.name == "main":
|
|
99
|
+
return u
|
|
100
|
+
return units[0]
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _emit_structs_cpp(classes: list) -> str:
|
|
104
|
+
"""Emit C++ struct definitions and constructors for ClassUnits."""
|
|
105
|
+
out: list[str] = []
|
|
106
|
+
for cls in classes:
|
|
107
|
+
fields_str = ""
|
|
108
|
+
for fname, ftype in cls.fields:
|
|
109
|
+
nt = SPEC.types.get(ftype, SPEC.types.get("float"))
|
|
110
|
+
if ftype == "list":
|
|
111
|
+
nt = "std::vector<int64_t>"
|
|
112
|
+
elif ftype not in SPEC.types:
|
|
113
|
+
nt = "double"
|
|
114
|
+
fields_str += f" {nt} {fname};\n"
|
|
115
|
+
out.append(f"struct {cls.name} {{\n{fields_str}}};\n")
|
|
116
|
+
# constructor
|
|
117
|
+
if cls.constructor_params:
|
|
118
|
+
params_str = ", ".join(
|
|
119
|
+
f"{SPEC.types.get(ptype, 'double')} {pname}"
|
|
120
|
+
for pname, ptype in cls.constructor_params
|
|
121
|
+
)
|
|
122
|
+
init_list = ", ".join(fname for fname, _ in cls.constructor_params)
|
|
123
|
+
out.append(f"{cls.name} {cls.name}_new({params_str}) {{\n"
|
|
124
|
+
f" return {cls.name}{{{init_list}}};\n}}\n")
|
|
125
|
+
else:
|
|
126
|
+
out.append(f"{cls.name} {cls.name}_new() {{\n"
|
|
127
|
+
f" return {cls.name}{{}};\n}}\n")
|
|
128
|
+
return "\n".join(out)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def emit_cpp(units: list[FuncUnit], entry: str | None,
|
|
132
|
+
library_mode: bool = False,
|
|
133
|
+
extern_fns: list[FuncUnit] | None = None,
|
|
134
|
+
classes: list | None = None,
|
|
135
|
+
constants: dict | None = None,
|
|
136
|
+
preamble: dict | None = None) -> tuple[str, dict[str, str]]:
|
|
137
|
+
emitter = CppEmitter(SPEC)
|
|
138
|
+
emitter.library_mode = library_mode
|
|
139
|
+
emitter.constants = constants or {}
|
|
140
|
+
# in multi-backend mode, force extern "C" on all functions so Rust can link
|
|
141
|
+
emitter.force_extern_c = extern_fns is not None
|
|
142
|
+
emitted: dict[str, str] = {}
|
|
143
|
+
fns: list[str] = []
|
|
144
|
+
|
|
145
|
+
# register class names
|
|
146
|
+
if classes:
|
|
147
|
+
for cls in classes:
|
|
148
|
+
emitter.class_names.add(cls.name)
|
|
149
|
+
emitter.class_fields[cls.name] = cls.fields
|
|
150
|
+
emitter.class_bases[cls.name] = cls.bases
|
|
151
|
+
emitter.class_properties[cls.name] = cls.properties
|
|
152
|
+
emitter.class_static_methods[cls.name] = cls.static_methods
|
|
153
|
+
|
|
154
|
+
prelude = (
|
|
155
|
+
"#include <cstdint>\n"
|
|
156
|
+
"#include <iostream>\n"
|
|
157
|
+
"#include <vector>\n"
|
|
158
|
+
"#include <map>\n"
|
|
159
|
+
"#include <tuple>\n"
|
|
160
|
+
"#include <cmath>\n"
|
|
161
|
+
"#include <numeric>\n"
|
|
162
|
+
"#include <algorithm>\n"
|
|
163
|
+
"#include <string>\n\n"
|
|
164
|
+
"using std::cout;\n"
|
|
165
|
+
"using std::endl;\n\n"
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
# emit extern "C" declarations for cross-backend calls (Rust functions)
|
|
169
|
+
if extern_fns:
|
|
170
|
+
decls = []
|
|
171
|
+
for u in extern_fns:
|
|
172
|
+
if not u.supported:
|
|
173
|
+
continue
|
|
174
|
+
params = ", ".join(
|
|
175
|
+
f"{'const std::vector<int64_t>&' if ptype == 'list' else SPEC.types.get(ptype, 'double')} {pname}"
|
|
176
|
+
for pname, ptype in u.params)
|
|
177
|
+
ret = SPEC.types.get(u.ret_type, "void") if u.ret_type != "None" else "void"
|
|
178
|
+
decls.append(f'extern "C" {ret} {u.name}({params});')
|
|
179
|
+
prelude += "\n// cross-backend declarations (Rust functions called from C++)\n" + "\n".join(decls) + "\n\n"
|
|
180
|
+
|
|
181
|
+
# emit struct definitions
|
|
182
|
+
if classes:
|
|
183
|
+
struct_code = _emit_structs_cpp(classes)
|
|
184
|
+
if struct_code:
|
|
185
|
+
prelude += struct_code + "\n"
|
|
186
|
+
|
|
187
|
+
# build function return type lookup for type inference
|
|
188
|
+
for u in units:
|
|
189
|
+
if u.supported:
|
|
190
|
+
emitter.func_return_types[u.name] = u.ret_type
|
|
191
|
+
|
|
192
|
+
# emit forward declarations so functions can call each other regardless of order
|
|
193
|
+
# (also needed before preamble so preamble code can call GE-compiled functions)
|
|
194
|
+
forward_decls: list[str] = []
|
|
195
|
+
for u in units:
|
|
196
|
+
if not u.supported or (u.is_method and u.name.endswith(".__init__")):
|
|
197
|
+
continue
|
|
198
|
+
params = ", ".join(
|
|
199
|
+
f"{'std::vector<int64_t>' if ptype == 'list' else SPEC.types.get(ptype, 'double')} {pname}"
|
|
200
|
+
for pname, ptype in u.params
|
|
201
|
+
) or ""
|
|
202
|
+
# main always returns int in C++; list returns std::vector<int64_t>
|
|
203
|
+
if u.name == "main":
|
|
204
|
+
ret = "int"
|
|
205
|
+
elif u.ret_type == "None":
|
|
206
|
+
ret = "void"
|
|
207
|
+
elif u.ret_type == "list":
|
|
208
|
+
ret = "std::vector<int64_t>"
|
|
209
|
+
else:
|
|
210
|
+
ret = SPEC.types.get(u.ret_type, "void")
|
|
211
|
+
if emitter.force_extern_c:
|
|
212
|
+
forward_decls.append(f'extern "C" {ret} {u.name}({params});')
|
|
213
|
+
else:
|
|
214
|
+
forward_decls.append(f'{ret} {u.name}({params});')
|
|
215
|
+
if forward_decls:
|
|
216
|
+
prelude += "\n// forward declarations\n" + "\n".join(forward_decls) + "\n\n"
|
|
217
|
+
|
|
218
|
+
# inject user preamble (file-scope code from ge_preamble("cpp", "..."))
|
|
219
|
+
# (after forward declarations so preamble can call GE-compiled functions)
|
|
220
|
+
if preamble and "cpp" in preamble:
|
|
221
|
+
prelude += "\n// === user preamble (ge_preamble) ===\n" + preamble["cpp"] + "\n\n"
|
|
222
|
+
|
|
223
|
+
if library_mode:
|
|
224
|
+
for u in units:
|
|
225
|
+
if not u.supported:
|
|
226
|
+
continue
|
|
227
|
+
if u.is_method and u.name.endswith(".__init__"):
|
|
228
|
+
continue
|
|
229
|
+
code = emitter.emit(u)
|
|
230
|
+
if emitter.unsupported_emissions:
|
|
231
|
+
u.supported = False
|
|
232
|
+
u.unsupported_reasons.extend(emitter.unsupported_emissions)
|
|
233
|
+
continue
|
|
234
|
+
emitted[u.name] = code
|
|
235
|
+
fns.append(code)
|
|
236
|
+
return prelude + "\n".join(fns) + "\n", emitted
|
|
237
|
+
|
|
238
|
+
entry_u = _entry_unit(units, entry)
|
|
239
|
+
for u in units:
|
|
240
|
+
if u.is_method and u.name.endswith(".__init__"):
|
|
241
|
+
continue
|
|
242
|
+
if u is entry_u and u.name == "main":
|
|
243
|
+
code = emitter.emit(u, sig_override="int main()", body_suffix="return 0;")
|
|
244
|
+
else:
|
|
245
|
+
code = emitter.emit(u)
|
|
246
|
+
if emitter.unsupported_emissions:
|
|
247
|
+
u.supported = False
|
|
248
|
+
u.unsupported_reasons.extend(emitter.unsupported_emissions)
|
|
249
|
+
continue
|
|
250
|
+
emitted[u.name] = code
|
|
251
|
+
fns.append(code)
|
|
252
|
+
|
|
253
|
+
if entry_u.name != "main":
|
|
254
|
+
if entry_u.ret_type == "None":
|
|
255
|
+
wrapper = "int main() {\n " + entry_u.name + "();\n return 0;\n}\n"
|
|
256
|
+
else:
|
|
257
|
+
wrapper = "int main() {\n auto _r = " + entry_u.name + "();\n (void)_r;\n return 0;\n}\n"
|
|
258
|
+
fns.append(wrapper)
|
|
259
|
+
|
|
260
|
+
program = prelude + "\n".join(fns) + "\n"
|
|
261
|
+
# inject stdlib runtime after the prelude so #include <cmath> is in scope
|
|
262
|
+
from ..stdlib import get_runtime
|
|
263
|
+
runtime = get_runtime("cpp")
|
|
264
|
+
if runtime:
|
|
265
|
+
program = prelude + runtime + "\n" + "\n".join(fns) + "\n"
|
|
266
|
+
return program, emitted
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
"""C# backend spec + program assembly.
|
|
2
|
+
|
|
3
|
+
Compiles via .NET NativeAOT to a native shared library that exports C ABI
|
|
4
|
+
functions via [UnmanagedCallersOnly]. Interops with Rust/C++/Zig/Go through
|
|
5
|
+
the C ABI boundary.
|
|
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="csharp",
|
|
16
|
+
types={"int": "long", "float": "double", "bool": "bool", "str": "string", "None": "void"},
|
|
17
|
+
list_type="List<long>",
|
|
18
|
+
list_param_type="List<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}.Length",
|
|
24
|
+
list_len="{x}.Count",
|
|
25
|
+
print_int='Console.WriteLine({v})',
|
|
26
|
+
print_float='Console.WriteLine({v})',
|
|
27
|
+
print_str='Console.WriteLine({v})',
|
|
28
|
+
print_bool='Console.WriteLine({v})',
|
|
29
|
+
print_generic='Console.WriteLine({v})',
|
|
30
|
+
int_cast="(long)({x})",
|
|
31
|
+
float_cast="(double)({x})",
|
|
32
|
+
float_div="((double)({l}) / (double)({r}))",
|
|
33
|
+
floor_div="({l} / {r})",
|
|
34
|
+
sum_call="{it}.Sum()",
|
|
35
|
+
abs_int="Math.Abs({x})",
|
|
36
|
+
abs_float="Math.Abs({x})",
|
|
37
|
+
min_call="{it}.Min()",
|
|
38
|
+
max_call="{it}.Max()",
|
|
39
|
+
pow_call="Math.Pow({l}, {r})",
|
|
40
|
+
append_call="{x}.Add({v})",
|
|
41
|
+
index_call="{x}[(int)({i})]",
|
|
42
|
+
comment="//",
|
|
43
|
+
fn_template="{sig}\n{{\n{body}\n}}",
|
|
44
|
+
main_template="",
|
|
45
|
+
ffi_prefix="[UnmanagedCallersOnly]\npublic static ",
|
|
46
|
+
indent=" ",
|
|
47
|
+
str_concat="{l} + {r}",
|
|
48
|
+
str_len="{x}.Length",
|
|
49
|
+
str_index="(long)({x}[{i}])",
|
|
50
|
+
str_slice="{x}.Substring({start}, {len})",
|
|
51
|
+
str_slice_start="{x}.Substring({start})",
|
|
52
|
+
str_slice_end="{x}.Substring(0, {end})",
|
|
53
|
+
list_concat="new List<long>({l}).Concat({r}).ToList()",
|
|
54
|
+
foreach_template="foreach (var {var} in {iter})",
|
|
55
|
+
try_template="try {{\n{body}\n}} catch {{\n{handler}\n}}",
|
|
56
|
+
struct_template="struct {name} {{\n{fields}\n}}",
|
|
57
|
+
struct_field_template=" public {type} {name};",
|
|
58
|
+
struct_new_template="static {name} {name}_New({params}) {{\n{body}\n}}",
|
|
59
|
+
dict_type="Dictionary<string, {V}>",
|
|
60
|
+
dict_get="{d}[{k}]",
|
|
61
|
+
dict_set="{d}[{k}] = {v}",
|
|
62
|
+
dict_contains="{d}.ContainsKey({k})",
|
|
63
|
+
tuple_type="({T}, {T})",
|
|
64
|
+
tuple_get="{t}.Item{i1}",
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class CSharpEmitter(Emitter):
|
|
69
|
+
# C# reserved keywords that need @ prefix when used as identifiers
|
|
70
|
+
_KEYWORDS = {"abstract", "as", "base", "bool", "break", "byte", "case",
|
|
71
|
+
"catch", "char", "checked", "class", "const", "continue",
|
|
72
|
+
"decimal", "default", "delegate", "do", "double", "else",
|
|
73
|
+
"enum", "event", "explicit", "extern", "false", "finally",
|
|
74
|
+
"fixed", "float", "for", "foreach", "goto", "if", "implicit",
|
|
75
|
+
"in", "int", "interface", "internal", "is", "lock", "long",
|
|
76
|
+
"namespace", "new", "null", "object", "operator", "out",
|
|
77
|
+
"override", "params", "private", "protected", "public",
|
|
78
|
+
"readonly", "ref", "return", "sbyte", "sealed", "short",
|
|
79
|
+
"sizeof", "stackalloc", "static", "string", "struct",
|
|
80
|
+
"switch", "this", "throw", "true", "try", "typeof", "uint",
|
|
81
|
+
"ulong", "unchecked", "unsafe", "ushort", "using",
|
|
82
|
+
"virtual", "void", "volatile", "while"}
|
|
83
|
+
|
|
84
|
+
def _esc(self, name: str) -> str:
|
|
85
|
+
if name in self._KEYWORDS:
|
|
86
|
+
return "@" + name
|
|
87
|
+
return name
|
|
88
|
+
|
|
89
|
+
def _ident(self, name: str) -> str:
|
|
90
|
+
"""Escape C# reserved words (e.g. a GE parameter named `base`)."""
|
|
91
|
+
return self._esc(name)
|
|
92
|
+
|
|
93
|
+
# note: function names are renamed in the shared IR (pyeffic.idents) so
|
|
94
|
+
# cross-backend symbols agree; _ident only covers locals and parameters.
|
|
95
|
+
|
|
96
|
+
def for_loop(self, node, ind): # type: ignore[override]
|
|
97
|
+
var = node.target.id if isinstance(node.target, ast.Name) else "_"
|
|
98
|
+
it = node.iter
|
|
99
|
+
if isinstance(it, ast.Call) and getattr(it.func, "id", None) == "range":
|
|
100
|
+
args = it.args
|
|
101
|
+
if len(args) == 1:
|
|
102
|
+
lo, hi, step = "0", self.expr(args[0]), "1"
|
|
103
|
+
elif len(args) == 2:
|
|
104
|
+
lo, hi, step = self.expr(args[0]), self.expr(args[1]), "1"
|
|
105
|
+
else:
|
|
106
|
+
lo, hi, step = self.expr(args[0]), self.expr(args[1]), self.expr(args[2])
|
|
107
|
+
self.var_types[var] = "int"
|
|
108
|
+
self.lines.append(f"{ind}for (long {var} = {lo}; {var} < {hi}; {var}++) {{")
|
|
109
|
+
else:
|
|
110
|
+
iter_s = self.expr(it)
|
|
111
|
+
self.var_types[var] = "long"
|
|
112
|
+
self.lines.append(f"{ind}foreach (var {var} in {iter_s}) {{")
|
|
113
|
+
self.indent_lvl += 1
|
|
114
|
+
for s in node.body:
|
|
115
|
+
self.stmt(s)
|
|
116
|
+
self.indent_lvl -= 1
|
|
117
|
+
self.lines.append(f"{ind}}}")
|
|
118
|
+
|
|
119
|
+
def signature(self, unit: FuncUnit) -> str:
|
|
120
|
+
emit_name = unit.name.replace(".", "_")
|
|
121
|
+
params = []
|
|
122
|
+
for pname, ptype in unit.params:
|
|
123
|
+
nt = self.param_native_type(ptype)
|
|
124
|
+
params.append(f"{nt} {self._ident(pname)}")
|
|
125
|
+
ret = self.py_to_native(unit.ret_type) if unit.ret_type != "None" else "void"
|
|
126
|
+
param_str = ", ".join(params)
|
|
127
|
+
base_sig = f"static {ret} {emit_name}({param_str})"
|
|
128
|
+
# add C ABI export prefix in library mode for FFI-exported functions
|
|
129
|
+
if self.library_mode and getattr(unit, "ffi_export", False) and self.spec.ffi_prefix:
|
|
130
|
+
return self.spec.ffi_prefix + base_sig
|
|
131
|
+
return base_sig
|
|
132
|
+
|
|
133
|
+
def stmt(self, node): # type: ignore[override]
|
|
134
|
+
# escape C# keywords in variable declarations
|
|
135
|
+
if isinstance(node, ast.AnnAssign) and isinstance(node.target, ast.Name):
|
|
136
|
+
name = self._esc(node.target.id)
|
|
137
|
+
ann_type = self._ann_type(node.annotation)
|
|
138
|
+
nt = self.py_to_native(ann_type)
|
|
139
|
+
if node.value is not None:
|
|
140
|
+
val = self.expr(node.value)
|
|
141
|
+
self.lines.append(f"{self.spec.indent * self.indent_lvl}{nt} {name} = {val};")
|
|
142
|
+
self.var_types[node.target.id] = ann_type
|
|
143
|
+
self.declared.add(node.target.id)
|
|
144
|
+
else:
|
|
145
|
+
self.lines.append(f"{self.spec.indent * self.indent_lvl}{nt} {name};")
|
|
146
|
+
self.var_types[node.target.id] = ann_type
|
|
147
|
+
self.declared.add(node.target.id)
|
|
148
|
+
return
|
|
149
|
+
super().stmt(node)
|
|
150
|
+
|
|
151
|
+
def _try_stmt(self, node, ind):
|
|
152
|
+
"""C# try/catch syntax."""
|
|
153
|
+
self.lines.append(f"{ind}try {{")
|
|
154
|
+
self.indent_lvl += 1
|
|
155
|
+
for s in node.body:
|
|
156
|
+
self.stmt(s)
|
|
157
|
+
self.indent_lvl -= 1
|
|
158
|
+
if node.handlers:
|
|
159
|
+
self.lines.append(f"{ind}}} catch {{")
|
|
160
|
+
self.indent_lvl += 1
|
|
161
|
+
for s in node.handlers[0].body:
|
|
162
|
+
self.stmt(s)
|
|
163
|
+
self.indent_lvl -= 1
|
|
164
|
+
self.lines.append(f"{ind}}}")
|
|
165
|
+
else:
|
|
166
|
+
self.lines.append(f"{ind}}} catch {{")
|
|
167
|
+
self.lines.append(f"{ind}}}")
|
|
168
|
+
if node.finalbody:
|
|
169
|
+
self.lines.append(f"{ind}finally {{")
|
|
170
|
+
self.indent_lvl += 1
|
|
171
|
+
for s in node.finalbody:
|
|
172
|
+
self.stmt(s)
|
|
173
|
+
self.indent_lvl -= 1
|
|
174
|
+
self.lines.append(f"{ind}}}")
|
|
175
|
+
if isinstance(node, ast.Name) and node.id in self._KEYWORDS:
|
|
176
|
+
return "@" + node.id
|
|
177
|
+
return super().expr(node)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _entry_unit(units: list[FuncUnit], entry: str | None) -> FuncUnit:
|
|
181
|
+
if entry:
|
|
182
|
+
for u in units:
|
|
183
|
+
if u.name == entry:
|
|
184
|
+
return u
|
|
185
|
+
for u in units:
|
|
186
|
+
if u.name == "main":
|
|
187
|
+
return u
|
|
188
|
+
return units[0]
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _emit_structs_csharp(classes: list) -> str:
|
|
192
|
+
"""Emit C# struct definitions and constructors for ClassUnits."""
|
|
193
|
+
out: list[str] = []
|
|
194
|
+
for cls in classes:
|
|
195
|
+
fields_str = ""
|
|
196
|
+
for fname, ftype in cls.fields:
|
|
197
|
+
nt = SPEC.types.get(ftype, SPEC.types.get("float"))
|
|
198
|
+
if ftype == "list":
|
|
199
|
+
nt = "long[]"
|
|
200
|
+
fields_str += f" public {nt} {fname};\n"
|
|
201
|
+
out.append(f"struct {cls.name} {{\n{fields_str}}}\n")
|
|
202
|
+
if cls.constructor_params:
|
|
203
|
+
params_str = ", ".join(
|
|
204
|
+
f"{SPEC.types.get(ptype, 'double')} {pname}"
|
|
205
|
+
for pname, ptype in cls.constructor_params
|
|
206
|
+
)
|
|
207
|
+
init_lines = "\n".join(
|
|
208
|
+
f" {fname} = {fname}," for fname, _ in cls.constructor_params
|
|
209
|
+
)
|
|
210
|
+
out.append(f"static {cls.name} {cls.name}_new({params_str}) {{\n"
|
|
211
|
+
f" return new {cls.name} {{\n{init_lines}\n }};\n}}\n")
|
|
212
|
+
else:
|
|
213
|
+
out.append(f"static {cls.name} {cls.name}_new() {{\n"
|
|
214
|
+
f" return new {cls.name}();\n}}\n")
|
|
215
|
+
return "\n".join(out)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def emit_csharp(units: list[FuncUnit], entry: str | None,
|
|
219
|
+
library_mode: bool = False,
|
|
220
|
+
extern_fns: list[FuncUnit] | None = None,
|
|
221
|
+
classes: list | None = None,
|
|
222
|
+
constants: dict | None = None,
|
|
223
|
+
preamble: dict | None = None) -> tuple[str, dict[str, str]]:
|
|
224
|
+
"""Return (full_program_source, {func_name: emitted_source}).
|
|
225
|
+
|
|
226
|
+
In library_mode: emit [UnmanagedCallersOnly] exports for C ABI FFI.
|
|
227
|
+
extern_fns: functions from other backends that this C# code calls.
|
|
228
|
+
"""
|
|
229
|
+
emitter = CSharpEmitter(SPEC)
|
|
230
|
+
emitter.library_mode = library_mode
|
|
231
|
+
emitter.constants = constants or {}
|
|
232
|
+
emitted: dict[str, str] = {}
|
|
233
|
+
fns: list[str] = []
|
|
234
|
+
|
|
235
|
+
# register class names
|
|
236
|
+
if classes:
|
|
237
|
+
for cls in classes:
|
|
238
|
+
emitter.class_names.add(cls.name)
|
|
239
|
+
emitter.class_fields[cls.name] = cls.fields
|
|
240
|
+
emitter.class_bases[cls.name] = cls.bases
|
|
241
|
+
emitter.class_properties[cls.name] = cls.properties
|
|
242
|
+
emitter.class_static_methods[cls.name] = cls.static_methods
|
|
243
|
+
|
|
244
|
+
prelude = (
|
|
245
|
+
"using System;\n"
|
|
246
|
+
"using System.Collections.Generic;\n"
|
|
247
|
+
"using System.Linq;\n\n"
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
# emit DllImport declarations for cross-backend calls
|
|
251
|
+
if extern_fns:
|
|
252
|
+
decls = []
|
|
253
|
+
for u in extern_fns:
|
|
254
|
+
if not u.supported:
|
|
255
|
+
continue
|
|
256
|
+
params = ", ".join(
|
|
257
|
+
f"{'long[]' if ptype == 'list' else SPEC.types.get(ptype, 'double')} {pname}"
|
|
258
|
+
for pname, ptype in u.params)
|
|
259
|
+
ret = SPEC.types.get(u.ret_type, "void") if u.ret_type != "None" else "void"
|
|
260
|
+
decls.append(
|
|
261
|
+
f'[DllImport("ge_logic", CallingConvention = CallingConvention.Cdecl)]\n'
|
|
262
|
+
f"static extern {ret} {u.name}({params});"
|
|
263
|
+
)
|
|
264
|
+
prelude += "\n// cross-backend declarations (functions from other backends)\n" + "\n".join(decls) + "\n\n"
|
|
265
|
+
|
|
266
|
+
# emit struct definitions (inside the class Program block)
|
|
267
|
+
struct_code = ""
|
|
268
|
+
if classes:
|
|
269
|
+
struct_code = _emit_structs_csharp(classes)
|
|
270
|
+
if struct_code:
|
|
271
|
+
struct_code += "\n"
|
|
272
|
+
|
|
273
|
+
# build function return type lookup for type inference
|
|
274
|
+
for u in units:
|
|
275
|
+
if u.supported:
|
|
276
|
+
emitter.func_return_types[u.name] = u.ret_type
|
|
277
|
+
|
|
278
|
+
if library_mode:
|
|
279
|
+
for u in units:
|
|
280
|
+
if not u.supported:
|
|
281
|
+
continue
|
|
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
|
+
return prelude + "\n".join(fns) + "\n", emitted
|
|
292
|
+
|
|
293
|
+
entry_u = _entry_unit(units, entry)
|
|
294
|
+
for u in units:
|
|
295
|
+
if u.is_method and u.name.endswith(".__init__"):
|
|
296
|
+
continue
|
|
297
|
+
code = emitter.emit(u)
|
|
298
|
+
if emitter.unsupported_emissions:
|
|
299
|
+
u.supported = False
|
|
300
|
+
u.unsupported_reasons.extend(emitter.unsupported_emissions)
|
|
301
|
+
continue
|
|
302
|
+
emitted[u.name] = code
|
|
303
|
+
fns.append(code)
|
|
304
|
+
|
|
305
|
+
# C# requires all methods inside a class; wrap in a Program class
|
|
306
|
+
# add struct definitions at the beginning of the class block
|
|
307
|
+
if struct_code:
|
|
308
|
+
fns.insert(0, struct_code)
|
|
309
|
+
|
|
310
|
+
if entry_u.name != "main":
|
|
311
|
+
if entry_u.ret_type == "None":
|
|
312
|
+
wrapper = " static void Main() {\n " + entry_u.name + "();\n }\n"
|
|
313
|
+
else:
|
|
314
|
+
wrapper = " static int Main() {\n return (int)" + entry_u.name + "();\n }\n"
|
|
315
|
+
indented_fns = []
|
|
316
|
+
for fn in fns:
|
|
317
|
+
for line in fn.split("\n"):
|
|
318
|
+
indented_fns.append(" " + line)
|
|
319
|
+
indented_fns.append(wrapper.rstrip())
|
|
320
|
+
else:
|
|
321
|
+
# rename main to Main for C# entry point
|
|
322
|
+
# C# Main must return void or int (32-bit), not long
|
|
323
|
+
if entry_u.ret_type == "None":
|
|
324
|
+
fns[-1] = fns[-1].replace(f"static void {entry_u.name}(", "static void Main(")
|
|
325
|
+
else:
|
|
326
|
+
# Replace the return type and add a cast at the return
|
|
327
|
+
fns[-1] = fns[-1].replace(f"static long {entry_u.name}(", "static int Main(")
|
|
328
|
+
fns[-1] = fns[-1].replace(f"static int {entry_u.name}(", "static int Main(")
|
|
329
|
+
indented_fns = []
|
|
330
|
+
for fn in fns:
|
|
331
|
+
for line in fn.split("\n"):
|
|
332
|
+
indented_fns.append(" " + line)
|
|
333
|
+
|
|
334
|
+
program = prelude + "class Program {\n" + "\n".join(indented_fns) + "\n}\n"
|
|
335
|
+
# inject stdlib runtime (inside the Program class)
|
|
336
|
+
from ..stdlib import get_runtime
|
|
337
|
+
runtime = get_runtime("csharp")
|
|
338
|
+
if runtime:
|
|
339
|
+
# indent runtime code to be inside the class
|
|
340
|
+
indented_runtime = "\n".join(" " + line if line else line for line in runtime.splitlines())
|
|
341
|
+
program = prelude + "class Program {\n" + indented_runtime + "\n" + "\n".join(indented_fns) + "\n}\n"
|
|
342
|
+
return program, emitted
|