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,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