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,2359 @@
1
+ """Shared AST->code walker. Backends supply a `Spec` of format strings."""
2
+ from __future__ import annotations
3
+
4
+ import ast
5
+ from dataclasses import dataclass
6
+
7
+ from ..analyzer import FuncUnit
8
+
9
+
10
+ def _ann_base(node: ast.Subscript) -> str:
11
+ """Extract base type name from a subscript annotation like list[int]."""
12
+ if isinstance(node.value, ast.Name):
13
+ return node.value.id
14
+ return "list"
15
+
16
+
17
+ @dataclass
18
+ class Spec:
19
+ name: str
20
+ types: dict[str, str]
21
+ list_type: str # e.g. "Vec<{T}>" with {T} placeholder
22
+ list_param_type: str # borrowed form for params, e.g. "&[i64]"
23
+ list_elem_type: str # scalar int type used as list element, e.g. "i64"/"int64_t"
24
+ borrow_list_arg: bool # prefix `&` at call sites for list args (Rust)
25
+ range_call: str # "({lo}..{hi})" / range step variant handled separately
26
+ range_step_call: str
27
+ len_call: str # "{x}.len()" / "{x}.size()"
28
+ print_int: str
29
+ print_float: str
30
+ print_str: str
31
+ print_bool: str
32
+ print_generic: str # fallback
33
+ int_cast: str # "{x} as i64" / "static_cast<int64_t>({x})"
34
+ float_cast: str # "{x} as f64" / "static_cast<double>({x})"
35
+ float_div: str # how to render true division
36
+ floor_div: str
37
+ sum_call: str # "{it}.sum()" / accumulate
38
+ abs_int: str
39
+ abs_float: str
40
+ min_call: str
41
+ max_call: str
42
+ pow_call: str
43
+ append_call: str # "{x}.push({v})" / "{x}.push_back({v})"
44
+ index_call: str # "{x}[{i}]" (rust needs *no* change; cpp same)
45
+ comment: str # "//" or "//"
46
+ fn_template: str # full function template with {sig},{body}
47
+ main_template: str # program wrapper
48
+ var_decl_template: str = "{nt} {target} = {val};" # typed variable declaration
49
+ ffi_prefix: str = "" # decoration for C-ABI exports in library mode
50
+ indent: str = " "
51
+ # string operations
52
+ str_concat: str = "{l} + {r}" # string concatenation
53
+ str_len: str = "{x}.length()" # string length (C++ style)
54
+ str_index: str = "{x}[{i}]" # string char index
55
+ str_slice: str = "{x}.substr({start}, {len})" # string slice (start, len)
56
+ str_slice_start: str = "{x}.substr({start})" # string slice from start
57
+ str_slice_end: str = "{x}.substr(0, {end})" # string slice to end
58
+ # list concatenation (list + list -> new list)
59
+ list_concat: str = "{l} + {r}" # default; overridden per backend
60
+ # for-in list iteration
61
+ foreach_template: str = "for (auto {var} : {iter})" # C++ style
62
+ # try/except
63
+ try_template: str = "try {{\n{body}\n}} catch (...) {{\n{handler}\n}}"
64
+ # struct definition
65
+ struct_template: str = "struct {name} {{\n{fields}\n}};"
66
+ struct_field_template: str = " {type} {name};"
67
+ # whether if/while conditions need parentheses (C++/C#/Zig/Kotlin yes, Rust/Go no)
68
+ condition_parens: bool = True
69
+ struct_new_template: str = "{name} {name}_new({params}) {{\n{body}\n}}"
70
+ # dict support
71
+ dict_type: str = "std::map<std::string, {V}>"
72
+ dict_get: str = "{d}.at({k})"
73
+ dict_set: str = "{d}[{k}] = {v}"
74
+ dict_contains: str = "({d}.find({k}) != {d}.end())"
75
+ # tuple support
76
+ tuple_type: str = "std::tuple<{T}>"
77
+ tuple_get: str = "std::get<{i}>({t})"
78
+ # list length (separate from len_call which may be used for strings)
79
+ list_len: str = "{x}.size()"
80
+
81
+
82
+ class Emitter:
83
+ def __init__(self, spec: Spec):
84
+ self.spec = spec
85
+ self.lines: list[str] = []
86
+ self.indent_lvl = 0
87
+ # type environment for local vars (inferred from assignments)
88
+ self.var_types: dict[str, str] = {}
89
+ self.declared: set[str] = set() # vars already given a `let`/type decl
90
+ self.params: set[str] = set() # param names (already borrowed if list)
91
+ self.library_mode: bool = False # emit C-ABI exports instead of a main()
92
+ self.extern_names: set[str] = set() # extern "C" fns from other backends
93
+ self.force_extern_c: bool = False # force extern "C" on all functions (multi-backend)
94
+ self.export_names: set[str] = set() # Rust fns called from C++ — export them
95
+ self.class_names: set[str] = set() # known class names for constructor calls
96
+ self.class_fields: dict[str, list[tuple[str, str]]] = {} # class -> [(field, type)]
97
+ self.func_mutated_params: dict[str, set[int]] = {} # func name -> mutated param indices
98
+ self.class_bases: dict[str, list[str]] = {} # class -> [parent class names]
99
+ self.class_properties: dict[str, list[str]] = {} # class -> [property names]
100
+ self.class_static_methods: dict[str, list[str]] = {} # class -> [static method names]
101
+ self.current_class: str = "" # current class being emitted (for super())
102
+ # Production: track unsupported emissions to prevent silent failures
103
+ self.unsupported_emissions: list[str] = [] # list of unsupported features encountered
104
+ self.current_func: str = "" # current function being emitted
105
+ # function return type lookup: "func_name" -> "int" or "Class.method" -> "int"
106
+ self.func_return_types: dict[str, str] = {}
107
+ # container element types: param name -> element type (e.g. "str" for list[str])
108
+ self.param_elem_types: dict[str, str] = {}
109
+ # dict key/value types: param name -> (key_type, value_type)
110
+ # module-level constants: name -> value (for inlining into native code)
111
+ self.constants: dict[str, object] = {}
112
+ self.param_dict_types: dict[str, tuple[str, str]] = {}
113
+
114
+ def emit(self, unit: FuncUnit, sig_override: str | None = None,
115
+ body_suffix: str = "") -> str:
116
+ self.lines = []
117
+ self.indent_lvl = 1
118
+ self.var_types = {}
119
+ self.declared = set()
120
+ self.params = set()
121
+ self.mutated_params: set[str] = set() # params that are reassigned (need `mut` in Rust)
122
+ # track current class for super() calls
123
+ self.current_class = unit.class_name if unit.is_method else ""
124
+ # reset unsupported tracking for this function
125
+ self.unsupported_emissions = []
126
+ self.current_func = unit.name
127
+ # load container element types for this function's params
128
+ self.param_elem_types = getattr(unit, 'param_elem_types', {})
129
+ self.param_dict_types = getattr(unit, 'param_dict_types', {})
130
+ # check if this is a generator function (contains yield)
131
+ is_generator = self._is_generator(unit.body)
132
+ if is_generator:
133
+ # transform yield statements into list appends
134
+ unit = self._transform_generator(unit)
135
+ # seed params (already declared by the signature)
136
+ for pname, ptype in unit.params:
137
+ self.var_types[pname] = ptype
138
+ self.declared.add(pname)
139
+ self.params.add(pname)
140
+ # scan for parameter reassignments (Assign/AugAssign targeting a param)
141
+ for node in ast.walk(unit.body): # type: ignore[attr-defined]
142
+ if isinstance(node, ast.Assign):
143
+ for target in node.targets:
144
+ if isinstance(target, ast.Name) and target.id in self.params:
145
+ self.mutated_params.add(target.id)
146
+ elif isinstance(target, ast.Subscript) and isinstance(target.value, ast.Name) and target.value.id in self.params:
147
+ # dict/list element assignment mutates the container
148
+ self.mutated_params.add(target.value.id)
149
+ elif isinstance(node, ast.AugAssign):
150
+ if isinstance(node.target, ast.Name) and node.target.id in self.params:
151
+ self.mutated_params.add(node.target.id)
152
+ elif isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute):
153
+ # detect append() calls on list parameters
154
+ if node.func.attr == "append" and isinstance(node.func.value, ast.Name):
155
+ if node.func.value.id in self.params:
156
+ self.mutated_params.add(node.func.value.id)
157
+ # record which param indices are mutated (for call site borrow/deref)
158
+ mutated_indices = set()
159
+ for i, (pname, ptype) in enumerate(unit.params):
160
+ if pname in self.mutated_params:
161
+ mutated_indices.add(i)
162
+ if mutated_indices:
163
+ self.func_mutated_params[unit.name] = mutated_indices
164
+ for stmt in unit.body.body: # type: ignore[attr-defined]
165
+ self.stmt(stmt)
166
+ if body_suffix:
167
+ self.lines.append(self.spec.indent * self.indent_lvl + body_suffix)
168
+ body = "\n".join(self.lines)
169
+ sig = sig_override if sig_override else self.signature(unit)
170
+ return self.spec.fn_template.format(sig=sig, body=body, name=unit.name)
171
+
172
+ # ---- signatures ----
173
+ def py_to_native(self, t: str) -> str:
174
+ if t in self.spec.types:
175
+ return self.spec.types[t]
176
+ if t == "list":
177
+ return self.spec.list_type.format(T=self.spec.list_elem_type)
178
+ if t == "dict":
179
+ return self.spec.dict_type.format(V=self.spec.list_elem_type)
180
+ if t == "tuple":
181
+ return self.spec.tuple_type.format(T=self.spec.list_elem_type)
182
+ # class type — use the class name directly
183
+ if t in self.class_names:
184
+ return t
185
+ # unknown type — default to float
186
+ return self.spec.types["float"]
187
+
188
+ def _native_elem_type(self, py_type: str) -> str:
189
+ """Get the native element type for a Python type."""
190
+ return self.spec.types.get(py_type, self.spec.list_elem_type)
191
+
192
+ def param_native_type(self, t: str, pname: str = "") -> str:
193
+ """Native type for a function parameter (lists borrowed)."""
194
+ if t == "list":
195
+ if pname and pname in self.param_elem_types:
196
+ # heterogeneous list: use the element type from annotations
197
+ elem_t = self._native_elem_type(self.param_elem_types[pname])
198
+ if self.spec.name == "rust":
199
+ return f"&[{elem_t}]"
200
+ return self.spec.list_type.format(T=elem_t)
201
+ return self.spec.list_param_type
202
+ if t == "dict":
203
+ if pname and pname in self.param_dict_types:
204
+ key_t, val_t = self.param_dict_types[pname]
205
+ native_key = self._native_elem_type(key_t)
206
+ native_val = self._native_elem_type(val_t)
207
+ # Use backend-specific dict type with heterogeneous key/value types
208
+ if self.spec.name == "rust":
209
+ return f"&std::collections::HashMap<{native_key}, {native_val}>"
210
+ if self.spec.name == "cpp":
211
+ return f"const std::map<{native_key}, {native_val}>&"
212
+ if self.spec.name == "csharp":
213
+ return f"Dictionary<{native_key}, {native_val}>"
214
+ if self.spec.name == "go":
215
+ return f"map[{native_key}]{native_val}"
216
+ if self.spec.name == "kotlin":
217
+ return f"Map<{native_key}, {native_val}>"
218
+ if self.spec.name == "zig":
219
+ return f"*const std.AutoHashMap({native_key}, {native_val})"
220
+ return self.spec.dict_type.format(V=self.spec.list_elem_type)
221
+ if t == "tuple":
222
+ return self.spec.tuple_type.format(T=self.spec.list_elem_type)
223
+ # class type — pass by value (or const ref in C++)
224
+ if t in self.class_names:
225
+ if self.spec.name == "cpp":
226
+ return f"const {t}&"
227
+ return t
228
+ return self.py_to_native(t)
229
+
230
+ def signature(self, unit: FuncUnit) -> str:
231
+ # method names use underscore: ClassName.method -> ClassName_method
232
+ emit_name = unit.name.replace(".", "_")
233
+ params = []
234
+ for pname, ptype in unit.params:
235
+ nt = self.param_native_type(ptype, pname)
236
+ ident = self._ident(pname)
237
+ if self.spec.name == "rust":
238
+ # add `mut` for params that are reassigned in the body
239
+ if ptype == "list" and pname in self.mutated_params:
240
+ # mutated list params need &mut Vec<T> instead of &[T]
241
+ elem_t = self._native_elem_type(self.param_elem_types.get(pname, "int"))
242
+ nt = f"&mut {self.spec.list_type.format(T=elem_t)}"
243
+ params.append(f"{ident}: {nt}")
244
+ else:
245
+ mut_prefix = "mut " if pname in self.mutated_params else ""
246
+ params.append(f"{mut_prefix}{ident}: {nt}")
247
+ else:
248
+ params.append(f"{nt} {ident}")
249
+ params_s = ", ".join(params)
250
+ if unit.ret_type == "None":
251
+ base_sig = (f"fn {emit_name}({params_s})" if self.spec.name == "rust"
252
+ else f"void {emit_name}({params_s})")
253
+ else:
254
+ ret = self.py_to_native(unit.ret_type)
255
+ base_sig = (f"fn {emit_name}({params_s}) -> {ret}" if self.spec.name == "rust"
256
+ else f"{ret} {emit_name}({params_s})")
257
+ if self.library_mode and getattr(unit, "ffi_export", False) and self.spec.ffi_prefix:
258
+ return self.spec.ffi_prefix + base_sig
259
+ if self.force_extern_c and self.spec.ffi_prefix:
260
+ return self.spec.ffi_prefix + base_sig
261
+ # export Rust functions that are called from C++ (cross-backend)
262
+ if unit.name in self.export_names and self.spec.ffi_prefix:
263
+ return self.spec.ffi_prefix + base_sig
264
+ return base_sig
265
+
266
+ # ---- statements ----
267
+ def stmt(self, node: ast.AST) -> None:
268
+ ind = self.spec.indent * self.indent_lvl
269
+ if isinstance(node, ast.Return):
270
+ if node.value is None:
271
+ self.lines.append(f"{ind}return;")
272
+ else:
273
+ # in Rust, returning a mutated list param (&mut Vec) from a
274
+ # function that returns Vec requires cloning
275
+ if (self.spec.name == "rust" and isinstance(node.value, ast.Name)
276
+ and node.value.id in self.mutated_params
277
+ and self.var_types.get(node.value.id) == "list"):
278
+ self.lines.append(f"{ind}return (*{node.value.id}).clone();")
279
+ else:
280
+ self.lines.append(f"{ind}return {self.expr(node.value)};")
281
+ elif isinstance(node, ast.Assign):
282
+ target = node.targets[0]
283
+ val = self.expr(node.value)
284
+ if isinstance(target, ast.Tuple):
285
+ # tuple unpacking: a, b = expr
286
+ self._tuple_unpack(target, node.value, ind)
287
+ elif isinstance(target, ast.Name):
288
+ t = self.infer_type(node.value)
289
+ self.var_types[target.id] = t
290
+ nt = self.py_to_native(t)
291
+ if target.id in self.declared:
292
+ # in Rust, assigning to a mutated list param (&mut Vec)
293
+ # requires dereferencing: *graph = value
294
+ if (self.spec.name == "rust" and target.id in self.mutated_params
295
+ and self.var_types.get(target.id) == "list"):
296
+ self.lines.append(f"{ind}*{self._ident(target.id)} = {val};")
297
+ else:
298
+ self.lines.append(f"{ind}{self._ident(target.id)} = {val};")
299
+ else:
300
+ self.declared.add(target.id)
301
+ if self.spec.name == "rust":
302
+ self.lines.append(f"{ind}let mut {self._ident(target.id)}: {nt} = {val};")
303
+ else:
304
+ decl = self.spec.var_decl_template.format(
305
+ nt=nt, target=self._ident(target.id), val=val)
306
+ self.lines.append(f"{ind}{decl}")
307
+ elif isinstance(target, ast.Subscript):
308
+ target_type = self.infer_type(target.value)
309
+ if target_type == "dict":
310
+ key = self.expr(target.slice)
311
+ d = self.expr(target.value)
312
+ self.lines.append(f"{ind}{self.spec.dict_set.format(d=d, k=key, v=val)};")
313
+ else:
314
+ self.lines.append(f"{ind}{self.expr(target)} = {val};")
315
+ elif isinstance(target, ast.Attribute):
316
+ # self.field = value -> self.field = value (struct field assignment)
317
+ self.lines.append(f"{ind}{self.expr(target)} = {val};")
318
+ else:
319
+ self.lines.append(f"{ind}// unsupported assign target")
320
+ elif isinstance(node, ast.AugAssign):
321
+ target = self.expr(node.target)
322
+ op = self.binop_symbol(node.op)
323
+ self.lines.append(f"{ind}{target} {op}= {self.expr(node.value)};")
324
+ elif isinstance(node, ast.AnnAssign):
325
+ if node.value is None:
326
+ if isinstance(node.target, ast.Name):
327
+ t = self._ann_type(node.annotation)
328
+ self.var_types[node.target.id] = t
329
+ self.declared.add(node.target.id)
330
+ return
331
+ target = node.target.id if isinstance(node.target, ast.Name) else self.expr(node.target)
332
+ t = self._ann_type(node.annotation) if isinstance(node.target, ast.Name) else self.infer_type(node.value)
333
+ if isinstance(node.target, ast.Name):
334
+ self.var_types[target] = t
335
+ self.declared.add(target)
336
+ nt = self.py_to_native(t)
337
+ if self.spec.name == "rust":
338
+ self.lines.append(f"{ind}let mut {target}: {nt} = {self.expr(node.value)};")
339
+ else:
340
+ decl = self.spec.var_decl_template.format(
341
+ nt=nt, target=target, val=self.expr(node.value))
342
+ self.lines.append(f"{ind}{decl}")
343
+ elif isinstance(node, ast.If):
344
+ # Type narrowing: if test is isinstance(x, T), narrow x to T in the if branch
345
+ narrowed_var, narrowed_type = self._extract_isinstance_narrowing(node.test)
346
+ saved_type = None
347
+ if narrowed_var and narrowed_type:
348
+ saved_type = self.var_types.get(narrowed_var)
349
+ self.var_types[narrowed_var] = narrowed_type
350
+ self.lines.append(f"{ind}if {self._condition(node.test)} {{")
351
+ self.indent_lvl += 1
352
+ for s in node.body:
353
+ self.stmt(s)
354
+ self.indent_lvl -= 1
355
+ # Restore type after the if branch (else branch should not have narrowed type)
356
+ if narrowed_var and narrowed_type and saved_type is not None:
357
+ self.var_types[narrowed_var] = saved_type
358
+ elif narrowed_var and narrowed_type:
359
+ self.var_types.pop(narrowed_var, None)
360
+ if node.orelse:
361
+ # elif chains: ast nests elif as If inside orelse
362
+ if len(node.orelse) == 1 and isinstance(node.orelse[0], ast.If):
363
+ self.lines.append(f"{ind}}} else {self._elif(node.orelse[0])}")
364
+ else:
365
+ self.lines.append(f"{ind}}} else {{")
366
+ self.indent_lvl += 1
367
+ for s in node.orelse:
368
+ self.stmt(s)
369
+ self.indent_lvl -= 1
370
+ self.lines.append(f"{ind}}}")
371
+ else:
372
+ self.lines.append(f"{ind}}}")
373
+ elif isinstance(node, ast.For):
374
+ self.for_loop(node, ind)
375
+ elif isinstance(node, ast.While):
376
+ self.lines.append(f"{ind}while {self._condition(node.test)} {{")
377
+ self.indent_lvl += 1
378
+ for s in node.body:
379
+ self.stmt(s)
380
+ self.indent_lvl -= 1
381
+ self.lines.append(f"{ind}}}")
382
+ elif isinstance(node, ast.With):
383
+ self._with_stmt(node, ind)
384
+ elif isinstance(node, ast.Expr):
385
+ if isinstance(node.value, ast.Call):
386
+ self.lines.append(f"{ind}{self.expr(node.value)};")
387
+ # else: bare expression -> ignore (docstrings etc.)
388
+ elif isinstance(node, ast.Pass):
389
+ pass
390
+ elif isinstance(node, ast.Break):
391
+ self.lines.append(f"{ind}break;")
392
+ elif isinstance(node, ast.Continue):
393
+ self.lines.append(f"{ind}continue;")
394
+ elif isinstance(node, ast.Try):
395
+ self._try_stmt(node, ind)
396
+ elif isinstance(node, ast.Delete):
397
+ self._delete_stmt(node, ind)
398
+ elif isinstance(node, ast.Raise):
399
+ self._raise_stmt(node, ind)
400
+ elif isinstance(node, ast.Assert):
401
+ self._assert_stmt(node, ind)
402
+ elif isinstance(node, ast.Match):
403
+ self._match_stmt(node, ind)
404
+ elif isinstance(node, ast.ClassDef):
405
+ # struct/class definitions are handled at program level, not in function bodies
406
+ pass
407
+ else:
408
+ self._mark_unsupported(f"statement {type(node).__name__}")
409
+ self.lines.append(f"{ind}// unsupported stmt: {type(node).__name__}")
410
+
411
+ def _delete_stmt(self, node: ast.Delete, ind: str) -> None:
412
+ """Emit del statement — for list/dict element removal."""
413
+ for target in node.targets:
414
+ if isinstance(target, ast.Subscript):
415
+ base = self.expr(target.value)
416
+ idx = self.expr(target.slice)
417
+ if self.spec.name == "rust":
418
+ self.lines.append(f"{ind}{base}.remove({idx} as usize);")
419
+ elif self.spec.name == "cpp":
420
+ self.lines.append(f"{ind}{base}.erase({base}.begin() + {idx});")
421
+ elif self.spec.name == "csharp":
422
+ self.lines.append(f"{ind}{base}.RemoveAt((int)({idx}));")
423
+ elif self.spec.name == "go":
424
+ self.lines.append(f"{ind}{base} = append({base}[:{idx}], {base}[{idx}+1:]...)")
425
+ elif self.spec.name == "kotlin":
426
+ self.lines.append(f"{ind}{base}.removeAt({idx}.toInt())")
427
+ else:
428
+ self.lines.append(f"{ind}// del {base}[{idx}]")
429
+ elif isinstance(target, ast.Name):
430
+ # del var — just mark as removed (in native, can't truly delete)
431
+ self.lines.append(f"{ind}// del {target.id}")
432
+ elif isinstance(target, ast.Attribute):
433
+ base = self.expr(target.value)
434
+ self.lines.append(f"{ind}// del {base}.{target.attr}")
435
+
436
+ def _raise_stmt(self, node: ast.Raise, ind: str) -> None:
437
+ """Emit raise statement as a panic/throw."""
438
+ exc_name = ""
439
+ if node.exc:
440
+ if isinstance(node.exc, ast.Name):
441
+ exc_name = node.exc.id
442
+ elif isinstance(node.exc, ast.Call):
443
+ exc_name = getattr(node.exc.func, "id", "Exception")
444
+ else:
445
+ exc_name = "Exception"
446
+ msg = '"GE runtime error"'
447
+ if node.exc and isinstance(node.exc, ast.Call) and node.exc.args:
448
+ msg = self.expr(node.exc.args[0])
449
+ if self.spec.name == "rust":
450
+ self.lines.append(f'{ind}panic!("{exc_name}: {{}}", {msg});')
451
+ elif self.spec.name == "cpp":
452
+ self.lines.append(f'{ind}throw std::runtime_error("{exc_name}");')
453
+ elif self.spec.name == "csharp":
454
+ self.lines.append(f'{ind}throw new System.Exception("{exc_name}");')
455
+ elif self.spec.name == "go":
456
+ self.lines.append(f'{ind}panic("{exc_name}")')
457
+ elif self.spec.name == "kotlin":
458
+ self.lines.append(f'{ind}throw RuntimeException("{exc_name}")')
459
+ elif self.spec.name == "zig":
460
+ self.lines.append(f'{ind}std.debug.panic("{exc_name}", .{{}});')
461
+ else:
462
+ self.lines.append(f'{ind}// raise {exc_name}')
463
+
464
+ def _assert_stmt(self, node: ast.Assert, ind: str) -> None:
465
+ """Emit assert statement."""
466
+ cond = self._condition(node.test)
467
+ msg = ""
468
+ if node.msg:
469
+ msg = self.expr(node.msg)
470
+ if self.spec.name == "rust":
471
+ if msg:
472
+ self.lines.append(f'{ind}assert!({cond}, "{msg}");')
473
+ else:
474
+ self.lines.append(f"{ind}assert!({cond});")
475
+ elif self.spec.name == "cpp":
476
+ self.lines.append(f"{ind}assert({cond});")
477
+ elif self.spec.name == "csharp":
478
+ self.lines.append(f"{ind}System.Diagnostics.Debug.Assert({cond});")
479
+ elif self.spec.name == "go":
480
+ self.lines.append(f"{ind}if !({cond}) {{ panic(\"assertion failed\") }}")
481
+ elif self.spec.name == "kotlin":
482
+ self.lines.append(f"{ind}assert({cond})")
483
+ elif self.spec.name == "zig":
484
+ self.lines.append(f"{ind}std.debug.assert({cond});")
485
+ else:
486
+ self.lines.append(f"{ind}// assert {cond}")
487
+
488
+ def _match_stmt(self, node: ast.Match, ind: str) -> None:
489
+ """Lower Python 3.10+ match statement to if-else chains."""
490
+ subject = self.expr(node.subject)
491
+ # Use a temp variable for the subject
492
+ subj_var = "__match_subj"
493
+ if self.spec.name == "rust":
494
+ self.lines.append(f"{ind}let {subj_var} = {subject};")
495
+ elif self.spec.name in ("cpp", "csharp", "kotlin"):
496
+ self.lines.append(f"{ind}auto {subj_var} = {subject};")
497
+ elif self.spec.name == "go":
498
+ self.lines.append(f"{ind}{subj_var} := {subject}")
499
+ elif self.spec.name == "zig":
500
+ self.lines.append(f"{ind}const {subj_var} = {subject};")
501
+ else:
502
+ self.lines.append(f"{ind}var {subj_var} = {subject};")
503
+ first = True
504
+ for case in node.cases:
505
+ pattern = case.pattern
506
+ guard = case.guard
507
+ body = case.body
508
+ # Build condition from pattern
509
+ cond = self._match_pattern(pattern, subj_var)
510
+ if guard:
511
+ cond = f"({cond} && {self._condition(guard)})"
512
+ if first:
513
+ self.lines.append(f"{ind}if {cond} {{")
514
+ first = False
515
+ else:
516
+ self.lines.append(f"{ind}}} else if {cond} {{")
517
+ self.indent_lvl += 1
518
+ for s in body:
519
+ self.stmt(s)
520
+ self.indent_lvl -= 1
521
+ # wildcard case (case _:) becomes else
522
+ has_wildcard = any(
523
+ isinstance(c.pattern, ast.MatchAs) and c.pattern.pattern is None
524
+ for c in node.cases
525
+ )
526
+ if has_wildcard:
527
+ self.lines.append(f"{ind}}} else {{")
528
+ self.indent_lvl += 1
529
+ for case in node.cases:
530
+ if isinstance(case.pattern, ast.MatchAs) and case.pattern.pattern is None:
531
+ for s in case.body:
532
+ self.stmt(s)
533
+ break
534
+ self.indent_lvl -= 1
535
+ self.lines.append(f"{ind}}}")
536
+
537
+ def _match_pattern(self, pattern: ast.AST, subj: str) -> str:
538
+ """Convert a match pattern to a boolean condition."""
539
+ if isinstance(pattern, ast.MatchValue):
540
+ return f"({subj} == {self.expr(pattern.value)})"
541
+ if isinstance(pattern, ast.MatchAs):
542
+ if pattern.pattern is None:
543
+ return "true" # wildcard
544
+ return self._match_pattern(pattern.pattern, subj)
545
+ if isinstance(pattern, ast.MatchOr):
546
+ parts = [self._match_pattern(p, subj) for p in pattern.patterns]
547
+ return "(" + " || ".join(parts) + ")"
548
+ if isinstance(pattern, ast.MatchSequence):
549
+ # match [a, b]: check length and bind elements
550
+ return f"true" # simplified
551
+ if isinstance(pattern, ast.MatchClass):
552
+ cls = getattr(pattern.cls, "id", "Unknown")
553
+ return f"/*isinstance({subj}, {cls})*/"
554
+ return "true"
555
+
556
+ def _try_stmt(self, node: ast.Try, ind: str) -> None:
557
+ """Emit try/except/finally with backend-appropriate semantics.
558
+
559
+ Based on research:
560
+ - C++/C#/Kotlin: use try/catch/finally (native exception support)
561
+ - Rust: use std::panic::catch_unwind with Result type
562
+ - Go/Zig: mark as unsupported (no exception mechanism)
563
+ """
564
+ if self.spec.name == "rust":
565
+ self._try_stmt_rust(node, ind)
566
+ elif self.spec.name in ("cpp", "csharp", "kotlin"):
567
+ self._try_stmt_native(node, ind)
568
+ else:
569
+ # Go and Zig don't have exceptions — mark as unsupported
570
+ self._mark_unsupported("try/except (no exceptions in target language)")
571
+ self.lines.append(f"{ind}// unsupported: try/except not available in {self.spec.name}")
572
+
573
+ def _try_stmt_native(self, node: ast.Try, ind: str) -> None:
574
+ """Emit try/catch/finally for C++/C#/Kotlin (native exception support)."""
575
+ self.lines.append(f"{ind}try {{")
576
+ self.indent_lvl += 1
577
+ for s in node.body:
578
+ self.stmt(s)
579
+ self.indent_lvl -= 1
580
+ if node.handlers:
581
+ # C++/C# use catch, Kotlin uses catch
582
+ catch_kw = "catch" if self.spec.name != "kotlin" else "catch"
583
+ for handler in node.handlers:
584
+ exc_name = ""
585
+ if handler.type and isinstance(handler.type, ast.Name):
586
+ exc_name = handler.type.id
587
+ if self.spec.name == "kotlin":
588
+ self.lines.append(f"{ind}}} {catch_kw} (e: Exception) {{")
589
+ elif self.spec.name == "csharp":
590
+ self.lines.append(f"{ind}}} {catch_kw} (Exception e) {{")
591
+ else:
592
+ self.lines.append(f"{ind}}} {catch_kw} (...) {{")
593
+ self.indent_lvl += 1
594
+ if handler.name:
595
+ # bind the exception variable
596
+ if self.spec.name == "kotlin":
597
+ self.lines.append(f"{ind}val {handler.name} = e")
598
+ elif self.spec.name == "csharp":
599
+ self.lines.append(f"{ind}var {handler.name} = e")
600
+ for s in handler.body:
601
+ self.stmt(s)
602
+ self.indent_lvl -= 1
603
+ else:
604
+ self.lines.append(f"{ind}}} catch (...) {{")
605
+ self.indent_lvl += 1
606
+ self.lines.append(f"{ind}// catch-all")
607
+ self.indent_lvl -= 1
608
+ self.lines.append(f"{ind}}}")
609
+ # finally block
610
+ if node.finalbody:
611
+ if self.spec.name == "kotlin":
612
+ self.lines.append(f"{ind}finally {{")
613
+ else:
614
+ self.lines.append(f"{ind}// finally:")
615
+ self.indent_lvl += 1
616
+ for s in node.finalbody:
617
+ self.stmt(s)
618
+ self.indent_lvl -= 1
619
+ if self.spec.name == "kotlin":
620
+ self.lines.append(f"{ind}}}")
621
+
622
+ def _try_stmt_rust(self, node: ast.Try, ind: str) -> None:
623
+ """Emit try/except for Rust using std::panic::catch_unwind.
624
+
625
+ Based on py2many and Rust best practices:
626
+ - try body wrapped in catch_unwind closure
627
+ - except handlers check the Result
628
+ - finally runs after the match
629
+ """
630
+ self.lines.append(f"{ind}let __result = std::panic::catch_unwind(|| {{")
631
+ self.indent_lvl += 1
632
+ for s in node.body:
633
+ self.stmt(s)
634
+ self.indent_lvl -= 1
635
+ self.lines.append(f"{ind}}});")
636
+ if node.handlers:
637
+ self.lines.append(f"{ind}if let Err(__e) = __result {{")
638
+ self.indent_lvl += 1
639
+ for s in node.handlers[0].body:
640
+ self.stmt(s)
641
+ self.indent_lvl -= 1
642
+ self.lines.append(f"{ind}}}")
643
+ if node.finalbody:
644
+ for s in node.finalbody:
645
+ self.stmt(s)
646
+
647
+ def _extract_isinstance_narrowing(self, test: ast.AST) -> tuple[str, str]:
648
+ """Extract type narrowing from isinstance checks.
649
+
650
+ Based on mypy's type narrowing: isinstance(x, int) in an if branch
651
+ narrows x to int within that branch.
652
+
653
+ Returns (var_name, narrowed_type) or ("", "") if no narrowing applies.
654
+ """
655
+ # isinstance(x, T) -> narrow x to T
656
+ if (isinstance(test, ast.Call) and
657
+ isinstance(test.func, ast.Name) and
658
+ test.func.id == "isinstance" and
659
+ len(test.args) == 2 and
660
+ isinstance(test.args[0], ast.Name) and
661
+ isinstance(test.args[1], ast.Name)):
662
+ var_name = test.args[0].id
663
+ type_name = test.args[1].id
664
+ if type_name in ("int", "float", "bool", "str", "list", "dict", "tuple", "set"):
665
+ return (var_name, type_name)
666
+ if type_name in self.class_names:
667
+ return (var_name, type_name)
668
+ # x is not None -> narrow x to its non-optional form
669
+ if (isinstance(test, ast.Compare) and
670
+ len(test.ops) == 1 and
671
+ isinstance(test.ops[0], ast.IsNot) and
672
+ isinstance(test.left, ast.Name) and
673
+ len(test.comparators) == 1 and
674
+ isinstance(test.comparators[0], ast.Constant) and
675
+ test.comparators[0].value is None):
676
+ var_name = test.left.id
677
+ current_type = self.var_types.get(var_name, "")
678
+ # If current type is Optional-like, narrow to the base type
679
+ if current_type.startswith("Optional["):
680
+ return (var_name, current_type[9:-1])
681
+ return ("", "")
682
+ return ("", "")
683
+
684
+ def _tuple_unpack(self, target: ast.Tuple, value: ast.AST, ind: str) -> None:
685
+ """Handle tuple unpacking: a, b = expr"""
686
+ n = len(target.elts)
687
+ if isinstance(value, ast.Tuple) and len(value.elts) == n:
688
+ # a, b = 1, 2 — direct assignment
689
+ for t, v in zip(target.elts, value.elts):
690
+ v_str = self.expr(v)
691
+ t_type = self.infer_type(v)
692
+ self.var_types[t.id] = t_type
693
+ nt = self.py_to_native(t_type)
694
+ if t.id in self.declared:
695
+ self.lines.append(f"{ind}{t.id} = {v_str};")
696
+ else:
697
+ self.declared.add(t.id)
698
+ if self.spec.name == "rust":
699
+ self.lines.append(f"{ind}let mut {t.id}: {nt} = {v_str};")
700
+ else:
701
+ decl = self.spec.var_decl_template.format(nt=nt, target=t.id, val=v_str)
702
+ self.lines.append(f"{ind}{decl}")
703
+ else:
704
+ # a, b = func() — use tuple element access
705
+ val_str = self.expr(value)
706
+ # declare a temp tuple variable
707
+ tmp = "__tup"
708
+ if self.spec.name == "rust":
709
+ self.lines.append(f"{ind}let {tmp} = {val_str};")
710
+ elif self.spec.name in ("cpp", "csharp", "kotlin"):
711
+ self.lines.append(f"{ind}auto {tmp} = {val_str};")
712
+ elif self.spec.name == "go":
713
+ self.lines.append(f"{ind}{tmp} := {val_str}")
714
+ elif self.spec.name == "zig":
715
+ self.lines.append(f"{ind}const {tmp} = {val_str};")
716
+ else:
717
+ self.lines.append(f"{ind}var {tmp} = {val_str};")
718
+ for i, t in enumerate(target.elts):
719
+ if self.spec.name == "rust":
720
+ access = f"{tmp}.{i}"
721
+ elif self.spec.name == "cpp":
722
+ access = f"std::get<{i}>({tmp})"
723
+ elif self.spec.name == "csharp":
724
+ access = f"{tmp}.Item{i+1}"
725
+ elif self.spec.name == "go":
726
+ field = chr(97 + i)
727
+ access = f"{tmp}.{field}"
728
+ elif self.spec.name == "kotlin":
729
+ field = "first" if i == 0 else "second" if i == 1 else f"component{i+1}"
730
+ access = f"{tmp}.{field}"
731
+ else:
732
+ access = f"{tmp}[{i}]"
733
+ self.var_types[t.id] = "int"
734
+ self.declared.add(t.id)
735
+ nt = self.py_to_native("int")
736
+ if self.spec.name == "rust":
737
+ self.lines.append(f"{ind}let mut {t.id}: {nt} = {access};")
738
+ else:
739
+ decl = self.spec.var_decl_template.format(nt=nt, target=t.id, val=access)
740
+ self.lines.append(f"{ind}{decl}")
741
+
742
+ def _find_parent_class(self) -> str:
743
+ """Find the first parent class of the current class for super() calls."""
744
+ bases = self.class_bases.get(self.current_class, [])
745
+ return bases[0] if bases else ""
746
+
747
+ def _mark_unsupported(self, feature: str) -> str:
748
+ """Track an unsupported feature emission. Returns a comment string.
749
+
750
+ Production: instead of silently emitting wrong code, we track every
751
+ unsupported feature so the pipeline can mark the function as
752
+ unsupported and fall back to CPython.
753
+ """
754
+ msg = f"{self.current_func}: {feature}"
755
+ if msg not in self.unsupported_emissions:
756
+ self.unsupported_emissions.append(msg)
757
+ return f"/*unsupported: {feature}*/"
758
+
759
+ def _ident(self, name: str) -> str:
760
+ """Escape a local/parameter name that collides with a target keyword.
761
+
762
+ Function names are handled earlier, in the shared IR, because they can
763
+ cross the C ABI; locals never do, so each backend uses its own idiom
764
+ (`@base` in C#, `r#match` in Rust, `base_` in C++).
765
+ """
766
+ from ..idents import escape_local
767
+ return escape_local(name, self.spec.name)
768
+
769
+ def _condition(self, node: ast.AST) -> str:
770
+ """Convert a Python truthy condition to a native boolean expression.
771
+
772
+ Python allows 'if flag:' where flag is an int, but most compiled
773
+ languages require an explicit comparison. This converts bare int
774
+ expressions to 'expr != 0'.
775
+ """
776
+ # Already a comparison or bool op — leave as-is
777
+ if isinstance(node, (ast.Compare, ast.BoolOp)):
778
+ cond = self.expr(node)
779
+ # Unary not — leave as-is
780
+ elif isinstance(node, ast.UnaryOp) and isinstance(node.op, ast.Not):
781
+ cond = self.expr(node)
782
+ else:
783
+ # Bare expression — check if it's an int
784
+ t = self.infer_type(node)
785
+ if t == "int":
786
+ zero = self._zero_literal()
787
+ cond = f"({self.expr(node)} != {zero})"
788
+ elif t == "bool":
789
+ cond = self.expr(node)
790
+ else:
791
+ # Default: wrap in comparison for safety
792
+ zero = self._zero_literal()
793
+ cond = f"({self.expr(node)} != {zero})"
794
+ # C-like backends (C++/C#/Zig/Kotlin) need parentheses around the condition
795
+ if getattr(self.spec, "condition_parens", True):
796
+ return f"({cond})"
797
+ return cond
798
+
799
+ def _zero_literal(self) -> str:
800
+ """Return the zero literal for this backend's int type."""
801
+ if self.spec.name == "kotlin":
802
+ return "0L"
803
+ return "0"
804
+
805
+ def _elif(self, node: ast.If) -> str:
806
+ head = f"if {self._condition(node.test)} {{"
807
+ save = self.lines
808
+ self.lines = []
809
+ self.indent_lvl += 1
810
+ for s in node.body:
811
+ self.stmt(s)
812
+ self.indent_lvl -= 1
813
+ body = "\n".join(self.lines)
814
+ self.lines = save
815
+ tail = ""
816
+ if node.orelse:
817
+ if len(node.orelse) == 1 and isinstance(node.orelse[0], ast.If):
818
+ tail = " } else " + self._elif(node.orelse[0])
819
+ else:
820
+ save2 = self.lines
821
+ self.lines = []
822
+ self.indent_lvl += 1
823
+ for s in node.orelse:
824
+ self.stmt(s)
825
+ self.indent_lvl -= 1
826
+ tail = " } else {\n" + "\n".join(self.lines) + "\n" + self.spec.indent * self.indent_lvl + "}"
827
+ self.lines = save2
828
+ else:
829
+ tail = "\n" + self.spec.indent * self.indent_lvl + "}"
830
+ return head + "\n" + body + tail
831
+
832
+ def for_loop(self, node: ast.For, ind: str) -> None:
833
+ var = node.target.id if isinstance(node.target, ast.Name) else "_"
834
+ is_tuple_target = isinstance(node.target, ast.Tuple)
835
+ it = node.iter
836
+ if isinstance(it, ast.Call) and getattr(it.func, "id", None) == "range":
837
+ args = it.args
838
+ if len(args) == 1:
839
+ hi = self.expr(args[0])
840
+ head = self.spec.range_call.format(lo="0", hi=hi, step="1")
841
+ elif len(args) == 2:
842
+ lo = self.expr(args[0])
843
+ hi = self.expr(args[1])
844
+ head = self.spec.range_call.format(lo=lo, hi=hi, step="1")
845
+ else:
846
+ lo = self.expr(args[0])
847
+ hi = self.expr(args[1])
848
+ step = self.expr(args[2])
849
+ head = self.spec.range_step_call.format(lo=lo, hi=hi, step=step)
850
+ self.var_types[var] = "int"
851
+ self.lines.append(f"{ind}for {var} in {head} {{")
852
+ elif isinstance(it, ast.Call) and getattr(it.func, "id", None) == "enumerate" and is_tuple_target:
853
+ # for i, x in enumerate(items):
854
+ self._for_enumerate(node, ind)
855
+ return
856
+ elif isinstance(it, ast.Call) and getattr(it.func, "id", None) == "zip" and is_tuple_target:
857
+ # for a, b in zip(x, y):
858
+ self._for_zip(node, ind)
859
+ return
860
+ else:
861
+ # iterate a container (list)
862
+ iter_s = self.expr(it)
863
+ elem_type = self._iter_elem_type(it)
864
+ self.var_types[var] = elem_type
865
+ head = self.spec.foreach_template.format(var=var, iter=iter_s,
866
+ etype=self.spec.list_elem_type)
867
+ self.lines.append(f"{ind}{head} {{")
868
+ self.indent_lvl += 1
869
+ for s in node.body:
870
+ self.stmt(s)
871
+ self.indent_lvl -= 1
872
+ self.lines.append(f"{ind}}}")
873
+
874
+ def _for_enumerate(self, node: ast.For, ind: str) -> None:
875
+ """Handle: for i, x in enumerate(items):"""
876
+ target = node.target # ast.Tuple
877
+ idx_var = target.elts[0].id
878
+ val_var = target.elts[1].id
879
+ it = self.expr(node.iter.args[0])
880
+ self.var_types[idx_var] = "int"
881
+ self.var_types[val_var] = "int"
882
+ if self.spec.name == "rust":
883
+ self.lines.append(f"{ind}for ({idx_var}, {val_var}) in {it}.iter().enumerate() {{")
884
+ elif self.spec.name == "cpp":
885
+ self.lines.append(f"{ind}for (int64_t {idx_var} = 0; auto& {val_var} : {it}) {{")
886
+ elif self.spec.name == "csharp":
887
+ self.lines.append(f"{ind}foreach (var __pair in {it}.Select((x, i) => (i, x))) {{")
888
+ self.lines.append(f"{ind} var {idx_var} = __pair.Item1; var {val_var} = __pair.Item2;")
889
+ elif self.spec.name == "go":
890
+ self.lines.append(f"{ind}for {idx_var}, {val_var} := range {it} {{")
891
+ elif self.spec.name == "kotlin":
892
+ self.lines.append(f"{ind}for ((__i, {val_var}) in {it}.withIndex()) {{")
893
+ self.lines.append(f"{ind} var {idx_var} = __i.toLong()")
894
+ elif self.spec.name == "zig":
895
+ self.lines.append(f"{ind}for ({it}, 0..) |{val_var}, {idx_var}| {{")
896
+ else:
897
+ self.lines.append(f"{ind}// unsupported enumerate for-loop")
898
+ self.indent_lvl += 1
899
+ for s in node.body:
900
+ self.stmt(s)
901
+ self.indent_lvl -= 1
902
+ self.lines.append(f"{ind}}}")
903
+
904
+ def _for_zip(self, node: ast.For, ind: str) -> None:
905
+ """Handle: for a, b in zip(x, y):"""
906
+ target = node.target # ast.Tuple
907
+ var_a = target.elts[0].id
908
+ var_b = target.elts[1].id
909
+ it_a = self.expr(node.iter.args[0])
910
+ it_b = self.expr(node.iter.args[1])
911
+ self.var_types[var_a] = "int"
912
+ self.var_types[var_b] = "int"
913
+ if self.spec.name == "rust":
914
+ self.lines.append(f"{ind}for ({var_a}, {var_b}) in {it_a}.iter().zip({it_b}.iter()) {{")
915
+ elif self.spec.name == "cpp":
916
+ self.lines.append(f"{ind}for (int64_t __i = 0; __i < (int64_t)std::min({it_a}.size(), {it_b}.size()); __i++) {{")
917
+ self.lines.append(f"{ind} int64_t {var_a} = {it_a}[__i]; int64_t {var_b} = {it_b}[__i];")
918
+ elif self.spec.name == "csharp":
919
+ self.lines.append(f"{ind}foreach (var __pair in {it_a}.Zip({it_b}, (a, b) => (a, b))) {{")
920
+ self.lines.append(f"{ind} var {var_a} = __pair.Item1; var {var_b} = __pair.Item2;")
921
+ elif self.spec.name == "go":
922
+ self.lines.append(f"{ind}for __i := int64(0); __i < int64(len({it_a})) && __i < int64(len({it_b})); __i++ {{")
923
+ self.lines.append(f"{ind} {var_a} := {it_a}[__i]; {var_b} := {it_b}[__i]")
924
+ elif self.spec.name == "kotlin":
925
+ self.lines.append(f"{ind}for (__pair in {it_a}.zip({it_b})) {{")
926
+ self.lines.append(f"{ind} var {var_a} = __pair.first; var {var_b} = __pair.second")
927
+ elif self.spec.name == "zig":
928
+ self.lines.append(f"{ind}var __i: usize = 0;")
929
+ self.lines.append(f"{ind}while (__i < {it_a}.len and __i < {it_b}.len) : (__i += 1) {{")
930
+ self.lines.append(f"{ind} const {var_a} = {it_a}[__i]; const {var_b} = {it_b}[__i];")
931
+ else:
932
+ self.lines.append(f"{ind}// unsupported zip for-loop")
933
+ self.indent_lvl += 1
934
+ for s in node.body:
935
+ self.stmt(s)
936
+ self.indent_lvl -= 1
937
+ self.lines.append(f"{ind}}}")
938
+
939
+ def _iter_elem_type(self, it: ast.AST) -> str:
940
+ """Infer element type of an iterable."""
941
+ t = self.infer_type(it)
942
+ if t == "list":
943
+ return "int"
944
+ if t == "str":
945
+ return "str"
946
+ return "int"
947
+
948
+ # ---- expressions ----
949
+ def expr(self, node: ast.AST) -> str:
950
+ if isinstance(node, ast.Constant):
951
+ v = node.value
952
+ if isinstance(v, bool):
953
+ return "true" if v else "false"
954
+ if isinstance(v, int):
955
+ return str(v)
956
+ if isinstance(v, float):
957
+ return repr(v)
958
+ if isinstance(v, str):
959
+ if self.spec.name == "rust":
960
+ return f'String::from("{v.replace(chr(34), chr(92)+chr(34))}")'
961
+ return '"' + v.replace('"', '\\"') + '"'
962
+ if v is None:
963
+ return "()" if self.spec.name == "rust" else "nullptr"
964
+ if isinstance(node, ast.JoinedStr):
965
+ return self._fstring(node)
966
+ if isinstance(node, ast.Lambda):
967
+ return self._lambda(node)
968
+ if isinstance(node, ast.Name):
969
+ # rename self -> _self (self is reserved in Rust)
970
+ if node.id == "self":
971
+ return "_self"
972
+ # inline module-level constants
973
+ if node.id in self.constants:
974
+ val = self.constants[node.id]
975
+ if isinstance(val, bool):
976
+ return "true" if val else "false"
977
+ if isinstance(val, int):
978
+ return str(val)
979
+ if isinstance(val, float):
980
+ return str(val)
981
+ if isinstance(val, str):
982
+ if self.spec.name == "rust":
983
+ return f'String::from("{val}")'
984
+ return f'"{val}"'
985
+ return self._ident(node.id)
986
+ if isinstance(node, ast.BinOp):
987
+ left = self.expr(node.left)
988
+ right = self.expr(node.right)
989
+ if isinstance(node.op, ast.Div):
990
+ return self.spec.float_div.format(l=left, r=right)
991
+ if isinstance(node.op, ast.FloorDiv):
992
+ return self.spec.floor_div.format(l=left, r=right)
993
+ if isinstance(node.op, ast.Pow):
994
+ return self.spec.pow_call.format(l=left, r=right)
995
+ if isinstance(node.op, ast.Add):
996
+ lt = self.infer_type(node.left)
997
+ rt = self.infer_type(node.right)
998
+ if lt == "str" or rt == "str":
999
+ return self.spec.str_concat.format(l=left, r=right)
1000
+ if lt == "list" or rt == "list":
1001
+ return self.spec.list_concat.format(l=left, r=right)
1002
+ return f"({left} {self.binop_symbol(node.op)} {right})"
1003
+ if isinstance(node, ast.UnaryOp):
1004
+ operand = self.expr(node.operand)
1005
+ if isinstance(node.op, ast.USub):
1006
+ return f"(-{operand})"
1007
+ if isinstance(node.op, ast.UAdd):
1008
+ return f"(+{operand})"
1009
+ if isinstance(node.op, ast.Not):
1010
+ return f"(!{operand})"
1011
+ if isinstance(node, ast.BoolOp):
1012
+ op = "&&" if isinstance(node.op, ast.And) else "||"
1013
+ parts = [self.expr(v) for v in node.values]
1014
+ return "(" + f" {op} ".join(parts) + ")"
1015
+ if isinstance(node, ast.Compare):
1016
+ left = self.expr(node.left)
1017
+ left_type = self.infer_type(node.left)
1018
+ parts = []
1019
+ cur = left
1020
+ for op, comp in zip(node.ops, node.comparators):
1021
+ if isinstance(op, (ast.In, ast.NotIn)):
1022
+ right = self.expr(comp)
1023
+ right_type = self.infer_type(comp)
1024
+ if right_type == "dict":
1025
+ contains = self.spec.dict_contains.format(d=right, k=cur)
1026
+ if isinstance(op, ast.NotIn):
1027
+ parts.append(f"(!{contains})")
1028
+ else:
1029
+ parts.append(contains)
1030
+ elif right_type == "list":
1031
+ # list membership: use contains/find
1032
+ if self.spec.name == "rust":
1033
+ contains = f"{right}.iter().any(|&x| x == {cur})"
1034
+ elif self.spec.name == "cpp":
1035
+ contains = f"(std::find({right}.begin(), {right}.end(), {cur}) != {right}.end())"
1036
+ elif self.spec.name == "csharp":
1037
+ contains = f"{right}.Contains({cur})"
1038
+ elif self.spec.name == "zig":
1039
+ contains = f"contains(i64, {right}, {cur})"
1040
+ elif self.spec.name == "go":
1041
+ contains = f"contains({right}, {cur})"
1042
+ elif self.spec.name == "kotlin":
1043
+ contains = f"{right}.contains({cur})"
1044
+ else:
1045
+ contains = f"/*contains*/"
1046
+ if isinstance(op, ast.NotIn):
1047
+ parts.append(f"(!{contains})")
1048
+ else:
1049
+ parts.append(contains)
1050
+ else:
1051
+ # string contains
1052
+ if self.spec.name == "rust":
1053
+ contains = f"{right}.contains({cur})"
1054
+ elif self.spec.name == "cpp":
1055
+ contains = f"({right}.find({cur}) != std::string::npos)"
1056
+ elif self.spec.name == "csharp":
1057
+ contains = f"{right}.Contains({cur})"
1058
+ elif self.spec.name == "go":
1059
+ contains = f"strContains({right}, {cur})"
1060
+ elif self.spec.name == "kotlin":
1061
+ contains = f"{right}.contains({cur})"
1062
+ else:
1063
+ contains = f"/*contains*/"
1064
+ if isinstance(op, ast.NotIn):
1065
+ parts.append(f"(!{contains})")
1066
+ else:
1067
+ parts.append(contains)
1068
+ cur = self.expr(comp)
1069
+ else:
1070
+ sym = self.cmp_symbol(op)
1071
+ right = self.expr(comp)
1072
+ parts.append(f"({cur} {sym} {right})")
1073
+ cur = right
1074
+ return "(" + " && ".join(parts) + ")"
1075
+ if isinstance(node, ast.IfExp):
1076
+ # ternary: x if cond else y
1077
+ cond = self._condition(node.test)
1078
+ body = self.expr(node.body)
1079
+ orelse = self.expr(node.orelse)
1080
+ if self.spec.name == "rust":
1081
+ return f"if {cond} {{ {body} }} else {{ {orelse} }}"
1082
+ if self.spec.name in ("cpp", "csharp", "kotlin"):
1083
+ return f"({cond} ? {body} : {orelse})"
1084
+ if self.spec.name == "go":
1085
+ return f"(func() int64 {{ if {cond} {{ return {body} }}; return {orelse} }}())"
1086
+ if self.spec.name == "zig":
1087
+ return f"if ({cond}) {body} else {orelse}"
1088
+ return f"({cond} ? {body} : {orelse})"
1089
+ if isinstance(node, ast.Call):
1090
+ return self.call(node)
1091
+ if isinstance(node, ast.Subscript):
1092
+ base = self.expr(node.value)
1093
+ base_type = self.infer_type(node.value)
1094
+ # handle slicing (ast.Slice)
1095
+ if isinstance(node.slice, ast.Slice):
1096
+ sl = node.slice
1097
+ start = self.expr(sl.lower) if sl.lower else None
1098
+ stop = self.expr(sl.upper) if sl.upper else None
1099
+ if base_type == "str":
1100
+ if start and stop:
1101
+ return self.spec.str_slice.format(x=base, start=start, end=stop, len=f"({stop} - {start})")
1102
+ if start:
1103
+ return self.spec.str_slice_start.format(x=base, start=start)
1104
+ if stop:
1105
+ return self.spec.str_slice_end.format(x=base, end=stop)
1106
+ return base # full slice s[:] = s
1107
+ # list slicing — not fully supported, mark as unsupported
1108
+ if start and stop:
1109
+ return self._mark_unsupported(f"list slice {base}[{start}:{stop}]")
1110
+ return self._mark_unsupported(f"list slice {base}")
1111
+ idx = self.expr(node.slice)
1112
+ if base_type == "dict":
1113
+ return self.spec.dict_get.format(d=base, k=idx)
1114
+ if base_type == "tuple":
1115
+ # tuple element access: use tuple_get spec
1116
+ # idx should be a constant integer
1117
+ if isinstance(node.slice, ast.Constant) and isinstance(node.slice.value, int):
1118
+ i = node.slice.value
1119
+ # Go uses field names a, b, c... and Kotlin uses first, second
1120
+ if self.spec.name == "go":
1121
+ field = chr(97 + i) # 0->a, 1->b, etc.
1122
+ return self.spec.tuple_get.format(t=base, field=field)
1123
+ if self.spec.name == "kotlin":
1124
+ field = "first" if i == 0 else "second" if i == 1 else f"component{i + 1}"
1125
+ return self.spec.tuple_get.format(t=base, field=field)
1126
+ if self.spec.name == "csharp":
1127
+ return self.spec.tuple_get.format(t=base, i1=i + 1)
1128
+ return self.spec.tuple_get.format(t=base, i=i)
1129
+ return self.spec.tuple_get.format(t=base, i=idx)
1130
+ if base_type == "str":
1131
+ return self.spec.str_index.format(x=base, i=idx)
1132
+ # Production: bounds-checked list indexing
1133
+ if self.spec.name == "rust":
1134
+ return f"{base}[{idx} as usize]"
1135
+ return self.spec.index_call.format(x=base, i=idx)
1136
+ if isinstance(node, ast.List):
1137
+ elems = ", ".join(self.expr(e) for e in node.elts)
1138
+ if self.spec.name == "rust":
1139
+ return f"vec![{elems}]"
1140
+ if self.spec.name == "go":
1141
+ return f"[]{self.spec.list_elem_type}{{{elems}}}"
1142
+ if self.spec.name == "kotlin":
1143
+ return f"mutableListOf({elems})"
1144
+ if self.spec.name == "zig":
1145
+ return f"&[_]i64{{ {elems} }}"
1146
+ if self.spec.name == "csharp":
1147
+ return f"new {self.spec.list_type}{{{elems}}}"
1148
+ return f"std::vector<{self.spec.list_elem_type}>{{{elems}}}"
1149
+ if isinstance(node, ast.ListComp):
1150
+ return self._list_comp(node)
1151
+ if isinstance(node, ast.Dict):
1152
+ # emit as a map literal — backend-specific
1153
+ if self.spec.name == "rust":
1154
+ pairs = ", ".join(f"({self.expr(k)}, {self.expr(v)})"
1155
+ for k, v in zip(node.keys, node.values))
1156
+ return f"std::collections::HashMap::from([{pairs}])"
1157
+ if self.spec.name == "cpp":
1158
+ pairs = ", ".join(f"{{ {self.expr(k)}, {self.expr(v)} }}"
1159
+ for k, v in zip(node.keys, node.values))
1160
+ return f"{{{{{pairs}}}}}"
1161
+ if self.spec.name == "csharp":
1162
+ pairs = ", ".join(f"{{ {self.expr(k)}, {self.expr(v)} }}"
1163
+ for k, v in zip(node.keys, node.values))
1164
+ return f"new Dictionary<string, {self.spec.list_elem_type}>() {{{pairs}}}"
1165
+ if self.spec.name == "go":
1166
+ pairs = ", ".join(f"{self.expr(k)}: {self.expr(v)}"
1167
+ for k, v in zip(node.keys, node.values))
1168
+ return f"map[string]{self.spec.list_elem_type}{{{pairs}}}"
1169
+ if self.spec.name == "kotlin":
1170
+ pairs = ", ".join(f"{self.expr(k)} to {self.expr(v)}"
1171
+ for k, v in zip(node.keys, node.values))
1172
+ return f"hashMapOf({pairs})"
1173
+ if self.spec.name == "zig":
1174
+ # Zig doesn't have map literals — emit a block expression
1175
+ # that creates and populates the map
1176
+ puts = "\n".join(f" m.put({self.expr(k)}, {self.expr(v)}) catch unreachable;"
1177
+ for k, v in zip(node.keys, node.values))
1178
+ return f"blk: {{\n var m = std.StringHashMap({self.spec.list_elem_type}).init(std.heap.page_allocator);\n{puts}\n break :blk m;\n }}"
1179
+ return f"{{{pairs}}}"
1180
+ if isinstance(node, ast.Set):
1181
+ elems = ", ".join(self.expr(e) for e in node.elts)
1182
+ if self.spec.name == "rust":
1183
+ return f"std::collections::HashSet::from([{elems}])"
1184
+ if self.spec.name == "cpp":
1185
+ return f"std::set<int64_t>{{{elems}}}"
1186
+ if self.spec.name == "csharp":
1187
+ return f"new HashSet<long>{{{elems}}}"
1188
+ if self.spec.name == "go":
1189
+ return f"map[int64_t]struct{{}}{{}}"
1190
+ if self.spec.name == "kotlin":
1191
+ return f"hashSetOf({elems})"
1192
+ return f"std::set<int64_t>{{{elems}}}"
1193
+ if isinstance(node, ast.SetComp):
1194
+ return self._set_comp(node)
1195
+ if isinstance(node, ast.DictComp):
1196
+ return self._dict_comp(node)
1197
+ if isinstance(node, ast.Tuple):
1198
+ elems = ", ".join(self.expr(e) for e in node.elts)
1199
+ if self.spec.name == "rust":
1200
+ return f"({elems})"
1201
+ if self.spec.name == "cpp":
1202
+ return f"std::make_tuple({elems})"
1203
+ if self.spec.name == "csharp":
1204
+ return f"({elems})"
1205
+ if self.spec.name == "kotlin":
1206
+ return f"Pair({elems})"
1207
+ if self.spec.name == "zig":
1208
+ return f"[_]{self.spec.list_elem_type}{{ {elems} }}"
1209
+ if self.spec.name == "go":
1210
+ # Go uses struct literal with field names a, b, c...
1211
+ fields = ", ".join(f"{chr(97 + i)}: {self.expr(e)}"
1212
+ for i, e in enumerate(node.elts))
1213
+ # build struct type with field names
1214
+ field_types = "; ".join(f"{chr(97 + i)} {self.spec.list_elem_type}"
1215
+ for i in range(len(node.elts)))
1216
+ return f"struct {{ {field_types} }}{{ {fields} }}"
1217
+ return f"({elems})"
1218
+ if isinstance(node, ast.Attribute):
1219
+ base = self.expr(node.value)
1220
+ # check if this is a property access (obj.prop -> ClassName_prop(obj))
1221
+ base_type = self.infer_type(node.value)
1222
+ if base_type in self.class_names:
1223
+ props = self.class_properties.get(base_type, [])
1224
+ if node.attr in props:
1225
+ return f"{base_type}_{node.attr}({base})"
1226
+ return f"{base}.{node.attr}"
1227
+ return self._mark_unsupported('{type(node).__name__}')
1228
+
1229
+ def call(self, node: ast.Call) -> str:
1230
+ fname = getattr(node.func, "id", None)
1231
+ # inline target-language escape hatch: ge_inline("backend", "raw code")
1232
+ # Security: validate backend name and log usage
1233
+ if fname == "ge_inline":
1234
+ if len(node.args) >= 2:
1235
+ target_backend = ""
1236
+ if isinstance(node.args[0], ast.Constant) and isinstance(node.args[0].value, str):
1237
+ target_backend = node.args[0].value
1238
+ # validate backend name against known backends
1239
+ valid_backends = {"rust", "cpp", "csharp", "zig", "go", "kotlin", "dart"}
1240
+ if target_backend not in valid_backends:
1241
+ self._mark_unsupported(f"ge_inline: invalid backend '{target_backend}'")
1242
+ return f"/*ge_inline: invalid backend '{target_backend}'*/"
1243
+ if isinstance(node.args[1], ast.Constant) and isinstance(node.args[1].value, str):
1244
+ raw_code = node.args[1].value
1245
+ if target_backend == self.spec.name:
1246
+ return raw_code
1247
+ return f"/*ge_inline: skipped for {self.spec.name}*/"
1248
+ return f"/*ge_inline: invalid usage*/"
1249
+ if fname == "ge_raw":
1250
+ # emit raw code regardless of backend
1251
+ # Security: this is an arbitrary code injection point
1252
+ if node.args and isinstance(node.args[0], ast.Constant) and isinstance(node.args[0].value, str):
1253
+ return node.args[0].value
1254
+ return f"/*ge_raw: invalid usage*/"
1255
+ if fname == "ge_preamble":
1256
+ # module-level file-scope injection, collected by the analyzer.
1257
+ # A call inside a function body has nothing left to do.
1258
+ return f"/*ge_preamble: hoisted to file scope*/"
1259
+ if fname == "print":
1260
+ return self.print_call(node.args)
1261
+ if fname == "len":
1262
+ arg = node.args[0]
1263
+ t = self.infer_type(arg)
1264
+ if t == "str":
1265
+ return self.spec.str_len.format(x=self.expr(arg))
1266
+ if t == "list":
1267
+ return self.spec.list_len.format(x=self.expr(arg))
1268
+ return self.spec.len_call.format(x=self.expr(arg))
1269
+ if fname == "abs":
1270
+ t = self.infer_type(node.args[0])
1271
+ tmpl = self.spec.abs_int if t == "int" else self.spec.abs_float
1272
+ return tmpl.format(x=self.expr(node.args[0]))
1273
+ if fname == "sum":
1274
+ return self.spec.sum_call.format(it=self.expr(node.args[0]))
1275
+ if fname in ("min", "max"):
1276
+ tmpl = self.spec.min_call if fname == "min" else self.spec.max_call
1277
+ raw = [self.expr(a) for a in node.args]
1278
+ if len(raw) == 1:
1279
+ return tmpl.format(it=raw[0])
1280
+ return f"{fname}({', '.join(raw)})"
1281
+ if fname == "pow":
1282
+ return self.spec.pow_call.format(l=self.expr(node.args[0]), r=self.expr(node.args[1]))
1283
+ if fname == "int":
1284
+ return f"({self.spec.int_cast.format(x=self.expr(node.args[0]))})"
1285
+ if fname == "float":
1286
+ return f"({self.spec.float_cast.format(x=self.expr(node.args[0]))})"
1287
+ if fname == "str":
1288
+ return self._str_call(node.args[0])
1289
+ if fname == "bool":
1290
+ return f"({self.expr(node.args[0])} != 0)"
1291
+ if fname == "sorted":
1292
+ # sorted(list) -> copy and sort (simplified)
1293
+ if self.spec.name == "rust":
1294
+ return f"{{ let mut v = {self.expr(node.args[0])}.clone(); v.sort(); v }}"
1295
+ if self.spec.name == "cpp":
1296
+ return f"([&]() {{ auto v = {self.expr(node.args[0])}; std::sort(v.begin(), v.end()); return v; }}())"
1297
+ if self.spec.name == "csharp":
1298
+ return f"{self.expr(node.args[0])}.OrderBy(x => x).ToList()"
1299
+ if self.spec.name == "go":
1300
+ return f"func() []int64 {{ var v = make([]int64, len({self.expr(node.args[0])})); copy(v, {self.expr(node.args[0])}); sort.Slice(v, func(i, j int) bool {{ return v[i] < v[j] }}); return v; }}()"
1301
+ if self.spec.name == "kotlin":
1302
+ return f"{self.expr(node.args[0])}.sorted().toMutableList()"
1303
+ return self._mark_unsupported('sorted()')
1304
+ if fname == "reversed":
1305
+ if self.spec.name == "rust":
1306
+ return f"{{ let v = {self.expr(node.args[0])}; v.iter().rev().cloned().collect::<Vec<_>>() }}"
1307
+ if self.spec.name == "cpp":
1308
+ return f"([&]() {{ auto v = {self.expr(node.args[0])}; std::reverse(v.begin(), v.end()); return v; }}())"
1309
+ if self.spec.name == "csharp":
1310
+ return f"{self.expr(node.args[0])}.ToArray().Reverse().ToList()"
1311
+ if self.spec.name == "go":
1312
+ return f"func() []int64 {{ var src = {self.expr(node.args[0])}; var v = make([]int64, len(src)); for i := range src {{ v[i] = src[len(src)-1-i] }}; return v; }}()"
1313
+ if self.spec.name == "kotlin":
1314
+ return f"{self.expr(node.args[0])}.reversed()"
1315
+ return self._mark_unsupported('reversed()')
1316
+ if fname == "any":
1317
+ it = self.expr(node.args[0])
1318
+ if self.spec.name == "rust":
1319
+ return f"{it}.iter().any(|&x| x != 0)"
1320
+ if self.spec.name == "cpp":
1321
+ return f"std::any_of({it}.begin(), {it}.end(), [](int64_t x) {{ return x != 0; }})"
1322
+ if self.spec.name == "csharp":
1323
+ return f"{it}.Any(x => x != 0)"
1324
+ if self.spec.name == "go":
1325
+ return f"func() bool {{ for _, v := range {it} {{ if v != 0 {{ return true }} }}; return false }}()"
1326
+ if self.spec.name == "kotlin":
1327
+ return f"{it}.any {{ it != 0L }}"
1328
+ return self._mark_unsupported('any()')
1329
+ if fname == "all":
1330
+ it = self.expr(node.args[0])
1331
+ if self.spec.name == "rust":
1332
+ return f"{it}.iter().all(|&x| x != 0)"
1333
+ if self.spec.name == "cpp":
1334
+ return f"std::all_of({it}.begin(), {it}.end(), [](int64_t x) {{ return x != 0; }})"
1335
+ if self.spec.name == "csharp":
1336
+ return f"{it}.All(x => x != 0)"
1337
+ if self.spec.name == "go":
1338
+ return f"func() bool {{ for _, v := range {it} {{ if v == 0 {{ return false }} }}; return true }}()"
1339
+ if self.spec.name == "kotlin":
1340
+ return f"{it}.all {{ it != 0L }}"
1341
+ return self._mark_unsupported('all()')
1342
+ if fname == "divmod":
1343
+ a = self.expr(node.args[0])
1344
+ b = self.expr(node.args[1])
1345
+ if self.spec.name == "rust":
1346
+ return f"({a} / {b}, {a} % {b})"
1347
+ if self.spec.name == "cpp":
1348
+ return f"std::make_tuple({a} / {b}, {a} % {b})"
1349
+ if self.spec.name == "csharp":
1350
+ return f"({a} / {b}, {a} % {b})"
1351
+ if self.spec.name == "go":
1352
+ return f"struct {{ a int64; b int64 }}{{ {a} / {b}, {a} % {b} }}"
1353
+ if self.spec.name == "kotlin":
1354
+ return f"Pair({a} / {b}, {a} % {b})"
1355
+ return f"({a} / {b}, {a} % {b})"
1356
+ if fname == "bin":
1357
+ v = self.expr(node.args[0])
1358
+ if self.spec.name == "rust":
1359
+ return f'format!("{{:b}}", {v})'
1360
+ if self.spec.name == "cpp":
1361
+ return f"std::bitset<64>({v}).to_string()"
1362
+ if self.spec.name == "csharp":
1363
+ return f"Convert.ToString({v}, 2)"
1364
+ if self.spec.name == "go":
1365
+ return f'strconv.FormatInt({v}, 2)'
1366
+ if self.spec.name == "kotlin":
1367
+ return f"{v}.toString(2)"
1368
+ return self._mark_unsupported('bin()')
1369
+ if fname == "hex":
1370
+ v = self.expr(node.args[0])
1371
+ if self.spec.name == "rust":
1372
+ return f'format!("{{:x}}", {v})'
1373
+ if self.spec.name == "cpp":
1374
+ return f'ge_int_to_hex({v})'
1375
+ if self.spec.name == "csharp":
1376
+ return f"{v}.ToString(\"x\")"
1377
+ if self.spec.name == "go":
1378
+ return f'strconv.FormatInt({v}, 16)'
1379
+ if self.spec.name == "kotlin":
1380
+ return f"{v}.toString(16)"
1381
+ return self._mark_unsupported('hex()')
1382
+ if fname == "oct":
1383
+ v = self.expr(node.args[0])
1384
+ if self.spec.name == "rust":
1385
+ return f'format!("{{:o}}", {v})'
1386
+ if self.spec.name == "cpp":
1387
+ return f'ge_int_to_oct({v})'
1388
+ if self.spec.name == "csharp":
1389
+ return f"Convert.ToString({v}, 8)"
1390
+ if self.spec.name == "go":
1391
+ return f'strconv.FormatInt({v}, 8)'
1392
+ if self.spec.name == "kotlin":
1393
+ return f"{v}.toString(8)"
1394
+ return self._mark_unsupported('oct()')
1395
+ if fname == "chr":
1396
+ v = self.expr(node.args[0])
1397
+ if self.spec.name == "rust":
1398
+ return f"char::from_u32({v} as u32).unwrap_or('?').to_string()"
1399
+ if self.spec.name == "cpp":
1400
+ return f"std::string(1, (char)({v}))"
1401
+ if self.spec.name == "csharp":
1402
+ return f"((char)({v})).ToString()"
1403
+ if self.spec.name == "go":
1404
+ return f"string(rune({v}))"
1405
+ if self.spec.name == "kotlin":
1406
+ return f"{v}.toChar().toString()"
1407
+ return self._mark_unsupported('chr()')
1408
+ if fname == "ord":
1409
+ v = self.expr(node.args[0])
1410
+ if self.spec.name == "rust":
1411
+ return f"{v}.as_bytes()[0] as i64"
1412
+ if self.spec.name == "cpp":
1413
+ return f"(int64_t)({v})[0]"
1414
+ if self.spec.name == "csharp":
1415
+ return f"(long)({v})[0]"
1416
+ if self.spec.name == "go":
1417
+ return f"int64({v}[0])"
1418
+ if self.spec.name == "kotlin":
1419
+ return f"{v}[0].toLong()"
1420
+ return self._mark_unsupported('ord()')
1421
+ if fname == "isinstance":
1422
+ # Static type check — resolved at compile time
1423
+ val_type = self.infer_type(node.args[0])
1424
+ if isinstance(node.args[1], ast.Name):
1425
+ check_type = node.args[1].id
1426
+ return "true" if val_type == check_type else "false"
1427
+ return "false"
1428
+ if fname == "repr":
1429
+ v = self.expr(node.args[0])
1430
+ t = self.infer_type(node.args[0])
1431
+ if t == "str":
1432
+ if self.spec.name == "rust":
1433
+ return f'format!("{{:?}}", {v})'
1434
+ if self.spec.name == "cpp":
1435
+ return f'"\\""' + f" + {v} + " + f'"\\""'
1436
+ if self.spec.name == "csharp":
1437
+ return f'"\\\""' + f" + {v} + " + f'"\\\""'
1438
+ if self.spec.name == "go":
1439
+ return f'fmt.Sprintf("%q", {v})'
1440
+ if self.spec.name == "kotlin":
1441
+ return f'"\\""' + f" + {v} + " + f'"\\""'
1442
+ return self._str_call(node.args[0])
1443
+ if fname == "hash":
1444
+ v = self.expr(node.args[0])
1445
+ if self.spec.name == "rust":
1446
+ return f"std::collections::hash_map::DefaultHasher::hash(&mut std::collections::hash_map::DefaultHasher::new(), &{v}) as i64"
1447
+ if self.spec.name == "cpp":
1448
+ return f"(int64_t)std::hash<int64_t>()({v})"
1449
+ if self.spec.name == "csharp":
1450
+ return f"{v}.GetHashCode()"
1451
+ if self.spec.name == "go":
1452
+ return f"int64(uintptr(unsafe.Pointer(&{v})))"
1453
+ if self.spec.name == "kotlin":
1454
+ return f"{v}.hashCode().toLong()"
1455
+ return self._mark_unsupported('hash()')
1456
+ if fname == "input":
1457
+ if self.spec.name == "rust":
1458
+ return '{ let mut s = String::new(); std::io::stdin().read_line(&mut s).ok(); s.trim_end().to_string() }'
1459
+ if self.spec.name == "cpp":
1460
+ return '([]() { std::string s; std::getline(std::cin, s); return s; }())'
1461
+ if self.spec.name == "csharp":
1462
+ return 'Console.ReadLine() ?? ""'
1463
+ if self.spec.name == "go":
1464
+ return 'func() string { var s string; fmt.Scanln(&s); return s }()'
1465
+ if self.spec.name == "kotlin":
1466
+ return 'readLine() ?: ""'
1467
+ return self._mark_unsupported('input()')
1468
+ if fname == "map":
1469
+ fn_arg = node.args[0]
1470
+ it = self.expr(node.args[1])
1471
+ fn_name = getattr(fn_arg, "id", None) or getattr(getattr(fn_arg, "func", None), "id", None)
1472
+ if fn_name and self.spec.name == "rust":
1473
+ return f"{it}.iter().map(|&x| {fn_name}(x)).collect::<Vec<i64>>()"
1474
+ if fn_name and self.spec.name == "cpp":
1475
+ return f"([&]() {{ std::vector<int64_t> r; for (auto x : {it}) r.push_back({fn_name}(x)); return r; }}())"
1476
+ if fn_name and self.spec.name == "csharp":
1477
+ return f"{it}.Select(x => {fn_name}(x)).ToList()"
1478
+ if fn_name and self.spec.name == "go":
1479
+ return f"func() []int64 {{ var r []int64; for _, x := range {it} {{ r = append(r, {fn_name}(x)) }}; return r; }}()"
1480
+ if fn_name and self.spec.name == "kotlin":
1481
+ return f"{it}.map {{ {fn_name}(it) }}.toMutableList()"
1482
+ if fn_name:
1483
+ return self._mark_unsupported('map()')
1484
+ # lambda arg
1485
+ if isinstance(fn_arg, ast.Lambda):
1486
+ lam = self._lambda(fn_arg)
1487
+ if self.spec.name == "rust":
1488
+ return f"{it}.iter().map({lam}).collect::<Vec<i64>>()"
1489
+ if self.spec.name == "cpp":
1490
+ return f"([&]() {{ std::vector<int64_t> r; for (auto x : {it}) r.push_back(({lam})(x)); return r; }}())"
1491
+ if self.spec.name == "csharp":
1492
+ return f"{it}.Select({lam}).ToList()"
1493
+ if self.spec.name == "kotlin":
1494
+ return f"{it}.map {{ {lam}(it) }}.toMutableList()"
1495
+ return self._mark_unsupported('map()')
1496
+ if fname == "filter":
1497
+ fn_arg = node.args[0]
1498
+ it = self.expr(node.args[1])
1499
+ fn_name = getattr(fn_arg, "id", None) or getattr(getattr(fn_arg, "func", None), "id", None)
1500
+ if fn_name and self.spec.name == "rust":
1501
+ return f"{it}.iter().filter(|&x| {fn_name}(x) != 0).cloned().collect::<Vec<i64>>()"
1502
+ if fn_name and self.spec.name == "cpp":
1503
+ return f"([&]() {{ std::vector<int64_t> r; for (auto x : {it}) if ({fn_name}(x) != 0) r.push_back(x); return r; }}())"
1504
+ if fn_name and self.spec.name == "csharp":
1505
+ return f"{it}.Where(x => {fn_name}(x) != 0).ToList()"
1506
+ if fn_name and self.spec.name == "kotlin":
1507
+ return f"{it}.filter {{ {fn_name}(it) != 0L }}.toMutableList()"
1508
+ if isinstance(fn_arg, ast.Lambda):
1509
+ lam = self._lambda(fn_arg)
1510
+ if self.spec.name == "rust":
1511
+ return f"{it}.iter().filter(|&x| ({lam})(x) != 0).cloned().collect::<Vec<i64>>()"
1512
+ if self.spec.name == "cpp":
1513
+ return f"([&]() {{ std::vector<int64_t> r; for (auto x : {it}) if (({lam})(x) != 0) r.push_back(x); return r; }}())"
1514
+ if self.spec.name == "csharp":
1515
+ return f"{it}.Where(x => ({lam})(x) != 0).ToList()"
1516
+ if self.spec.name == "kotlin":
1517
+ return f"{it}.filter {{ ({lam})(it) != 0L }}.toMutableList()"
1518
+ return self._mark_unsupported('filter()')
1519
+ if fname == "enumerate":
1520
+ it = self.expr(node.args[0])
1521
+ if self.spec.name == "rust":
1522
+ return f"{it}.iter().enumerate().map(|(i, x)| (i as i64, *x)).collect::<Vec<(i64, i64)>>()"
1523
+ if self.spec.name == "cpp":
1524
+ return f"([&]() {{ std::vector<std::tuple<int64_t,int64_t>> r; for (int64_t i = 0; i < (int64_t){it}.size(); i++) r.push_back(std::make_tuple(i, {it}[i])); return r; }}())"
1525
+ if self.spec.name == "csharp":
1526
+ return f"{it}.Select((x, i) => (i, x)).ToList()"
1527
+ if self.spec.name == "kotlin":
1528
+ return f"{it}.mapIndexed {{ i, x -> Pair(i.toLong(), x) }}.toMutableList()"
1529
+ return self._mark_unsupported('enumerate()')
1530
+ if fname == "zip":
1531
+ it1 = self.expr(node.args[0])
1532
+ it2 = self.expr(node.args[1])
1533
+ if self.spec.name == "rust":
1534
+ return f"{it1}.iter().zip({it2}.iter()).map(|(a, b)| (*a, *b)).collect::<Vec<(i64, i64)>>()"
1535
+ if self.spec.name == "cpp":
1536
+ return f"([&]() {{ std::vector<std::tuple<int64_t,int64_t>> r; int64_t n = std::min({it1}.size(), {it2}.size()); for (int64_t i = 0; i < n; i++) r.push_back(std::make_tuple({it1}[i], {it2}[i])); return r; }}())"
1537
+ if self.spec.name == "csharp":
1538
+ return f"{it1}.Zip({it2}, (a, b) => (a, b)).ToList()"
1539
+ if self.spec.name == "kotlin":
1540
+ return f"{it1}.zip({it2}).toMutableList()"
1541
+ return self._mark_unsupported('zip()')
1542
+ if fname == "range":
1543
+ # range() used as a value (not in for loop) -> build a list
1544
+ args = node.args
1545
+ if len(args) == 1:
1546
+ lo, hi = "0", self.expr(args[0])
1547
+ elif len(args) == 2:
1548
+ lo, hi = self.expr(args[0]), self.expr(args[1])
1549
+ else:
1550
+ lo, hi = self.expr(args[0]), self.expr(args[1])
1551
+ if self.spec.name == "rust":
1552
+ return f"({lo}..{hi}).collect::<Vec<i64>>()"
1553
+ if self.spec.name == "cpp":
1554
+ return f"([&]() {{ std::vector<int64_t> r; for (int64_t i = {lo}; i < {hi}; i++) r.push_back(i); return r; }}())"
1555
+ if self.spec.name == "csharp":
1556
+ return f"Enumerable.Range((int)({lo}), (int)({hi}) - (int)({lo})).Select(x => (long)x).ToList()"
1557
+ if self.spec.name == "go":
1558
+ return f"func() []int64 {{ var r []int64; for i := {lo}; i < {hi}; i++ {{ r = append(r, i) }}; return r; }}()"
1559
+ if self.spec.name == "kotlin":
1560
+ return f"({lo} until {hi}).map {{ it.toLong() }}.toMutableList()"
1561
+ return self._mark_unsupported('range()')
1562
+ # stdlib file I/O and math functions
1563
+ if fname == "read_file":
1564
+ return self._stdlib_call("ge_read_file", node.args)
1565
+ if fname == "write_file":
1566
+ return self._stdlib_call("ge_write_file", node.args)
1567
+ if fname == "append_file":
1568
+ return self._stdlib_call("ge_append_file", node.args)
1569
+ if fname == "sqrt":
1570
+ return self._stdlib_call("ge_sqrt", node.args)
1571
+ if fname == "floor":
1572
+ return self._stdlib_call("ge_floor", node.args)
1573
+ if fname == "ceil":
1574
+ return self._stdlib_call("ge_ceil", node.args)
1575
+ if fname == "round":
1576
+ return self._stdlib_call("ge_round", node.args)
1577
+ if fname == "sin":
1578
+ return self._stdlib_call("ge_sin", node.args)
1579
+ if fname == "cos":
1580
+ return self._stdlib_call("ge_cos", node.args)
1581
+ if fname == "tan":
1582
+ return self._stdlib_call("ge_tan", node.args)
1583
+ if fname == "log":
1584
+ return self._stdlib_call("ge_log", node.args)
1585
+ if fname == "exp":
1586
+ return self._stdlib_call("ge_exp", node.args)
1587
+ if isinstance(node.func, ast.Attribute) and node.func.attr == "append":
1588
+ base = self.expr(node.func.value)
1589
+ return self.spec.append_call.format(x=base, v=self.expr(node.args[0]))
1590
+ # super().method(args) -> ParentClass_method(_self, args)
1591
+ if (isinstance(node.func, ast.Attribute) and
1592
+ isinstance(node.func.value, ast.Call) and
1593
+ getattr(node.func.value.func, "id", None) == "super"):
1594
+ method = node.func.attr
1595
+ args = [self._arg(a) for a in node.args]
1596
+ # find the parent class from the current method context
1597
+ parent = self._find_parent_class()
1598
+ if parent:
1599
+ return f"{parent}_{method}(_self, {', '.join(args)})"
1600
+ return f"/*super().{method}() — no parent class*/"
1601
+ # class constructor call: ClassName(args) -> ClassName_new(args)
1602
+ if fname and fname in self.class_names:
1603
+ args = [self._arg(a) for a in node.args]
1604
+ return f"{fname}_new({', '.join(args)})"
1605
+ # method call: obj.method(args) -> ClassName_method(obj, args)
1606
+ if isinstance(node.func, ast.Attribute) and not node.func.attr == "append":
1607
+ base = self.expr(node.func.value)
1608
+ args = [self._arg(a) for a in node.args]
1609
+ # check if base is a known class instance
1610
+ base_type = self.infer_type(node.func.value)
1611
+ if base_type in self.class_names:
1612
+ return f"{base_type}_{node.func.attr}({base}, {', '.join(args)})"
1613
+ # check if base name is a class name (static method call)
1614
+ if isinstance(node.func.value, ast.Name) and node.func.value.id in self.class_names:
1615
+ return f"{node.func.value.id}_{node.func.attr}({', '.join(args)})"
1616
+ # string methods
1617
+ if base_type == "str" or (isinstance(node.func.value, ast.Constant) and isinstance(node.func.value.value, str)):
1618
+ method = node.func.attr
1619
+ if method == "upper":
1620
+ if self.spec.name == "rust":
1621
+ return f"{base}.to_uppercase()"
1622
+ if self.spec.name == "cpp":
1623
+ return f"/*str.upper()*/"
1624
+ if self.spec.name == "csharp":
1625
+ return f"{base}.ToUpper()"
1626
+ if self.spec.name == "go":
1627
+ return f"strings.ToUpper({base})"
1628
+ if self.spec.name == "kotlin":
1629
+ return f"{base}.uppercase()"
1630
+ return f"{base}.upper()"
1631
+ if method == "lower":
1632
+ if self.spec.name == "rust":
1633
+ return f"{base}.to_lowercase()"
1634
+ if self.spec.name == "csharp":
1635
+ return f"{base}.ToLower()"
1636
+ if self.spec.name == "go":
1637
+ return f"strings.ToLower({base})"
1638
+ if self.spec.name == "kotlin":
1639
+ return f"{base}.lowercase()"
1640
+ return f"{base}.lower()"
1641
+ if method == "strip":
1642
+ if self.spec.name == "rust":
1643
+ return f"{base}.trim().to_string()"
1644
+ if self.spec.name == "csharp":
1645
+ return f"{base}.Trim()"
1646
+ if self.spec.name == "go":
1647
+ return f"strings.TrimSpace({base})"
1648
+ if self.spec.name == "kotlin":
1649
+ return f"{base}.trim()"
1650
+ return f"{base}.strip()"
1651
+ # generic method call
1652
+ return f"{base}.{node.func.attr}({', '.join(args)})"
1653
+ # generic (user) function call: borrow list args where appropriate
1654
+ # for Rust, check if the called function has mutated list params
1655
+ mutated_indices = self.func_mutated_params.get(fname, set())
1656
+ args = []
1657
+ for i, a in enumerate(node.args):
1658
+ if self.spec.name == "rust" and i in mutated_indices:
1659
+ # this arg position is a mutated list param — pass &mut
1660
+ t = self.infer_type(a)
1661
+ if t == "list" and isinstance(a, ast.Name):
1662
+ # if the arg is already a mutated param of the current function,
1663
+ # it's already a &mut Vec<i64> reference — pass it directly
1664
+ if a.id in self.mutated_params:
1665
+ args.append(self.expr(a))
1666
+ else:
1667
+ args.append(f"&mut {self.expr(a)}")
1668
+ continue
1669
+ args.append(self._arg(a))
1670
+ # handle keyword arguments: map to positional by reordering
1671
+ # (native languages don't support keyword args, so we just append them)
1672
+ for kw in node.keywords:
1673
+ if kw.arg is None:
1674
+ # **kwargs — not supported, skip
1675
+ continue
1676
+ args.append(self.expr(kw.value))
1677
+ call_str = f"{fname}({', '.join(args)})"
1678
+ # wrap extern "C" calls in unsafe block (Rust requires this)
1679
+ if fname in self.extern_names and self.spec.name == "rust":
1680
+ return f"unsafe {{ {call_str} }}"
1681
+ return call_str
1682
+
1683
+ def _str_call(self, arg: ast.AST) -> str:
1684
+ """Convert a value to string."""
1685
+ t = self.infer_type(arg)
1686
+ v = self.expr(arg)
1687
+ if self.spec.name == "rust":
1688
+ return f"{v}.to_string()"
1689
+ if self.spec.name == "cpp":
1690
+ return f"std::to_string({v})"
1691
+ if self.spec.name == "csharp":
1692
+ return f"{v}.ToString()"
1693
+ if self.spec.name == "zig":
1694
+ return f"std.fmt.allocPrint(std.heap.page_allocator, \"{{d}}\", .{{{v}}}) catch unreachable"
1695
+ if self.spec.name == "go":
1696
+ return f"strconv.FormatInt({v}, 10)"
1697
+ if self.spec.name == "kotlin":
1698
+ return f"{v}.toString()"
1699
+ return f"std::to_string({v})"
1700
+
1701
+ def _arg(self, node: ast.AST) -> str:
1702
+ """Render a user-call argument, borrowing owned list values when needed."""
1703
+ s = self.expr(node)
1704
+ if not self.spec.borrow_list_arg:
1705
+ return s
1706
+ if isinstance(node, ast.Name) and node.id in self.params:
1707
+ # already a borrowed slice param -> pass as-is
1708
+ # but if it's a mutated list param (&mut Vec), reborrow as &[i64]
1709
+ if self.spec.name == "rust" and node.id in self.mutated_params:
1710
+ return f"&*{s}"
1711
+ return s
1712
+ t = self.infer_type(node)
1713
+ if t == "list":
1714
+ return "&" + s
1715
+ return s
1716
+
1717
+ def _stdlib_call(self, ge_name: str, args: list[ast.AST]) -> str:
1718
+ """Emit a call to a GE stdlib runtime function, adapting the name per backend."""
1719
+ # C# uses PascalCase
1720
+ if self.spec.name == "csharp":
1721
+ parts = ge_name.split("_")
1722
+ cs_name = "".join(p.capitalize() for p in parts)
1723
+ cs_name = "Ge" + cs_name[2:] if cs_name.startswith("Ge") else cs_name
1724
+ arg_strs = ", ".join(self.expr(a) for a in args)
1725
+ return f"{cs_name}({arg_strs})"
1726
+ # Go uses camelCase
1727
+ if self.spec.name == "go":
1728
+ parts = ge_name.split("_")
1729
+ go_name = parts[0] + "".join(p.capitalize() for p in parts[1:])
1730
+ arg_strs = ", ".join(self.expr(a) for a in args)
1731
+ return f"{go_name}({arg_strs})"
1732
+ arg_strs = ", ".join(self.expr(a) for a in args)
1733
+ return f"{ge_name}({arg_strs})"
1734
+
1735
+ def _lambda(self, node: ast.Lambda) -> str:
1736
+ """Lower a lambda expression to backend-specific closure syntax."""
1737
+ params = node.args.args
1738
+ body_expr = self.expr(node.body)
1739
+
1740
+ if self.spec.name == "rust":
1741
+ args = ", ".join(f"{a.arg}: i64" for a in params)
1742
+ return f"|{args}| {{ {body_expr} }}"
1743
+ if self.spec.name == "cpp":
1744
+ args = ", ".join(f"int64_t {a.arg}" for a in params)
1745
+ return f"[]({args}) -> int64_t {{ return {body_expr}; }}"
1746
+ if self.spec.name == "csharp":
1747
+ args = ", ".join(f"long {a.arg}" for a in params)
1748
+ if len(params) == 1:
1749
+ return f"({args}) => {body_expr}"
1750
+ return f"({args}) => {body_expr}"
1751
+ if self.spec.name == "go":
1752
+ args = ", ".join(f"{a.arg} int64" for a in params)
1753
+ return f"func({args}) int64 {{ return {body_expr} }}"
1754
+ if self.spec.name == "kotlin":
1755
+ args = ", ".join(f"{a.arg}: Long" for a in params)
1756
+ return f"{{ {args} -> {body_expr} }}"
1757
+ if self.spec.name == "zig":
1758
+ # Zig doesn't have lambdas — emit as an anonymous struct with a call method
1759
+ # This is a simplification; real Zig would need a comptime generic
1760
+ return self._mark_unsupported('lambda in Zig')
1761
+ return self._mark_unsupported('lambda')
1762
+
1763
+ def _is_generator(self, body: ast.AST) -> bool:
1764
+ """Check if a function body contains yield statements."""
1765
+ for node in ast.walk(body):
1766
+ if isinstance(node, (ast.Yield, ast.YieldFrom)):
1767
+ return True
1768
+ return False
1769
+
1770
+ def _transform_generator(self, unit: FuncUnit) -> FuncUnit:
1771
+ """Transform a generator function into a list-building function.
1772
+
1773
+ Replaces `yield value` with `__gen_result.append(value)`,
1774
+ adds a list variable at the start, and a return at the end.
1775
+ Changes the return type to 'list'.
1776
+ """
1777
+ import copy
1778
+ # deep copy the body to avoid modifying the original
1779
+ body = copy.deepcopy(unit.body)
1780
+ # transform yield statements
1781
+ self._transform_yield(body)
1782
+ # prepend a list declaration and append a return statement
1783
+ list_decl = ast.AnnAssign(
1784
+ target=ast.Name(id="__gen_result", ctx=ast.Store()),
1785
+ annotation=ast.Name(id="list", ctx=ast.Load()),
1786
+ value=ast.List(elts=[], ctx=ast.Load()),
1787
+ simple=1,
1788
+ )
1789
+ list_decl.lineno = body.lineno
1790
+ ret = ast.Return(value=ast.Name(id="__gen_result", ctx=ast.Load()))
1791
+ ret.lineno = body.end_lineno or body.lineno
1792
+ body.body = [list_decl] + body.body + [ret]
1793
+ # change return type to list
1794
+ unit = FuncUnit(
1795
+ name=unit.name,
1796
+ lineno=unit.lineno,
1797
+ params=unit.params,
1798
+ ret_type="list",
1799
+ body=body,
1800
+ source=unit.source,
1801
+ forced_backend=unit.forced_backend,
1802
+ supported=unit.supported,
1803
+ unsupported_reasons=unit.unsupported_reasons,
1804
+ features=unit.features,
1805
+ backend=unit.backend,
1806
+ )
1807
+ return unit
1808
+
1809
+ def _transform_yield(self, node: ast.AST) -> None:
1810
+ """Recursively transform yield statements into list.append() calls."""
1811
+ for field in ast.iter_fields(node):
1812
+ if isinstance(field[1], list):
1813
+ new_list = []
1814
+ for child in field[1]:
1815
+ if isinstance(child, ast.Expr) and isinstance(child.value, ast.Yield):
1816
+ # yield value -> __gen_result.append(value)
1817
+ val = child.value.value
1818
+ if val is None:
1819
+ val = ast.Constant(value=None)
1820
+ append_call = ast.Expr(
1821
+ value=ast.Call(
1822
+ func=ast.Attribute(
1823
+ value=ast.Name(id="__gen_result", ctx=ast.Load()),
1824
+ attr="append",
1825
+ ctx=ast.Load(),
1826
+ ),
1827
+ args=[val],
1828
+ keywords=[],
1829
+ )
1830
+ )
1831
+ append_call.lineno = child.lineno
1832
+ new_list.append(append_call)
1833
+ elif isinstance(child, ast.Yield):
1834
+ # bare yield (not in Expr) -> wrap in append
1835
+ val = child.value if child.value else ast.Constant(value=None)
1836
+ append_call = ast.Expr(
1837
+ value=ast.Call(
1838
+ func=ast.Attribute(
1839
+ value=ast.Name(id="__gen_result", ctx=ast.Load()),
1840
+ attr="append",
1841
+ ctx=ast.Load(),
1842
+ ),
1843
+ args=[val],
1844
+ keywords=[],
1845
+ )
1846
+ )
1847
+ append_call.lineno = child.lineno
1848
+ new_list.append(append_call)
1849
+ else:
1850
+ self._transform_yield(child)
1851
+ new_list.append(child)
1852
+ setattr(node, field[0], new_list)
1853
+ elif isinstance(field[1], ast.AST):
1854
+ self._transform_yield(field[1])
1855
+
1856
+ def _with_stmt(self, node: ast.With, ind: str) -> None:
1857
+ """Lower a with statement to backend-specific resource management."""
1858
+ # Only support single-item with statements for now
1859
+ if len(node.items) != 1:
1860
+ self.lines.append(f"{ind}// unsupported multi-item with statement")
1861
+ return
1862
+ item = node.items[0]
1863
+ ctx = item.context_expr
1864
+ var_name = item.optional_vars.id if item.optional_vars else None
1865
+
1866
+ # Check if it's an open() call
1867
+ is_open = isinstance(ctx, ast.Call) and getattr(ctx.func, "id", None) == "open"
1868
+ if is_open:
1869
+ args = ctx.args
1870
+ path = self.expr(args[0]) if args else '""'
1871
+ mode = self.expr(args[1]) if len(args) > 1 else '"r"'
1872
+
1873
+ if self.spec.name == "cpp":
1874
+ if var_name:
1875
+ self.lines.append(f'{ind}std::ifstream {var_name}({path});')
1876
+ self.indent_lvl += 1
1877
+ for s in node.body:
1878
+ self.stmt(s)
1879
+ self.indent_lvl -= 1
1880
+ elif self.spec.name == "rust":
1881
+ if var_name:
1882
+ self.lines.append(f'{ind}let mut {var_name} = std::fs::File::open({path}).unwrap();')
1883
+ self.indent_lvl += 1
1884
+ for s in node.body:
1885
+ self.stmt(s)
1886
+ self.indent_lvl -= 1
1887
+ elif self.spec.name == "csharp":
1888
+ if var_name:
1889
+ self.lines.append(f'{ind}using (var {var_name} = System.IO.File.OpenText({path})) {{')
1890
+ else:
1891
+ self.lines.append(f'{ind}using (var __f = System.IO.File.OpenText({path})) {{')
1892
+ self.indent_lvl += 1
1893
+ for s in node.body:
1894
+ self.stmt(s)
1895
+ self.indent_lvl -= 1
1896
+ self.lines.append(f"{ind}}}")
1897
+ elif self.spec.name == "go":
1898
+ if var_name:
1899
+ self.lines.append(f'{ind}{var_name}, _ := os.Open({path})')
1900
+ self.lines.append(f'{ind}defer {var_name}.Close()')
1901
+ else:
1902
+ self.lines.append(f'{ind}__f, _ := os.Open({path})')
1903
+ self.lines.append(f'{ind}defer __f.Close()')
1904
+ self.indent_lvl += 1
1905
+ for s in node.body:
1906
+ self.stmt(s)
1907
+ self.indent_lvl -= 1
1908
+ elif self.spec.name == "kotlin":
1909
+ if var_name:
1910
+ self.lines.append(f'{ind}java.io.File({path}).bufferedReader().use {{ {var_name} ->')
1911
+ else:
1912
+ self.lines.append(f'{ind}java.io.File({path}).bufferedReader().use {{ __f ->')
1913
+ self.indent_lvl += 1
1914
+ for s in node.body:
1915
+ self.stmt(s)
1916
+ self.indent_lvl -= 1
1917
+ self.lines.append(f"{ind}}}")
1918
+ elif self.spec.name == "zig":
1919
+ if var_name:
1920
+ self.lines.append(f'{ind}var {var_name} = try std.fs.cwd().openFile({path}, .{{}});')
1921
+ self.lines.append(f'{ind}defer {var_name}.close();')
1922
+ self.indent_lvl += 1
1923
+ for s in node.body:
1924
+ self.stmt(s)
1925
+ self.indent_lvl -= 1
1926
+ else:
1927
+ self.lines.append(f"{ind}// unsupported with statement")
1928
+ self.indent_lvl += 1
1929
+ for s in node.body:
1930
+ self.stmt(s)
1931
+ self.indent_lvl -= 1
1932
+ else:
1933
+ # non-open with statements: emit as a comment + body
1934
+ self.lines.append(f"{ind}// with statement (non-file context)")
1935
+ self.indent_lvl += 1
1936
+ for s in node.body:
1937
+ self.stmt(s)
1938
+ self.indent_lvl -= 1
1939
+
1940
+ def _list_comp(self, node: ast.ListComp) -> str:
1941
+ """Lower a list comprehension to a block expression that builds a list."""
1942
+ # only support single-generator comprehensions for now
1943
+ if len(node.generators) != 1:
1944
+ return self._mark_unsupported('multi-gen comprehension')
1945
+ gen = node.generators[0]
1946
+ var = gen.target.id if isinstance(gen.target, ast.Name) else "_"
1947
+ # build the loop source
1948
+ it = gen.iter
1949
+ if isinstance(it, ast.Call) and getattr(it.func, "id", None) == "range":
1950
+ args = it.args
1951
+ if len(args) == 1:
1952
+ lo, hi = "0", self.expr(args[0])
1953
+ elif len(args) == 2:
1954
+ lo, hi = self.expr(args[0]), self.expr(args[1])
1955
+ else:
1956
+ lo, hi = self.expr(args[0]), self.expr(args[1])
1957
+ else:
1958
+ # for-in over a container
1959
+ lo, hi = None, None
1960
+ iter_src = self.expr(it)
1961
+
1962
+ elem = self.expr(node.elt)
1963
+ lt = self.spec.list_type.format(T=self.spec.list_elem_type)
1964
+
1965
+ if self.spec.name == "rust":
1966
+ if lo is not None:
1967
+ return (f"{{ let mut __v: {lt} = Vec::new(); "
1968
+ f"for {var} in {lo}..{hi} {{ __v.push({elem}); }} __v }}")
1969
+ return (f"{{ let mut __v: {lt} = Vec::new(); "
1970
+ f"for {var} in {iter_src} {{ __v.push({elem}); }} __v }}")
1971
+ if self.spec.name == "cpp":
1972
+ if lo is not None:
1973
+ return (f"([&]() {{ auto __v = std::vector<{self.spec.list_elem_type}>{{}}; "
1974
+ f"for (int64_t {var} = {lo}; {var} < {hi}; {var}++) {{ __v.push_back({elem}); }} "
1975
+ f"return __v; }}())")
1976
+ return (f"([&]() {{ auto __v = std::vector<{self.spec.list_elem_type}>{{}}; "
1977
+ f"for (auto {var} : {iter_src}) {{ __v.push_back({elem}); }} "
1978
+ f"return __v; }}())")
1979
+ if self.spec.name == "csharp":
1980
+ if lo is not None:
1981
+ # use LINQ: Enumerable.Range(start, count).Select(x => (long)elem).ToList()
1982
+ count = f"({hi} - {lo})"
1983
+ return f"Enumerable.Range((int)({lo}), (int)({count})).Select({var} => (long)({elem})).ToList()"
1984
+ return (f"{{ var __v = new {lt}(); "
1985
+ f"foreach (var {var} in {iter_src}) {{ __v.Add({elem}); }} "
1986
+ f"__v; }}")
1987
+ if self.spec.name == "go":
1988
+ if lo is not None:
1989
+ return (f"func() []int64 {{ var __v []int64; "
1990
+ f"for {var} := {lo}; {var} < {hi}; {var}++ {{ __v = append(__v, int64({elem})) }}; "
1991
+ f"return __v; }}()")
1992
+ return (f"func() []int64 {{ var __v []int64; "
1993
+ f"for _, {var} := range {iter_src} {{ __v = append(__v, int64({elem})) }}; "
1994
+ f"return __v; }}()")
1995
+ if self.spec.name == "zig":
1996
+ if lo is not None:
1997
+ return (f"blk: {{ var __v = std.ArrayList({self.spec.list_elem_type}).init(std.heap.page_allocator); "
1998
+ f"var {var}: i64 = {lo}; while ({var} < {hi}) : ({var} += 1) {{ __v.append({elem}) catch unreachable; }} "
1999
+ f"break :blk __v.toOwnedSlice() catch unreachable; }}")
2000
+ return self._mark_unsupported('for-in comprehension in Zig')
2001
+ if self.spec.name == "kotlin":
2002
+ if lo is not None:
2003
+ return (f"run {{ val __v = mutableListOf<Long>(); "
2004
+ f"for ({var} in {lo}..{hi}-1) {{ __v.add({elem}); }}; __v }}")
2005
+ return (f"run {{ val __v = mutableListOf<Long>(); "
2006
+ f"for ({var} in {iter_src}) {{ __v.add({elem}); }}; __v }}")
2007
+ return self._mark_unsupported('list comprehension')
2008
+
2009
+ def _set_comp(self, node: ast.SetComp) -> str:
2010
+ """Lower a set comprehension to a block expression that builds a set."""
2011
+ if len(node.generators) != 1:
2012
+ return self._mark_unsupported('multi-gen set comprehension')
2013
+ gen = node.generators[0]
2014
+ var = gen.target.id if isinstance(gen.target, ast.Name) else "_"
2015
+ it = gen.iter
2016
+ elem = self.expr(node.elt)
2017
+ if isinstance(it, ast.Call) and getattr(it.func, "id", None) == "range":
2018
+ args = it.args
2019
+ if len(args) == 1:
2020
+ lo, hi = "0", self.expr(args[0])
2021
+ elif len(args) == 2:
2022
+ lo, hi = self.expr(args[0]), self.expr(args[1])
2023
+ else:
2024
+ lo, hi = self.expr(args[0]), self.expr(args[1])
2025
+ iter_src = None
2026
+ else:
2027
+ lo, hi = None, None
2028
+ iter_src = self.expr(it)
2029
+ if self.spec.name == "rust":
2030
+ if lo is not None:
2031
+ return (f"{{ let mut __s: std::collections::HashSet<i64> = std::collections::HashSet::new(); "
2032
+ f"for {var} in {lo}..{hi} {{ __s.insert({elem}); }} __s }}")
2033
+ return (f"{{ let mut __s: std::collections::HashSet<i64> = std::collections::HashSet::new(); "
2034
+ f"for {var} in {iter_src} {{ __s.insert({elem}); }} __s }}")
2035
+ if self.spec.name == "cpp":
2036
+ if lo is not None:
2037
+ return (f"([&]() {{ std::set<int64_t> __s; "
2038
+ f"for (int64_t {var} = {lo}; {var} < {hi}; {var}++) {{ __s.insert({elem}); }} "
2039
+ f"return __s; }}())")
2040
+ return (f"([&]() {{ std::set<int64_t> __s; "
2041
+ f"for (auto {var} : {iter_src}) {{ __s.insert({elem}); }} "
2042
+ f"return __s; }}())")
2043
+ if self.spec.name == "csharp":
2044
+ if lo is not None:
2045
+ count = f"({hi} - {lo})"
2046
+ return f"Enumerable.Range((int)({lo}), (int)({count})).Select({var} => {elem}).ToHashSet()"
2047
+ return f"{iter_src}.Select({var} => {elem}).ToHashSet()"
2048
+ if self.spec.name == "kotlin":
2049
+ if lo is not None:
2050
+ return (f"run {{ val __s = hashSetOf<Long>(); "
2051
+ f"for ({var} in {lo}..{hi}-1) {{ __s.add({elem}) }}; __s }}")
2052
+ return (f"run {{ val __s = hashSetOf<Long>(); "
2053
+ f"for ({var} in {iter_src}) {{ __s.add({elem}) }}; __s }}")
2054
+ return self._mark_unsupported('set comprehension')
2055
+
2056
+ def _dict_comp(self, node: ast.DictComp) -> str:
2057
+ """Lower a dict comprehension to a block expression that builds a dict."""
2058
+ if len(node.generators) != 1:
2059
+ return self._mark_unsupported('multi-gen dict comprehension')
2060
+ gen = node.generators[0]
2061
+ var = gen.target.id if isinstance(gen.target, ast.Name) else "_"
2062
+ it = gen.iter
2063
+ key = self.expr(node.key)
2064
+ val = self.expr(node.value)
2065
+ if isinstance(it, ast.Call) and getattr(it.func, "id", None) == "range":
2066
+ args = it.args
2067
+ if len(args) == 1:
2068
+ lo, hi = "0", self.expr(args[0])
2069
+ elif len(args) == 2:
2070
+ lo, hi = self.expr(args[0]), self.expr(args[1])
2071
+ else:
2072
+ lo, hi = self.expr(args[0]), self.expr(args[1])
2073
+ iter_src = None
2074
+ else:
2075
+ lo, hi = None, None
2076
+ iter_src = self.expr(it)
2077
+ if self.spec.name == "rust":
2078
+ if lo is not None:
2079
+ return (f"{{ let mut __m: std::collections::HashMap<i64, i64> = std::collections::HashMap::new(); "
2080
+ f"for {var} in {lo}..{hi} {{ __m.insert({key}, {val}); }} __m }}")
2081
+ return (f"{{ let mut __m: std::collections::HashMap<i64, i64> = std::collections::HashMap::new(); "
2082
+ f"for {var} in {iter_src} {{ __m.insert({key}, {val}); }} __m }}")
2083
+ if self.spec.name == "cpp":
2084
+ if lo is not None:
2085
+ return (f"([&]() {{ std::map<int64_t, int64_t> __m; "
2086
+ f"for (int64_t {var} = {lo}; {var} < {hi}; {var}++) {{ __m[{key}] = {val}; }} "
2087
+ f"return __m; }}())")
2088
+ return (f"([&]() {{ std::map<int64_t, int64_t> __m; "
2089
+ f"for (auto {var} : {iter_src}) {{ __m[{key}] = {val}; }} "
2090
+ f"return __m; }}())")
2091
+ if self.spec.name == "csharp":
2092
+ if lo is not None:
2093
+ count = f"({hi} - {lo})"
2094
+ return f"Enumerable.Range((int)({lo}), (int)({count})).ToDictionary({var} => {key}, {var} => {val})"
2095
+ return f"{iter_src}.ToDictionary({var} => {key}, {var} => {val})"
2096
+ if self.spec.name == "kotlin":
2097
+ if lo is not None:
2098
+ return (f"run {{ val __m = hashMapOf<Long, Long>(); "
2099
+ f"for ({var} in {lo}..{hi}-1) {{ __m[{key}] = {val} }}; __m }}")
2100
+ return (f"run {{ val __m = hashMapOf<Long, Long>(); "
2101
+ f"for ({var} in {iter_src}) {{ __m[{key}] = {val} }}; __m }}")
2102
+ return self._mark_unsupported('dict comprehension')
2103
+
2104
+ def _fstring(self, node: ast.JoinedStr) -> str:
2105
+ """Convert an f-string (JoinedStr) to backend-specific string formatting."""
2106
+ parts = []
2107
+ for val in node.values:
2108
+ if isinstance(val, ast.Constant) and isinstance(val.value, str):
2109
+ parts.append(("text", val.value))
2110
+ elif isinstance(val, ast.FormattedValue):
2111
+ expr_str = self.expr(val.value)
2112
+ t = self.infer_type(val.value)
2113
+ parts.append(("expr", expr_str, t))
2114
+ # build the format string based on backend
2115
+ if self.spec.name == "rust":
2116
+ fmt_parts = []
2117
+ args = []
2118
+ for p in parts:
2119
+ if p[0] == "text":
2120
+ fmt_parts.append(p[1].replace("{", "{{").replace("}", "}}"))
2121
+ else:
2122
+ fmt_parts.append("{}")
2123
+ args.append(p[1])
2124
+ fmt_str = "".join(fmt_parts).replace('"', '\\"')
2125
+ args_str = ", ".join(args)
2126
+ return f'format!("{fmt_str}", {args_str})'
2127
+ if self.spec.name == "csharp":
2128
+ # C# interpolated string: $"text {expr} text"
2129
+ fmt_parts = []
2130
+ for p in parts:
2131
+ if p[0] == "text":
2132
+ fmt_parts.append(p[1].replace("{", "{{").replace("}", "}}"))
2133
+ else:
2134
+ fmt_parts.append("{" + p[1] + "}")
2135
+ return '$"' + "".join(fmt_parts).replace('"', '\\"') + '"'
2136
+ if self.spec.name == "kotlin":
2137
+ # Kotlin string template: "text ${expr} text"
2138
+ fmt_parts = []
2139
+ for p in parts:
2140
+ if p[0] == "text":
2141
+ fmt_parts.append(p[1].replace("$", "$$").replace('"', '\\"'))
2142
+ else:
2143
+ fmt_parts.append("${" + p[1] + "}")
2144
+ return '"' + "".join(fmt_parts) + '"'
2145
+ if self.spec.name == "go":
2146
+ # Go: fmt.Sprintf("text %v text", args...)
2147
+ fmt_parts = []
2148
+ args = []
2149
+ for p in parts:
2150
+ if p[0] == "text":
2151
+ fmt_parts.append(p[1].replace("%", "%%"))
2152
+ else:
2153
+ fmt_parts.append("%v")
2154
+ args.append(p[1])
2155
+ fmt_str = "".join(fmt_parts).replace('"', '\\"')
2156
+ args_str = ", ".join(args)
2157
+ return f'fmt.Sprintf("{fmt_str}", {args_str})'
2158
+ if self.spec.name == "zig":
2159
+ # Zig: std.fmt.allocPrint(allocator, "text {d} text", .{args...})
2160
+ fmt_parts = []
2161
+ args = []
2162
+ for p in parts:
2163
+ if p[0] == "text":
2164
+ fmt_parts.append(p[1].replace("{", "{{").replace("}", "}}"))
2165
+ else:
2166
+ t = p[2]
2167
+ if t == "str":
2168
+ fmt_parts.append("{s}")
2169
+ else:
2170
+ fmt_parts.append("{d}")
2171
+ args.append(p[1])
2172
+ fmt_str = "".join(fmt_parts).replace('"', '\\"')
2173
+ args_str = ", ".join(args)
2174
+ return f'std.fmt.allocPrint(std.heap.page_allocator, "{fmt_str}", .{{{args_str}}}) catch unreachable'
2175
+ # C++ default: string concatenation with std::to_string
2176
+ concat_parts = []
2177
+ for p in parts:
2178
+ if p[0] == "text":
2179
+ if p[1]:
2180
+ concat_parts.append('"' + p[1].replace('"', '\\"') + '"')
2181
+ else:
2182
+ t = p[2]
2183
+ if t == "str":
2184
+ concat_parts.append(p[1])
2185
+ else:
2186
+ concat_parts.append(f"std::to_string({p[1]})")
2187
+ if not concat_parts:
2188
+ return '""'
2189
+ return " + ".join(concat_parts)
2190
+
2191
+ def print_call(self, args: list) -> str:
2192
+ if not args:
2193
+ return self.spec.print_generic.format(v='""')
2194
+ arg = args[0]
2195
+ v = self.expr(arg)
2196
+ # infer type to choose the right print format
2197
+ t = self.infer_type(arg)
2198
+ if t == "str":
2199
+ return self.spec.print_str.format(v=v)
2200
+ if t == "float":
2201
+ return self.spec.print_float.format(v=v)
2202
+ if t == "bool":
2203
+ return self.spec.print_bool.format(v=v)
2204
+ # fallback to heuristics on the source text
2205
+ if any(c in v for c in ".") and not v.startswith('"'):
2206
+ return self.spec.print_float.format(v=v)
2207
+ if v in ("true", "false") or v.startswith("!"):
2208
+ return self.spec.print_bool.format(v=v)
2209
+ if v.startswith('"'):
2210
+ return self.spec.print_str.format(v=v)
2211
+ return self.spec.print_int.format(v=v)
2212
+
2213
+ # ---- type inference (simple) ----
2214
+ def _ann_type(self, node: ast.AST) -> str:
2215
+ """Read a PEP 484 annotation node to a tracked type string."""
2216
+ if isinstance(node, ast.Name):
2217
+ t = node.id
2218
+ if t in ("int", "float", "bool", "str", "list", "dict", "tuple", "set"):
2219
+ return t
2220
+ # class type
2221
+ if t in self.class_names:
2222
+ return t
2223
+ return "int"
2224
+ if isinstance(node, ast.Subscript):
2225
+ base = _ann_base(node)
2226
+ return base if base in ("list", "dict", "tuple", "set") else "list"
2227
+ return "int"
2228
+
2229
+ def infer_type(self, node: ast.AST) -> str:
2230
+ if isinstance(node, ast.Constant):
2231
+ if isinstance(node.value, bool):
2232
+ return "bool"
2233
+ if isinstance(node.value, int):
2234
+ return "int"
2235
+ if isinstance(node.value, float):
2236
+ return "float"
2237
+ if isinstance(node.value, str):
2238
+ return "str"
2239
+ if isinstance(node, ast.Name):
2240
+ return self.var_types.get(node.id, "int")
2241
+ if isinstance(node, ast.BinOp):
2242
+ lt = self.infer_type(node.left)
2243
+ rt = self.infer_type(node.right)
2244
+ if isinstance(node.op, ast.Div):
2245
+ return "float"
2246
+ if isinstance(node.op, ast.Add):
2247
+ if lt == "str" or rt == "str":
2248
+ return "str"
2249
+ if lt == "list" or rt == "list":
2250
+ return "list"
2251
+ if "float" in (lt, rt):
2252
+ return "float"
2253
+ return "int"
2254
+ if isinstance(node, ast.List):
2255
+ return "list"
2256
+ if isinstance(node, ast.ListComp):
2257
+ return "list"
2258
+ if isinstance(node, ast.Dict):
2259
+ return "dict"
2260
+ if isinstance(node, ast.Tuple):
2261
+ return "tuple"
2262
+ if isinstance(node, ast.JoinedStr):
2263
+ return "str"
2264
+ if isinstance(node, ast.Subscript):
2265
+ base_type = self.infer_type(node.value)
2266
+ if isinstance(node.slice, ast.Slice):
2267
+ # slicing a string returns a string, slicing a list returns a list
2268
+ return base_type if base_type in ("str", "list") else "list"
2269
+ if base_type == "str":
2270
+ return "int" # character access returns an integer (char code)
2271
+ if base_type == "dict":
2272
+ return "int" # dict values are integers
2273
+ if base_type == "tuple":
2274
+ return "int" # tuple elements are integers
2275
+ return "int" # list elements are integers
2276
+ if isinstance(node, ast.Call):
2277
+ fname = getattr(node.func, "id", None)
2278
+ if fname == "len":
2279
+ return "int"
2280
+ if fname == "float":
2281
+ return "float"
2282
+ if fname == "int":
2283
+ return "int"
2284
+ if fname == "str":
2285
+ return "str"
2286
+ if fname == "bool":
2287
+ return "bool"
2288
+ if fname == "abs":
2289
+ return self.infer_type(node.args[0])
2290
+ if fname in ("any", "all", "isinstance"):
2291
+ return "bool"
2292
+ if fname in ("bin", "hex", "oct", "chr", "repr", "input", "format"):
2293
+ return "str"
2294
+ if fname in ("ord", "hash"):
2295
+ return "int"
2296
+ if fname == "divmod":
2297
+ return "tuple"
2298
+ if fname in ("map", "filter", "sorted", "reversed", "enumerate", "zip", "range"):
2299
+ return "list"
2300
+ if fname == "round":
2301
+ return self.infer_type(node.args[0])
2302
+ if fname == "pow":
2303
+ return self.infer_type(node.args[0])
2304
+ if fname in ("ge_inline", "ge_raw"):
2305
+ # inline code — default to int, or use third arg as type hint
2306
+ if len(node.args) >= 3 and isinstance(node.args[2], ast.Name):
2307
+ return node.args[2].id
2308
+ return "int"
2309
+ # class constructor call
2310
+ if fname and fname in self.class_names:
2311
+ return fname
2312
+ # user-defined function call — look up return type
2313
+ if fname and fname in self.func_return_types:
2314
+ return self.func_return_types[fname]
2315
+ # method call: ClassName.method() — look up return type
2316
+ if (isinstance(node.func, ast.Attribute) and
2317
+ isinstance(node.func.value, ast.Name) and
2318
+ node.func.value.id in self.class_names):
2319
+ key = f"{node.func.value.id}.{node.func.attr}"
2320
+ if key in self.func_return_types:
2321
+ return self.func_return_types[key]
2322
+ if isinstance(node, ast.Set):
2323
+ return "set"
2324
+ if isinstance(node, ast.SetComp):
2325
+ return "set"
2326
+ if isinstance(node, ast.DictComp):
2327
+ return "dict"
2328
+ if isinstance(node, ast.UnaryOp):
2329
+ if isinstance(node.op, ast.USub):
2330
+ return self.infer_type(node.operand)
2331
+ if isinstance(node.op, ast.Not):
2332
+ return "bool"
2333
+ if isinstance(node, ast.Compare):
2334
+ return "bool"
2335
+ if isinstance(node, ast.BoolOp):
2336
+ return "bool"
2337
+ if isinstance(node, ast.IfExp):
2338
+ return self.infer_type(node.body)
2339
+ if isinstance(node, ast.Attribute):
2340
+ # field access — look up class field type
2341
+ base_type = self.infer_type(node.value)
2342
+ if base_type in self.class_fields:
2343
+ for fname, ftype in self.class_fields[base_type]:
2344
+ if fname == node.attr:
2345
+ return ftype
2346
+ return "int"
2347
+
2348
+ def binop_symbol(self, op: ast.AST) -> str:
2349
+ return {
2350
+ ast.Add: "+", ast.Sub: "-", ast.Mult: "*", ast.Mod: "%",
2351
+ ast.BitAnd: "&", ast.BitOr: "|", ast.BitXor: "^",
2352
+ ast.LShift: "<<", ast.RShift: ">>",
2353
+ }.get(type(op), "?")
2354
+
2355
+ def cmp_symbol(self, op: ast.AST) -> str:
2356
+ return {
2357
+ ast.Eq: "==", ast.NotEq: "!=", ast.Lt: "<", ast.LtE: "<=",
2358
+ ast.Gt: ">", ast.GtE: ">=", ast.Is: "==", ast.IsNot: "!=",
2359
+ }.get(type(op), "?")