gelang 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +65 -0
- package/LICENSE +21 -0
- package/README.md +535 -0
- package/bin/ge.js +112 -0
- package/package.json +62 -0
- package/python/pyeffic/__init__.py +9 -0
- package/python/pyeffic/__main__.py +6 -0
- package/python/pyeffic/analyzer.py +464 -0
- package/python/pyeffic/apisurface.py +238 -0
- package/python/pyeffic/autoselect.py +327 -0
- package/python/pyeffic/backends.py +87 -0
- package/python/pyeffic/bench.py +233 -0
- package/python/pyeffic/cli.py +184 -0
- package/python/pyeffic/compiler.py +421 -0
- package/python/pyeffic/config.py +383 -0
- package/python/pyeffic/dartgen.py +441 -0
- package/python/pyeffic/deploy.py +586 -0
- package/python/pyeffic/diagnostics.py +194 -0
- package/python/pyeffic/difftest.py +424 -0
- package/python/pyeffic/downloader.py +307 -0
- package/python/pyeffic/emitters/__init__.py +11 -0
- package/python/pyeffic/emitters/base.py +2359 -0
- package/python/pyeffic/emitters/cpp.py +266 -0
- package/python/pyeffic/emitters/csharp.py +342 -0
- package/python/pyeffic/emitters/dart.py +349 -0
- package/python/pyeffic/emitters/go.py +388 -0
- package/python/pyeffic/emitters/kotlin.py +314 -0
- package/python/pyeffic/emitters/rust.py +314 -0
- package/python/pyeffic/emitters/zig.py +411 -0
- package/python/pyeffic/ffi.py +49 -0
- package/python/pyeffic/frontends/__init__.py +94 -0
- package/python/pyeffic/frontends/hybrid.py +709 -0
- package/python/pyeffic/frontends/typescript.py +965 -0
- package/python/pyeffic/ge_cli.py +1148 -0
- package/python/pyeffic/golden.py +348 -0
- package/python/pyeffic/idents.py +206 -0
- package/python/pyeffic/modules.py +220 -0
- package/python/pyeffic/packer.py +222 -0
- package/python/pyeffic/pipeline.py +797 -0
- package/python/pyeffic/reactgen.py +966 -0
- package/python/pyeffic/researcher.py +177 -0
- package/python/pyeffic/scaffold.py +397 -0
- package/python/pyeffic/stdlib.py +246 -0
- package/python/pyeffic/styling.py +220 -0
- package/python/pyeffic/templates/desktop_gui/README.md +106 -0
- package/python/pyeffic/templates/desktop_gui/app/__init__.py +0 -0
- package/python/pyeffic/templates/desktop_gui/app/core/__init__.py +0 -0
- package/python/pyeffic/templates/desktop_gui/app/core/add.ge.py +13 -0
- package/python/pyeffic/templates/desktop_gui/app/core/factorial.ge.py +20 -0
- package/python/pyeffic/templates/desktop_gui/app/core/fibonacci.ge.py +25 -0
- package/python/pyeffic/templates/desktop_gui/app/core/gcd.ge.py +19 -0
- package/python/pyeffic/templates/desktop_gui/app/core/is_prime.ge.py +24 -0
- package/python/pyeffic/templates/desktop_gui/app/core/multiply.ge.py +13 -0
- package/python/pyeffic/templates/desktop_gui/app/core/power.ge.py +25 -0
- package/python/pyeffic/templates/desktop_gui/app/main.ge.py +49 -0
- package/python/pyeffic/templates/desktop_gui/app/memory/__init__.py +0 -0
- package/python/pyeffic/templates/desktop_gui/app/memory/buffer.ge.py +26 -0
- package/python/pyeffic/templates/desktop_gui/app/memory/limits.ge.py +47 -0
- package/python/pyeffic/templates/desktop_gui/app/memory/state.ge.py +44 -0
- package/python/pyeffic/templates/desktop_gui/app/ui/__init__.py +0 -0
- package/python/pyeffic/templates/desktop_gui/app/ui/layout.ge.py +64 -0
- package/python/pyeffic/templates/desktop_gui/app/ui/render.ge.py +87 -0
- package/python/pyeffic/templates/desktop_gui/app/ui/theme.ge.py +147 -0
- package/python/pyeffic/templates/desktop_gui/app/ui/widgets.ge.py +105 -0
- package/python/pyeffic/templates/desktop_gui/desktop/__init__.py +1 -0
- package/python/pyeffic/templates/desktop_gui/desktop/main.ge.py +258 -0
- package/python/pyeffic/templates/desktop_gui/ge.toml +16 -0
- package/python/pyeffic/templates/desktop_gui/tests/__init__.py +0 -0
- package/python/pyeffic/templates/desktop_gui/tests/ge_loader.py +76 -0
- package/python/pyeffic/templates/desktop_gui/tests/test_app.py +173 -0
- package/python/pyeffic/templates/web_react/README.md +115 -0
- package/python/pyeffic/templates/web_react/app/__init__.py +0 -0
- package/python/pyeffic/templates/web_react/app/core/__init__.py +0 -0
- package/python/pyeffic/templates/web_react/app/core/add.ge.py +9 -0
- package/python/pyeffic/templates/web_react/app/core/factorial.ge.py +16 -0
- package/python/pyeffic/templates/web_react/app/core/fibonacci.ge.py +21 -0
- package/python/pyeffic/templates/web_react/app/core/is_prime.ge.py +20 -0
- package/python/pyeffic/templates/web_react/app/core/multiply.ge.py +9 -0
- package/python/pyeffic/templates/web_react/app/main.ge.py +25 -0
- package/python/pyeffic/templates/web_react/app/memory/__init__.py +0 -0
- package/python/pyeffic/templates/web_react/app/memory/buffer.ge.py +25 -0
- package/python/pyeffic/templates/web_react/app/memory/limits.ge.py +51 -0
- package/python/pyeffic/templates/web_react/ge.toml +23 -0
- package/python/pyeffic/templates/web_react/tests/__init__.py +0 -0
- package/python/pyeffic/templates/web_react/tests/ge_loader.py +68 -0
- package/python/pyeffic/templates/web_react/tests/test_app.py +105 -0
- package/python/pyeffic/templates/web_react/ui/main.ge.ui +33 -0
- package/python/pyeffic/templates/web_react/web/__init__.py +0 -0
- package/python/pyeffic/templates/web_react/web/server.ge.py +78 -0
- package/python/pyeffic/ts2py.py +657 -0
- package/python/pyeffic/typecheck.py +232 -0
- package/python/pyeffic/ui.py +154 -0
- package/python/pyeffic/ui_dsl.py +618 -0
- package/python/pyeffic/widgets.py +87 -0
- package/scripts/README.md +42 -0
- package/scripts/check-toolchains.py +85 -0
|
@@ -0,0 +1,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), "?")
|