gelang 0.1.2 → 0.1.4
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 +53 -0
- package/package.json +1 -1
- package/python/pyeffic/analyzer.py +20 -0
- package/python/pyeffic/downloader.py +14 -2
- package/python/pyeffic/emitters/base.py +223 -17
- package/python/pyeffic/emitters/cpp.py +26 -3
- package/python/pyeffic/emitters/csharp.py +32 -3
- package/python/pyeffic/emitters/go.py +38 -6
- package/python/pyeffic/emitters/kotlin.py +29 -6
- package/python/pyeffic/emitters/rust.py +22 -3
- package/python/pyeffic/emitters/zig.py +33 -6
- package/python/pyeffic/stdlib.py +104 -0
- package/python/pyeffic/typecheck.py +16 -3
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,59 @@ All notable changes to GE. Format follows
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versioning follows
|
|
5
5
|
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.1.4] — 2026-09-13
|
|
8
|
+
|
|
9
|
+
Found by `scripts/e2e.py`, a new harness that compiles and runs 118
|
|
10
|
+
programs on every installed backend and compares stdout with CPython.
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **Wrong output.** `print(True)` printed lowercase `true` on Rust, C++,
|
|
15
|
+
Go, Zig and Kotlin; list printing used each target's native format
|
|
16
|
+
(`[1 2 3]` in Go, `{ 1, 2, 3 }` in Zig). Both now match Python.
|
|
17
|
+
- **Dict iteration** walked `(key, value)` pairs instead of keys, so
|
|
18
|
+
`for k in d: d[k]` failed to compile on every backend.
|
|
19
|
+
- **List slicing** was marked unsupported, which dropped the whole
|
|
20
|
+
function and surfaced as the misleading `main function not found`.
|
|
21
|
+
`xs[a:b]`, `xs[a:]`, `xs[:b]` and `xs[:]` are implemented everywhere.
|
|
22
|
+
- **`str.split` / `str.join`** had no implementation at all.
|
|
23
|
+
- **`str.replace`** had no implementation at all.
|
|
24
|
+
- **`min(a, b)` / `max(a, b)`** emitted a bare call no target provides.
|
|
25
|
+
- **`abs()`** on a literal was ambiguous in Rust, untyped in Zig, and
|
|
26
|
+
absent for `int64` in Go.
|
|
27
|
+
- **Default arguments** were rejected outright. A call may now supply
|
|
28
|
+
between `n_required` and `len(params)` arguments.
|
|
29
|
+
- **`set` annotations** became `f64`, because `set` was missing from the
|
|
30
|
+
native type mapping. Added `set_type` per backend.
|
|
31
|
+
- **Lists of strings** could not be expressed: only Rust and C++ had a
|
|
32
|
+
`{T}` placeholder in `list_type`.
|
|
33
|
+
- **C++ `std::sqrt`** appeared before `#include <cmath>`.
|
|
34
|
+
- **Kotlin** used `Math.pow` (no such class in Kotlin/Native) and a
|
|
35
|
+
C-style ternary (Kotlin has no `?:`).
|
|
36
|
+
- **C++ `upper()` / `lower()`** emitted a bare comment, so the program
|
|
37
|
+
compiled but did nothing.
|
|
38
|
+
- **Rust string concatenation** moved the left operand.
|
|
39
|
+
- **Emitter crashes**: when the entry function was rejected, zig, go,
|
|
40
|
+
kotlin and csharp raised `IndexError` instead of reporting the reason.
|
|
41
|
+
- **`ge tools check`** disagreed with `ge doctor` because `CompilerInfo`
|
|
42
|
+
field names (`dotnet`, `kotlinc`) differ from the public toolchain
|
|
43
|
+
names.
|
|
44
|
+
- **Zig** emitted a raw newline inside a string literal, and
|
|
45
|
+
`_mark_unsupported` produced a C-style comment, which Zig rejects.
|
|
46
|
+
|
|
47
|
+
### Added
|
|
48
|
+
|
|
49
|
+
- `scripts/e2e.py` — end-to-end tests against real toolchains.
|
|
50
|
+
- `ge doctor` — runtime and toolchain check with install hints.
|
|
51
|
+
|
|
52
|
+
### Known limitations
|
|
53
|
+
|
|
54
|
+
- Zig models lists as fixed slices, so `list.append` is rejected with a
|
|
55
|
+
clear diagnostic rather than compiling. Switching the backend to
|
|
56
|
+
`std.ArrayList` needs an allocator threaded through every signature.
|
|
57
|
+
- Tuples are supported at arity 2; other arities are rejected with a
|
|
58
|
+
diagnostic instead of emitting broken code.
|
|
59
|
+
|
|
7
60
|
## [Unreleased]
|
|
8
61
|
|
|
9
62
|
### Added
|
package/package.json
CHANGED
|
@@ -38,6 +38,11 @@ class FuncUnit:
|
|
|
38
38
|
param_elem_types: dict[str, str] = field(default_factory=dict)
|
|
39
39
|
# dict key/value types: {"d": ("str", "int")} means d is dict[str, int]
|
|
40
40
|
param_dict_types: dict[str, tuple[str, str]] = field(default_factory=dict)
|
|
41
|
+
# Number of leading parameters without a default. Calls may
|
|
42
|
+
# supply between n_required_params and len(params) arguments.
|
|
43
|
+
n_required_params: int = 0
|
|
44
|
+
# Default expressions per parameter name, as source text.
|
|
45
|
+
param_defaults: dict[str, str] = field(default_factory=dict)
|
|
41
46
|
|
|
42
47
|
def mark_unsupported(self, reason: str) -> None:
|
|
43
48
|
self.supported = False
|
|
@@ -310,6 +315,19 @@ def parse_source_full(source: str) -> tuple[list[FuncUnit], list[ClassUnit]]:
|
|
|
310
315
|
ret = _norm_type(node.returns)
|
|
311
316
|
if ret not in SCALAR_TYPES and ret not in CONTAINER_TYPES and ret != "None" and ret not in _class_names:
|
|
312
317
|
ret = "any"
|
|
318
|
+
# Default arguments: a call may supply between n_required and
|
|
319
|
+
# len(params) arguments; the rest are filled in at the call site.
|
|
320
|
+
_args = node.args.args
|
|
321
|
+
_n_defaults = len(node.args.defaults)
|
|
322
|
+
_n_required = len(_args) - _n_defaults
|
|
323
|
+
_param_defaults: dict[str, str] = {}
|
|
324
|
+
for _i, _a in enumerate(_args):
|
|
325
|
+
if _i >= _n_required:
|
|
326
|
+
_d = node.args.defaults[_i - _n_required]
|
|
327
|
+
try:
|
|
328
|
+
_param_defaults[_a.arg] = ast.unparse(_d)
|
|
329
|
+
except Exception:
|
|
330
|
+
_param_defaults[_a.arg] = ""
|
|
313
331
|
unit = FuncUnit(
|
|
314
332
|
name=node.name,
|
|
315
333
|
lineno=node.lineno,
|
|
@@ -319,6 +337,8 @@ def parse_source_full(source: str) -> tuple[list[FuncUnit], list[ClassUnit]]:
|
|
|
319
337
|
source="\n".join(src_lines[node.lineno - 1 : node.end_lineno]),
|
|
320
338
|
param_elem_types=param_elem_types,
|
|
321
339
|
param_dict_types=param_dict_types,
|
|
340
|
+
n_required_params=_n_required,
|
|
341
|
+
param_defaults=_param_defaults,
|
|
322
342
|
)
|
|
323
343
|
# parse @rust/@cpp/@dart/@csharp/@zig/@go decorators for per-function backend selection
|
|
324
344
|
for dec in node.decorator_list:
|
|
@@ -293,10 +293,22 @@ def get_tools_status() -> dict[str, dict]:
|
|
|
293
293
|
"""Get status of all known toolchains."""
|
|
294
294
|
from .config import detect_compilers
|
|
295
295
|
detected = detect_compilers()
|
|
296
|
+
# CompilerInfo field names differ from the public toolchain names
|
|
297
|
+
# (`dotnet` backs C#, `kotlinc` backs Kotlin), so map them explicitly.
|
|
298
|
+
fields = {
|
|
299
|
+
"rust": "rustc",
|
|
300
|
+
"cpp": "cpp",
|
|
301
|
+
"csharp": "dotnet",
|
|
302
|
+
"zig": "zig",
|
|
303
|
+
"go": "go",
|
|
304
|
+
"kotlin": "kotlinc",
|
|
305
|
+
"dart": "dart",
|
|
306
|
+
}
|
|
296
307
|
status = {}
|
|
297
308
|
for name in ["rust", "cpp", "csharp", "zig", "go", "kotlin", "dart"]:
|
|
298
|
-
|
|
299
|
-
|
|
309
|
+
detected_path = getattr(detected, fields.get(name, name), None)
|
|
310
|
+
available = is_toolchain_available(name) or detected_path is not None
|
|
311
|
+
path = get_toolchain_path(name) or detected_path
|
|
300
312
|
downloadable = name in DOWNLOAD_URLS
|
|
301
313
|
status[name] = {
|
|
302
314
|
"available": bool(available),
|
|
@@ -48,6 +48,31 @@ class Spec:
|
|
|
48
48
|
var_decl_template: str = "{nt} {target} = {val};" # typed variable declaration
|
|
49
49
|
ffi_prefix: str = "" # decoration for C-ABI exports in library mode
|
|
50
50
|
indent: str = " "
|
|
51
|
+
# Type-aware exponentiation. Integer `**` and float `**` need different
|
|
52
|
+
# target syntax in most languages; when these are empty the emitter falls
|
|
53
|
+
# back to pow_call.
|
|
54
|
+
pow_int: str = ""
|
|
55
|
+
pow_float: str = ""
|
|
56
|
+
# two-argument min(a, b) / max(a, b). Most targets have no plain
|
|
57
|
+
# min/max function, so these carry the target-specific spelling.
|
|
58
|
+
min2_call: str = ""
|
|
59
|
+
max2_call: str = ""
|
|
60
|
+
# List slicing. `{x}` is the list, `{start}` and `{stop}` are integer
|
|
61
|
+
# bounds. list_copy is the full-slice form (`xs[:]`), which Python
|
|
62
|
+
# defines as a shallow copy.
|
|
63
|
+
# str.split(sep) / sep.join(list). Empty means unsupported.
|
|
64
|
+
str_split: str = ""
|
|
65
|
+
str_join: str = ""
|
|
66
|
+
list_slice: str = ""
|
|
67
|
+
list_copy: str = ""
|
|
68
|
+
# Iterating a dict in Python yields its keys. `dict_keys` is an
|
|
69
|
+
# expression producing those keys; `foreach_dict_template` is the
|
|
70
|
+
# loop head to use with it (Go ranges a map differently).
|
|
71
|
+
dict_keys: str = ""
|
|
72
|
+
foreach_dict_template: str = ""
|
|
73
|
+
# Printing a sequence. Python renders lists as `[1, 2, 3]`, so every
|
|
74
|
+
# backend must match that rather than its own native format.
|
|
75
|
+
print_list: str = ""
|
|
51
76
|
# string operations
|
|
52
77
|
str_concat: str = "{l} + {r}" # string concatenation
|
|
53
78
|
str_len: str = "{x}.length()" # string length (C++ style)
|
|
@@ -74,6 +99,7 @@ class Spec:
|
|
|
74
99
|
dict_contains: str = "({d}.find({k}) != {d}.end())"
|
|
75
100
|
# tuple support
|
|
76
101
|
tuple_type: str = "std::tuple<{T}>"
|
|
102
|
+
set_type: str = "" # native set type
|
|
77
103
|
tuple_get: str = "std::get<{i}>({t})"
|
|
78
104
|
# list length (separate from len_call which may be used for strings)
|
|
79
105
|
list_len: str = "{x}.size()"
|
|
@@ -90,6 +116,9 @@ class Emitter:
|
|
|
90
116
|
self.params: set[str] = set() # param names (already borrowed if list)
|
|
91
117
|
self.library_mode: bool = False # emit C-ABI exports instead of a main()
|
|
92
118
|
self.extern_names: set[str] = set() # extern "C" fns from other backends
|
|
119
|
+
# {name: ([param names], {param: default source text})} so a call site
|
|
120
|
+
# that omits defaults can fill them in.
|
|
121
|
+
self.func_signatures: dict[str, tuple[list[str], dict[str, str]]] = {}
|
|
93
122
|
self.force_extern_c: bool = False # force extern "C" on all functions (multi-backend)
|
|
94
123
|
self.export_names: set[str] = set() # Rust fns called from C++ — export them
|
|
95
124
|
self.class_names: set[str] = set() # known class names for constructor calls
|
|
@@ -179,6 +208,10 @@ class Emitter:
|
|
|
179
208
|
return self.spec.dict_type.format(V=self.spec.list_elem_type)
|
|
180
209
|
if t == "tuple":
|
|
181
210
|
return self.spec.tuple_type.format(T=self.spec.list_elem_type)
|
|
211
|
+
if t == "set":
|
|
212
|
+
if self.spec.set_type:
|
|
213
|
+
return self.spec.set_type
|
|
214
|
+
return self.spec.list_type.format(T=self.spec.list_elem_type)
|
|
182
215
|
# class type — use the class name directly
|
|
183
216
|
if t in self.class_names:
|
|
184
217
|
return t
|
|
@@ -220,6 +253,12 @@ class Emitter:
|
|
|
220
253
|
return self.spec.dict_type.format(V=self.spec.list_elem_type)
|
|
221
254
|
if t == "tuple":
|
|
222
255
|
return self.spec.tuple_type.format(T=self.spec.list_elem_type)
|
|
256
|
+
if t == "set":
|
|
257
|
+
if self.spec.set_type:
|
|
258
|
+
if self.spec.name == "cpp":
|
|
259
|
+
return f"const {self.spec.set_type}&"
|
|
260
|
+
return self.spec.set_type
|
|
261
|
+
return self.spec.list_param_type
|
|
223
262
|
# class type — pass by value (or const ref in C++)
|
|
224
263
|
if t in self.class_names:
|
|
225
264
|
if self.spec.name == "cpp":
|
|
@@ -334,6 +373,12 @@ class Emitter:
|
|
|
334
373
|
self.var_types[target] = t
|
|
335
374
|
self.declared.add(target)
|
|
336
375
|
nt = self.py_to_native(t)
|
|
376
|
+
if t == "list":
|
|
377
|
+
# a bare `list` annotation carries no element type; take it
|
|
378
|
+
# from the value so `xs = "a,b".split(",")` is a string list
|
|
379
|
+
elem = self._list_elem_of_value(node.value)
|
|
380
|
+
if elem and elem != self.spec.list_elem_type:
|
|
381
|
+
nt = self.spec.list_type.format(T=elem)
|
|
337
382
|
if self.spec.name == "rust":
|
|
338
383
|
self.lines.append(f"{ind}let mut {target}: {nt} = {self.expr(node.value)};")
|
|
339
384
|
else:
|
|
@@ -754,7 +799,12 @@ class Emitter:
|
|
|
754
799
|
msg = f"{self.current_func}: {feature}"
|
|
755
800
|
if msg not in self.unsupported_emissions:
|
|
756
801
|
self.unsupported_emissions.append(msg)
|
|
757
|
-
|
|
802
|
+
# Use the backend's own comment syntax: a C-style comment is not
|
|
803
|
+
# valid in Zig, and the placeholder still has to parse.
|
|
804
|
+
marker = getattr(self.spec, "comment", "//") or "//"
|
|
805
|
+
if marker.startswith("/*"):
|
|
806
|
+
return f"/*unsupported: {feature}*/"
|
|
807
|
+
return f"{marker} unsupported: {feature}"
|
|
758
808
|
|
|
759
809
|
def _ident(self, name: str) -> str:
|
|
760
810
|
"""Escape a local/parameter name that collides with a target keyword.
|
|
@@ -858,12 +908,21 @@ class Emitter:
|
|
|
858
908
|
self._for_zip(node, ind)
|
|
859
909
|
return
|
|
860
910
|
else:
|
|
861
|
-
# iterate a container
|
|
911
|
+
# iterate a container
|
|
862
912
|
iter_s = self.expr(it)
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
913
|
+
if self.infer_type(it) == "dict" and self.spec.dict_keys:
|
|
914
|
+
# Python iterates dict keys, not (key, value) pairs
|
|
915
|
+
iter_s = self.spec.dict_keys.format(x=iter_s)
|
|
916
|
+
tmpl = (self.spec.foreach_dict_template
|
|
917
|
+
or self.spec.foreach_template)
|
|
918
|
+
self.var_types[var] = "str"
|
|
919
|
+
head = tmpl.format(var=var, iter=iter_s,
|
|
920
|
+
etype=self.spec.list_elem_type)
|
|
921
|
+
else:
|
|
922
|
+
elem_type = self._iter_elem_type(it)
|
|
923
|
+
self.var_types[var] = elem_type
|
|
924
|
+
head = self.spec.foreach_template.format(
|
|
925
|
+
var=var, iter=iter_s, etype=self.spec.list_elem_type)
|
|
867
926
|
self.lines.append(f"{ind}{head} {{")
|
|
868
927
|
self.indent_lvl += 1
|
|
869
928
|
for s in node.body:
|
|
@@ -991,7 +1050,7 @@ class Emitter:
|
|
|
991
1050
|
if isinstance(node.op, ast.FloorDiv):
|
|
992
1051
|
return self.spec.floor_div.format(l=left, r=right)
|
|
993
1052
|
if isinstance(node.op, ast.Pow):
|
|
994
|
-
return self.
|
|
1053
|
+
return self._pow(left, right, node)
|
|
995
1054
|
if isinstance(node.op, ast.Add):
|
|
996
1055
|
lt = self.infer_type(node.left)
|
|
997
1056
|
rt = self.infer_type(node.right)
|
|
@@ -1079,8 +1138,11 @@ class Emitter:
|
|
|
1079
1138
|
orelse = self.expr(node.orelse)
|
|
1080
1139
|
if self.spec.name == "rust":
|
|
1081
1140
|
return f"if {cond} {{ {body} }} else {{ {orelse} }}"
|
|
1082
|
-
if self.spec.name in ("cpp", "csharp"
|
|
1141
|
+
if self.spec.name in ("cpp", "csharp"):
|
|
1083
1142
|
return f"({cond} ? {body} : {orelse})"
|
|
1143
|
+
if self.spec.name == "kotlin":
|
|
1144
|
+
# Kotlin has no ?: operator — it uses an if expression
|
|
1145
|
+
return f"(if ({cond}) {body} else {orelse})"
|
|
1084
1146
|
if self.spec.name == "go":
|
|
1085
1147
|
return f"(func() int64 {{ if {cond} {{ return {body} }}; return {orelse} }}())"
|
|
1086
1148
|
if self.spec.name == "zig":
|
|
@@ -1104,9 +1166,18 @@ class Emitter:
|
|
|
1104
1166
|
if stop:
|
|
1105
1167
|
return self.spec.str_slice_end.format(x=base, end=stop)
|
|
1106
1168
|
return base # full slice s[:] = s
|
|
1107
|
-
# list slicing —
|
|
1108
|
-
if
|
|
1109
|
-
|
|
1169
|
+
# list slicing — xs[a:b], xs[a:], xs[:b], xs[:]
|
|
1170
|
+
if self.spec.list_slice:
|
|
1171
|
+
length = self.spec.len_call.format(x=base)
|
|
1172
|
+
if not start:
|
|
1173
|
+
start = self.spec.int_cast.format(x="0")
|
|
1174
|
+
if not stop:
|
|
1175
|
+
stop = length
|
|
1176
|
+
if not sl.lower and not sl.upper:
|
|
1177
|
+
if self.spec.list_copy:
|
|
1178
|
+
return self.spec.list_copy.format(x=base)
|
|
1179
|
+
return self.spec.list_slice.format(
|
|
1180
|
+
x=base, start=start, stop=stop)
|
|
1110
1181
|
return self._mark_unsupported(f"list slice {base}")
|
|
1111
1182
|
idx = self.expr(node.slice)
|
|
1112
1183
|
if base_type == "dict":
|
|
@@ -1135,17 +1206,22 @@ class Emitter:
|
|
|
1135
1206
|
return self.spec.index_call.format(x=base, i=idx)
|
|
1136
1207
|
if isinstance(node, ast.List):
|
|
1137
1208
|
elems = ", ".join(self.expr(e) for e in node.elts)
|
|
1209
|
+
# a literal of strings needs a string element type, not the
|
|
1210
|
+
# backend's default integer element
|
|
1211
|
+
elem = self._list_elem_of_value(node) or self.spec.list_elem_type
|
|
1138
1212
|
if self.spec.name == "rust":
|
|
1139
1213
|
return f"vec![{elems}]"
|
|
1140
1214
|
if self.spec.name == "go":
|
|
1141
|
-
return f"[]{
|
|
1215
|
+
return f"[]{elem}{{{elems}}}"
|
|
1142
1216
|
if self.spec.name == "kotlin":
|
|
1143
1217
|
return f"mutableListOf({elems})"
|
|
1144
1218
|
if self.spec.name == "zig":
|
|
1145
1219
|
return f"&[_]i64{{ {elems} }}"
|
|
1146
1220
|
if self.spec.name == "csharp":
|
|
1147
|
-
|
|
1148
|
-
|
|
1221
|
+
lt = self.spec.list_type.format(T=elem)
|
|
1222
|
+
return f"new {lt}{{{elems}}}"
|
|
1223
|
+
lt = self.spec.list_type.format(T=elem)
|
|
1224
|
+
return f"{lt}{{{elems}}}"
|
|
1149
1225
|
if isinstance(node, ast.ListComp):
|
|
1150
1226
|
return self._list_comp(node)
|
|
1151
1227
|
if isinstance(node, ast.Dict):
|
|
@@ -1186,15 +1262,36 @@ class Emitter:
|
|
|
1186
1262
|
if self.spec.name == "csharp":
|
|
1187
1263
|
return f"new HashSet<long>{{{elems}}}"
|
|
1188
1264
|
if self.spec.name == "go":
|
|
1189
|
-
|
|
1265
|
+
# Go has no set type; GE uses map[elem]bool. Go rejects
|
|
1266
|
+
# duplicate *constant* keys at compile time, so literals are
|
|
1267
|
+
# deduplicated here (a runtime set would collapse them anyway).
|
|
1268
|
+
seen: list[str] = []
|
|
1269
|
+
for e in node.elts:
|
|
1270
|
+
rendered = self.expr(e)
|
|
1271
|
+
if rendered not in seen:
|
|
1272
|
+
seen.append(rendered)
|
|
1273
|
+
if not seen:
|
|
1274
|
+
return "map[int64]bool{}"
|
|
1275
|
+
pairs = ", ".join(f"{v}: true" for v in seen)
|
|
1276
|
+
return f"map[int64]bool{{{pairs}}}"
|
|
1190
1277
|
if self.spec.name == "kotlin":
|
|
1191
1278
|
return f"hashSetOf({elems})"
|
|
1279
|
+
if self.spec.name == "zig":
|
|
1280
|
+
# no allocator-backed set in the Zig runtime
|
|
1281
|
+
return self._mark_unsupported("set literal (Zig)")
|
|
1192
1282
|
return f"std::set<int64_t>{{{elems}}}"
|
|
1193
1283
|
if isinstance(node, ast.SetComp):
|
|
1194
1284
|
return self._set_comp(node)
|
|
1195
1285
|
if isinstance(node, ast.DictComp):
|
|
1196
1286
|
return self._dict_comp(node)
|
|
1197
1287
|
if isinstance(node, ast.Tuple):
|
|
1288
|
+
# GE models tuples as fixed-width pairs. Anything else would need
|
|
1289
|
+
# a per-arity native type (Go structs, Kotlin Triple, ...), so it
|
|
1290
|
+
# is rejected loudly rather than emitted as broken code.
|
|
1291
|
+
if len(node.elts) != 2:
|
|
1292
|
+
return self._mark_unsupported(
|
|
1293
|
+
f"tuple of {len(node.elts)} elements "
|
|
1294
|
+
f"(only 2-element tuples are supported)")
|
|
1198
1295
|
elems = ", ".join(self.expr(e) for e in node.elts)
|
|
1199
1296
|
if self.spec.name == "rust":
|
|
1200
1297
|
return f"({elems})"
|
|
@@ -1273,10 +1370,16 @@ class Emitter:
|
|
|
1273
1370
|
if fname == "sum":
|
|
1274
1371
|
return self.spec.sum_call.format(it=self.expr(node.args[0]))
|
|
1275
1372
|
if fname in ("min", "max"):
|
|
1276
|
-
|
|
1373
|
+
is_min = fname == "min"
|
|
1374
|
+
tmpl = self.spec.min_call if is_min else self.spec.max_call
|
|
1277
1375
|
raw = [self.expr(a) for a in node.args]
|
|
1278
1376
|
if len(raw) == 1:
|
|
1279
1377
|
return tmpl.format(it=raw[0])
|
|
1378
|
+
if len(raw) == 2:
|
|
1379
|
+
pair = (self.spec.min2_call if is_min
|
|
1380
|
+
else self.spec.max2_call)
|
|
1381
|
+
if pair:
|
|
1382
|
+
return pair.format(a=raw[0], b=raw[1])
|
|
1280
1383
|
return f"{fname}({', '.join(raw)})"
|
|
1281
1384
|
if fname == "pow":
|
|
1282
1385
|
return self.spec.pow_call.format(l=self.expr(node.args[0]), r=self.expr(node.args[1]))
|
|
@@ -1584,8 +1687,26 @@ class Emitter:
|
|
|
1584
1687
|
return self._stdlib_call("ge_log", node.args)
|
|
1585
1688
|
if fname == "exp":
|
|
1586
1689
|
return self._stdlib_call("ge_exp", node.args)
|
|
1690
|
+
if (isinstance(node.func, ast.Attribute) and node.func.attr == "split"
|
|
1691
|
+
and len(node.args) == 1):
|
|
1692
|
+
if not self.spec.str_split:
|
|
1693
|
+
return self._mark_unsupported(
|
|
1694
|
+
f"str.split (not available on the {self.spec.name} backend)")
|
|
1695
|
+
return self.spec.str_split.format(
|
|
1696
|
+
x=self.expr(node.func.value), sep=self.expr(node.args[0]))
|
|
1697
|
+
if (isinstance(node.func, ast.Attribute) and node.func.attr == "join"
|
|
1698
|
+
and len(node.args) == 1):
|
|
1699
|
+
if not self.spec.str_join:
|
|
1700
|
+
return self._mark_unsupported(
|
|
1701
|
+
f"str.join (not available on the {self.spec.name} backend)")
|
|
1702
|
+
return self.spec.str_join.format(
|
|
1703
|
+
x=self.expr(node.args[0]), sep=self.expr(node.func.value))
|
|
1587
1704
|
if isinstance(node.func, ast.Attribute) and node.func.attr == "append":
|
|
1588
1705
|
base = self.expr(node.func.value)
|
|
1706
|
+
if not self.spec.append_call:
|
|
1707
|
+
return self._mark_unsupported(
|
|
1708
|
+
f"list.append (the {self.spec.name} backend models lists "
|
|
1709
|
+
f"as fixed slices)")
|
|
1589
1710
|
return self.spec.append_call.format(x=base, v=self.expr(node.args[0]))
|
|
1590
1711
|
# super().method(args) -> ParentClass_method(_self, args)
|
|
1591
1712
|
if (isinstance(node.func, ast.Attribute) and
|
|
@@ -1620,7 +1741,7 @@ class Emitter:
|
|
|
1620
1741
|
if self.spec.name == "rust":
|
|
1621
1742
|
return f"{base}.to_uppercase()"
|
|
1622
1743
|
if self.spec.name == "cpp":
|
|
1623
|
-
return f"
|
|
1744
|
+
return f"geStrUpper({base})"
|
|
1624
1745
|
if self.spec.name == "csharp":
|
|
1625
1746
|
return f"{base}.ToUpper()"
|
|
1626
1747
|
if self.spec.name == "go":
|
|
@@ -1631,6 +1752,8 @@ class Emitter:
|
|
|
1631
1752
|
if method == "lower":
|
|
1632
1753
|
if self.spec.name == "rust":
|
|
1633
1754
|
return f"{base}.to_lowercase()"
|
|
1755
|
+
if self.spec.name == "cpp":
|
|
1756
|
+
return f"geStrLower({base})"
|
|
1634
1757
|
if self.spec.name == "csharp":
|
|
1635
1758
|
return f"{base}.ToLower()"
|
|
1636
1759
|
if self.spec.name == "go":
|
|
@@ -1638,6 +1761,19 @@ class Emitter:
|
|
|
1638
1761
|
if self.spec.name == "kotlin":
|
|
1639
1762
|
return f"{base}.lowercase()"
|
|
1640
1763
|
return f"{base}.lower()"
|
|
1764
|
+
if method == "replace" and len(node.args) == 2:
|
|
1765
|
+
a = self.expr(node.args[0])
|
|
1766
|
+
b = self.expr(node.args[1])
|
|
1767
|
+
if self.spec.name == "rust":
|
|
1768
|
+
return f"{base}.replace({a}.as_str(), {b}.as_str())"
|
|
1769
|
+
if self.spec.name == "cpp":
|
|
1770
|
+
return f"geStrReplace({base}, {a}, {b})"
|
|
1771
|
+
if self.spec.name == "csharp":
|
|
1772
|
+
return f"{base}.Replace({a}, {b})"
|
|
1773
|
+
if self.spec.name == "go":
|
|
1774
|
+
return f"strings.ReplaceAll({base}, {a}, {b})"
|
|
1775
|
+
if self.spec.name == "kotlin":
|
|
1776
|
+
return f"{base}.replace({a}, {b})"
|
|
1641
1777
|
if method == "strip":
|
|
1642
1778
|
if self.spec.name == "rust":
|
|
1643
1779
|
return f"{base}.trim().to_string()"
|
|
@@ -1674,12 +1810,36 @@ class Emitter:
|
|
|
1674
1810
|
# **kwargs — not supported, skip
|
|
1675
1811
|
continue
|
|
1676
1812
|
args.append(self.expr(kw.value))
|
|
1813
|
+
args = self._fill_defaults(fname, args, node)
|
|
1677
1814
|
call_str = f"{fname}({', '.join(args)})"
|
|
1678
1815
|
# wrap extern "C" calls in unsafe block (Rust requires this)
|
|
1679
1816
|
if fname in self.extern_names and self.spec.name == "rust":
|
|
1680
1817
|
return f"unsafe {{ {call_str} }}"
|
|
1681
1818
|
return call_str
|
|
1682
1819
|
|
|
1820
|
+
def _fill_defaults(self, fname: str, args: list[str],
|
|
1821
|
+
node: ast.Call) -> list[str]:
|
|
1822
|
+
"""Append default arguments a call site omitted.
|
|
1823
|
+
|
|
1824
|
+
`def f(a, b=10)` called as `f(5)` must emit `f(5, 10)` in the target
|
|
1825
|
+
language, which has no notion of Python default parameters.
|
|
1826
|
+
"""
|
|
1827
|
+
if node.keywords:
|
|
1828
|
+
return args
|
|
1829
|
+
sig = self.func_signatures.get(fname)
|
|
1830
|
+
if not sig:
|
|
1831
|
+
return args
|
|
1832
|
+
names, defaults = sig
|
|
1833
|
+
if len(args) >= len(names):
|
|
1834
|
+
return args
|
|
1835
|
+
filled = list(args)
|
|
1836
|
+
for name in names[len(filled):]:
|
|
1837
|
+
d = defaults.get(name)
|
|
1838
|
+
if d is None or d == "":
|
|
1839
|
+
break
|
|
1840
|
+
filled.append(d)
|
|
1841
|
+
return filled
|
|
1842
|
+
|
|
1683
1843
|
def _str_call(self, arg: ast.AST) -> str:
|
|
1684
1844
|
"""Convert a value to string."""
|
|
1685
1845
|
t = self.infer_type(arg)
|
|
@@ -2201,6 +2361,8 @@ class Emitter:
|
|
|
2201
2361
|
return self.spec.print_float.format(v=v)
|
|
2202
2362
|
if t == "bool":
|
|
2203
2363
|
return self.spec.print_bool.format(v=v)
|
|
2364
|
+
if t in ("list", "tuple", "set") and self.spec.print_list:
|
|
2365
|
+
return self.spec.print_list.format(v=v)
|
|
2204
2366
|
# fallback to heuristics on the source text
|
|
2205
2367
|
if any(c in v for c in ".") and not v.startswith('"'):
|
|
2206
2368
|
return self.spec.print_float.format(v=v)
|
|
@@ -2226,6 +2388,50 @@ class Emitter:
|
|
|
2226
2388
|
return base if base in ("list", "dict", "tuple", "set") else "list"
|
|
2227
2389
|
return "int"
|
|
2228
2390
|
|
|
2391
|
+
def _list_elem_of_value(self, node: ast.AST) -> str:
|
|
2392
|
+
"""Native element type of a list-producing expression, or "".
|
|
2393
|
+
|
|
2394
|
+
A bare `list` annotation says nothing about the element type, so it is
|
|
2395
|
+
taken from the value: `"a,b".split(",")` yields strings, not ints.
|
|
2396
|
+
"""
|
|
2397
|
+
if (isinstance(node, ast.Call)
|
|
2398
|
+
and isinstance(node.func, ast.Attribute)):
|
|
2399
|
+
attr = node.func.attr
|
|
2400
|
+
if attr in ("split", "splitlines", "keys"):
|
|
2401
|
+
return self.spec.types.get("str") or self._native_elem_type("str")
|
|
2402
|
+
if attr == "values":
|
|
2403
|
+
return self.spec.list_elem_type
|
|
2404
|
+
if attr in ("copy", "sorted", "reverse"):
|
|
2405
|
+
return self._list_elem_of_value(node.func.value) or ""
|
|
2406
|
+
if attr == "split" or attr == "items":
|
|
2407
|
+
return self.spec.types.get("str", "String")
|
|
2408
|
+
if isinstance(node, ast.List):
|
|
2409
|
+
types = [self.infer_type(e) for e in node.elts]
|
|
2410
|
+
types = [t for t in types if t]
|
|
2411
|
+
if types and all(t == types[0] for t in types):
|
|
2412
|
+
return self._native_elem_type(types[0])
|
|
2413
|
+
if isinstance(node, ast.ListComp):
|
|
2414
|
+
return self._native_elem_type(self.infer_type(node.elt))
|
|
2415
|
+
if isinstance(node, ast.Subscript):
|
|
2416
|
+
return self._list_elem_of_value(node.value)
|
|
2417
|
+
return ""
|
|
2418
|
+
|
|
2419
|
+
def _pow(self, left: str, right: str, node: ast.AST) -> str:
|
|
2420
|
+
"""Render `a ** b`.
|
|
2421
|
+
|
|
2422
|
+
Integer and float exponentiation need different target syntax, and a
|
|
2423
|
+
bare literal like `2 ** 10` is ambiguous in Rust, so the operand type
|
|
2424
|
+
decides which template is used.
|
|
2425
|
+
"""
|
|
2426
|
+
lt = self.infer_type(node.left)
|
|
2427
|
+
rt = self.infer_type(node.right)
|
|
2428
|
+
is_float = lt == "float" or rt == "float"
|
|
2429
|
+
if is_float and self.spec.pow_float:
|
|
2430
|
+
return self.spec.pow_float.format(l=left, r=right)
|
|
2431
|
+
if not is_float and self.spec.pow_int:
|
|
2432
|
+
return self.spec.pow_int.format(l=left, r=right)
|
|
2433
|
+
return self.spec.pow_call.format(l=left, r=right)
|
|
2434
|
+
|
|
2229
2435
|
def infer_type(self, node: ast.AST) -> str:
|
|
2230
2436
|
if isinstance(node, ast.Constant):
|
|
2231
2437
|
if isinstance(node.value, bool):
|
|
@@ -12,6 +12,10 @@ SPEC = Spec(
|
|
|
12
12
|
list_type="std::vector<{T}>",
|
|
13
13
|
list_param_type="std::vector<int64_t>&",
|
|
14
14
|
list_elem_type="int64_t",
|
|
15
|
+
str_split="geStrSplit({x}, {sep})",
|
|
16
|
+
str_join="geStrJoin({x}, {sep})",
|
|
17
|
+
list_slice="std::vector<int64_t>({x}.begin() + ({start}), {x}.begin() + ({stop}))",
|
|
18
|
+
list_copy="std::vector<int64_t>({x})",
|
|
15
19
|
borrow_list_arg=False,
|
|
16
20
|
range_call="for_range({lo}, {hi})",
|
|
17
21
|
range_step_call="for_range({lo}, {hi}, {step})",
|
|
@@ -20,8 +24,9 @@ SPEC = Spec(
|
|
|
20
24
|
print_int='std::cout << ({v}) << std::endl',
|
|
21
25
|
print_float='std::cout << ({v}) << std::endl',
|
|
22
26
|
print_str='std::cout << ({v}) << std::endl',
|
|
23
|
-
print_bool='std::cout << ({v}) << std::endl',
|
|
27
|
+
print_bool='std::cout << (({v}) ? "True" : "False") << std::endl',
|
|
24
28
|
print_generic='std::cout << ({v}) << std::endl',
|
|
29
|
+
print_list='std::cout << geListStr({v}) << std::endl',
|
|
25
30
|
int_cast="static_cast<int64_t>({x})",
|
|
26
31
|
float_cast="static_cast<double>({x})",
|
|
27
32
|
float_div="(static_cast<double>({l}) / static_cast<double>({r}))",
|
|
@@ -29,9 +34,13 @@ SPEC = Spec(
|
|
|
29
34
|
sum_call="std::accumulate({it}.begin(), {it}.end(), 0LL)",
|
|
30
35
|
abs_int="std::abs({x})",
|
|
31
36
|
abs_float="std::fabs({x})",
|
|
37
|
+
min2_call="std::min({a}, {b})",
|
|
38
|
+
max2_call="std::max({a}, {b})",
|
|
32
39
|
min_call="*std::min_element({it}.begin(), {it}.end())",
|
|
33
40
|
max_call="*std::max_element({it}.begin(), {it}.end())",
|
|
34
41
|
pow_call="std::pow({l}, {r})",
|
|
42
|
+
pow_int="static_cast<int64_t>(std::pow(static_cast<double>({l}), static_cast<double>({r})))",
|
|
43
|
+
pow_float="std::pow({l}, {r})",
|
|
35
44
|
append_call="{x}.push_back({v})",
|
|
36
45
|
index_call="{x}[{i}]",
|
|
37
46
|
comment="//",
|
|
@@ -51,8 +60,10 @@ SPEC = Spec(
|
|
|
51
60
|
struct_field_template=" {type} {name};",
|
|
52
61
|
struct_new_template="{name} {name}_new({params}) {{\n{body}\n}}",
|
|
53
62
|
dict_type="std::map<std::string, {V}>",
|
|
63
|
+
set_type="std::set<int64_t>",
|
|
54
64
|
dict_get="{d}.at({k})",
|
|
55
65
|
dict_set="{d}[{k}] = {v}",
|
|
66
|
+
dict_keys="geDictKeys({x})",
|
|
56
67
|
dict_contains="({d}.find({k}) != {d}.end())",
|
|
57
68
|
tuple_type="std::tuple<{T}, {T}>",
|
|
58
69
|
tuple_get="std::get<{i}>({t})",
|
|
@@ -80,8 +91,13 @@ class CppEmitter(Emitter):
|
|
|
80
91
|
)
|
|
81
92
|
else:
|
|
82
93
|
iter_s = self.expr(it)
|
|
83
|
-
self.
|
|
84
|
-
|
|
94
|
+
if self.infer_type(it) == "dict":
|
|
95
|
+
self.var_types[var] = "str"
|
|
96
|
+
self.lines.append(
|
|
97
|
+
f"{ind}for (auto {var} : geDictKeys({iter_s})) {{")
|
|
98
|
+
else:
|
|
99
|
+
self.var_types[var] = "int"
|
|
100
|
+
self.lines.append(f"{ind}for (auto& {var} : {iter_s}) {{")
|
|
85
101
|
self.indent_lvl += 1
|
|
86
102
|
for s in node.body:
|
|
87
103
|
self.stmt(s)
|
|
@@ -135,6 +151,11 @@ def emit_cpp(units: list[FuncUnit], entry: str | None,
|
|
|
135
151
|
constants: dict | None = None,
|
|
136
152
|
preamble: dict | None = None) -> tuple[str, dict[str, str]]:
|
|
137
153
|
emitter = CppEmitter(SPEC)
|
|
154
|
+
emitter.func_signatures = {
|
|
155
|
+
u.name: ([p for p, _t in u.params],
|
|
156
|
+
dict(getattr(u, 'param_defaults', {})))
|
|
157
|
+
for u in units
|
|
158
|
+
}
|
|
138
159
|
emitter.library_mode = library_mode
|
|
139
160
|
emitter.constants = constants or {}
|
|
140
161
|
# in multi-backend mode, force extern "C" on all functions so Rust can link
|
|
@@ -160,6 +181,8 @@ def emit_cpp(units: list[FuncUnit], entry: str | None,
|
|
|
160
181
|
"#include <cmath>\n"
|
|
161
182
|
"#include <numeric>\n"
|
|
162
183
|
"#include <algorithm>\n"
|
|
184
|
+
"#include <set>\n"
|
|
185
|
+
"#include <cctype>\n"
|
|
163
186
|
"#include <string>\n\n"
|
|
164
187
|
"using std::cout;\n"
|
|
165
188
|
"using std::endl;\n\n"
|
|
@@ -14,9 +14,13 @@ from ..analyzer import FuncUnit
|
|
|
14
14
|
SPEC = Spec(
|
|
15
15
|
name="csharp",
|
|
16
16
|
types={"int": "long", "float": "double", "bool": "bool", "str": "string", "None": "void"},
|
|
17
|
-
list_type="List<
|
|
17
|
+
list_type="List<{T}>",
|
|
18
18
|
list_param_type="List<long>",
|
|
19
19
|
list_elem_type="long",
|
|
20
|
+
str_split="{x}.Split({sep}).ToList()",
|
|
21
|
+
str_join="string.Join({sep}, {x})",
|
|
22
|
+
list_slice="{x}.GetRange((int)({start}), (int)(({stop}) - ({start})))",
|
|
23
|
+
list_copy="new List<long>({x})",
|
|
20
24
|
borrow_list_arg=False,
|
|
21
25
|
range_call="({lo}..{hi})",
|
|
22
26
|
range_step_call="({lo}..{hi}).Step({step})",
|
|
@@ -27,6 +31,7 @@ SPEC = Spec(
|
|
|
27
31
|
print_str='Console.WriteLine({v})',
|
|
28
32
|
print_bool='Console.WriteLine({v})',
|
|
29
33
|
print_generic='Console.WriteLine({v})',
|
|
34
|
+
print_list='Console.WriteLine("[" + string.Join(", ", {v}) + "]")',
|
|
30
35
|
int_cast="(long)({x})",
|
|
31
36
|
float_cast="(double)({x})",
|
|
32
37
|
float_div="((double)({l}) / (double)({r}))",
|
|
@@ -34,9 +39,13 @@ SPEC = Spec(
|
|
|
34
39
|
sum_call="{it}.Sum()",
|
|
35
40
|
abs_int="Math.Abs({x})",
|
|
36
41
|
abs_float="Math.Abs({x})",
|
|
42
|
+
min2_call="Math.Min({a}, {b})",
|
|
43
|
+
max2_call="Math.Max({a}, {b})",
|
|
37
44
|
min_call="{it}.Min()",
|
|
38
45
|
max_call="{it}.Max()",
|
|
39
46
|
pow_call="Math.Pow({l}, {r})",
|
|
47
|
+
pow_int="(long)Math.Pow({l}, {r})",
|
|
48
|
+
pow_float="Math.Pow({l}, {r})",
|
|
40
49
|
append_call="{x}.Add({v})",
|
|
41
50
|
index_call="{x}[(int)({i})]",
|
|
42
51
|
comment="//",
|
|
@@ -57,8 +66,10 @@ SPEC = Spec(
|
|
|
57
66
|
struct_field_template=" public {type} {name};",
|
|
58
67
|
struct_new_template="static {name} {name}_New({params}) {{\n{body}\n}}",
|
|
59
68
|
dict_type="Dictionary<string, {V}>",
|
|
69
|
+
set_type="HashSet<long>",
|
|
60
70
|
dict_get="{d}[{k}]",
|
|
61
71
|
dict_set="{d}[{k}] = {v}",
|
|
72
|
+
dict_keys="{x}.Keys",
|
|
62
73
|
dict_contains="{d}.ContainsKey({k})",
|
|
63
74
|
tuple_type="({T}, {T})",
|
|
64
75
|
tuple_get="{t}.Item{i1}",
|
|
@@ -108,8 +119,12 @@ class CSharpEmitter(Emitter):
|
|
|
108
119
|
self.lines.append(f"{ind}for (long {var} = {lo}; {var} < {hi}; {var}++) {{")
|
|
109
120
|
else:
|
|
110
121
|
iter_s = self.expr(it)
|
|
111
|
-
self.
|
|
112
|
-
|
|
122
|
+
if self.infer_type(it) == "dict":
|
|
123
|
+
self.var_types[var] = "str"
|
|
124
|
+
self.lines.append(f"{ind}foreach (var {var} in {iter_s}.Keys) {{")
|
|
125
|
+
else:
|
|
126
|
+
self.var_types[var] = "long"
|
|
127
|
+
self.lines.append(f"{ind}foreach (var {var} in {iter_s}) {{")
|
|
113
128
|
self.indent_lvl += 1
|
|
114
129
|
for s in node.body:
|
|
115
130
|
self.stmt(s)
|
|
@@ -133,9 +148,15 @@ class CSharpEmitter(Emitter):
|
|
|
133
148
|
def stmt(self, node): # type: ignore[override]
|
|
134
149
|
# escape C# keywords in variable declarations
|
|
135
150
|
if isinstance(node, ast.AnnAssign) and isinstance(node.target, ast.Name):
|
|
151
|
+
# bare `list` gets its element type from the value
|
|
152
|
+
_ann = self._ann_type(node.annotation)
|
|
153
|
+
_elem = (self._list_elem_of_value(node.value)
|
|
154
|
+
if (_ann == "list" and node.value is not None) else "")
|
|
136
155
|
name = self._esc(node.target.id)
|
|
137
156
|
ann_type = self._ann_type(node.annotation)
|
|
138
157
|
nt = self.py_to_native(ann_type)
|
|
158
|
+
if _elem and _elem != self.spec.list_elem_type:
|
|
159
|
+
nt = self.spec.list_type.format(T=_elem)
|
|
139
160
|
if node.value is not None:
|
|
140
161
|
val = self.expr(node.value)
|
|
141
162
|
self.lines.append(f"{self.spec.indent * self.indent_lvl}{nt} {name} = {val};")
|
|
@@ -227,6 +248,11 @@ def emit_csharp(units: list[FuncUnit], entry: str | None,
|
|
|
227
248
|
extern_fns: functions from other backends that this C# code calls.
|
|
228
249
|
"""
|
|
229
250
|
emitter = CSharpEmitter(SPEC)
|
|
251
|
+
emitter.func_signatures = {
|
|
252
|
+
u.name: ([p for p, _t in u.params],
|
|
253
|
+
dict(getattr(u, 'param_defaults', {})))
|
|
254
|
+
for u in units
|
|
255
|
+
}
|
|
230
256
|
emitter.library_mode = library_mode
|
|
231
257
|
emitter.constants = constants or {}
|
|
232
258
|
emitted: dict[str, str] = {}
|
|
@@ -317,6 +343,9 @@ def emit_csharp(units: list[FuncUnit], entry: str | None,
|
|
|
317
343
|
for line in fn.split("\n"):
|
|
318
344
|
indented_fns.append(" " + line)
|
|
319
345
|
indented_fns.append(wrapper.rstrip())
|
|
346
|
+
elif not fns:
|
|
347
|
+
# entry rejected during emission; see the pipeline diagnostic
|
|
348
|
+
return prelude, emitted
|
|
320
349
|
else:
|
|
321
350
|
# rename main to Main for C# entry point
|
|
322
351
|
# C# Main must return void or int (32-bit), not long
|
|
@@ -13,9 +13,13 @@ from ..analyzer import FuncUnit
|
|
|
13
13
|
SPEC = Spec(
|
|
14
14
|
name="go",
|
|
15
15
|
types={"int": "int64", "float": "float64", "bool": "bool", "str": "string", "None": ""},
|
|
16
|
-
list_type="[]
|
|
16
|
+
list_type="[]{T}",
|
|
17
17
|
list_param_type="[]int64",
|
|
18
18
|
list_elem_type="int64",
|
|
19
|
+
str_split="strings.Split({x}, {sep})",
|
|
20
|
+
str_join="strings.Join({x}, {sep})",
|
|
21
|
+
list_slice="append([]int64(nil), {x}[{start}:{stop}]...)",
|
|
22
|
+
list_copy="append([]int64(nil), {x}...)",
|
|
19
23
|
borrow_list_arg=False,
|
|
20
24
|
range_call="({lo}..{hi})",
|
|
21
25
|
range_step_call="({lo}..{hi})",
|
|
@@ -24,18 +28,23 @@ SPEC = Spec(
|
|
|
24
28
|
print_int='fmt.Println({v})',
|
|
25
29
|
print_float='fmt.Println({v})',
|
|
26
30
|
print_str='fmt.Println({v})',
|
|
27
|
-
print_bool='fmt.Println({v})',
|
|
31
|
+
print_bool='fmt.Println(geBoolStr({v}))',
|
|
28
32
|
print_generic='fmt.Println({v})',
|
|
33
|
+
print_list='fmt.Println(geListStr({v}))',
|
|
29
34
|
int_cast="int64({x})",
|
|
30
35
|
float_cast="float64({x})",
|
|
31
36
|
float_div="(float64({l}) / float64({r}))",
|
|
32
37
|
floor_div="({l} / {r})",
|
|
33
38
|
sum_call="func() int64 {{ var s int64; for _, v := range {it} {{ s += v }}; return s }}()",
|
|
34
|
-
abs_int="
|
|
39
|
+
abs_int="geAbsInt({x})",
|
|
35
40
|
abs_float="math.Abs({x})",
|
|
41
|
+
min2_call="geMin2({a}, {b})",
|
|
42
|
+
max2_call="geMax2({a}, {b})",
|
|
36
43
|
min_call="func() int64 {{ var m int64; for i, v := range {it} {{ if i == 0 || v < m {{ m = v }} }}; return m }}()",
|
|
37
44
|
max_call="func() int64 {{ var m int64; for i, v := range {it} {{ if i == 0 || v > m {{ m = v }} }}; return m }}()",
|
|
38
45
|
pow_call="int64(math.Pow(float64({l}), float64({r})))",
|
|
46
|
+
pow_int="int64(math.Pow(float64({l}), float64({r})))",
|
|
47
|
+
pow_float="math.Pow({l}, {r})",
|
|
39
48
|
append_call="{x} = append({x}, {v})",
|
|
40
49
|
index_call="{x}[{i}]",
|
|
41
50
|
comment="//",
|
|
@@ -58,8 +67,11 @@ SPEC = Spec(
|
|
|
58
67
|
struct_field_template="\t{type} {name}",
|
|
59
68
|
struct_new_template="func {name}_New({params}) {name} {{\n{body}\n}}",
|
|
60
69
|
dict_type="map[string]{V}",
|
|
70
|
+
set_type="map[int64]bool",
|
|
61
71
|
dict_get="{d}[{k}]",
|
|
62
72
|
dict_set="{d}[{k}] = {v}",
|
|
73
|
+
dict_keys="{x}",
|
|
74
|
+
foreach_dict_template="for {var} := range {iter}",
|
|
63
75
|
dict_contains="dictContains({d}, {k})",
|
|
64
76
|
tuple_type="struct {{ a {T}; b {T} }}",
|
|
65
77
|
tuple_get="{t}.{field}",
|
|
@@ -157,8 +169,13 @@ class GoEmitter(Emitter):
|
|
|
157
169
|
self.lines.append(f"{ind} {var_a} := {it_a}[__i]; {var_b} := {it_b}[__i]")
|
|
158
170
|
else:
|
|
159
171
|
iter_s = self.expr(it)
|
|
160
|
-
self.
|
|
161
|
-
|
|
172
|
+
if self.infer_type(it) == "dict":
|
|
173
|
+
# range over a map yields keys
|
|
174
|
+
self.var_types[var] = "str"
|
|
175
|
+
self.lines.append(f"{ind}for {var} := range {iter_s} {{")
|
|
176
|
+
else:
|
|
177
|
+
self.var_types[var] = "long"
|
|
178
|
+
self.lines.append(f"{ind}for _, {var} := range {iter_s} {{")
|
|
162
179
|
self.indent_lvl += 1
|
|
163
180
|
for s in node.body:
|
|
164
181
|
self.stmt(s)
|
|
@@ -186,6 +203,10 @@ class GoEmitter(Emitter):
|
|
|
186
203
|
name = node.target.id
|
|
187
204
|
ann_type = self._ann_type(node.annotation)
|
|
188
205
|
nt = self.py_to_native(ann_type)
|
|
206
|
+
if ann_type == "list" and node.value is not None:
|
|
207
|
+
elem = self._list_elem_of_value(node.value)
|
|
208
|
+
if elem and elem != self.spec.list_elem_type:
|
|
209
|
+
nt = self.spec.list_type.format(T=elem)
|
|
189
210
|
if node.value is not None:
|
|
190
211
|
val = self.expr(node.value)
|
|
191
212
|
self.lines.append(f"{self.spec.indent * self.indent_lvl}var {name} {nt} = {val}")
|
|
@@ -211,7 +232,8 @@ class GoEmitter(Emitter):
|
|
|
211
232
|
def _ann_type(self, node: ast.AST) -> str:
|
|
212
233
|
if isinstance(node, ast.Name):
|
|
213
234
|
t = node.id
|
|
214
|
-
if t in ("int", "float", "bool", "str", "list", "dict",
|
|
235
|
+
if t in ("int", "float", "bool", "str", "list", "dict",
|
|
236
|
+
"tuple", "set"):
|
|
215
237
|
return t
|
|
216
238
|
if t in self.class_names:
|
|
217
239
|
return t
|
|
@@ -276,6 +298,11 @@ def emit_go(units: list[FuncUnit], entry: str | None,
|
|
|
276
298
|
extern_fns: functions from other backends that this Go code calls.
|
|
277
299
|
"""
|
|
278
300
|
emitter = GoEmitter(SPEC)
|
|
301
|
+
emitter.func_signatures = {
|
|
302
|
+
u.name: ([p for p, _t in u.params],
|
|
303
|
+
dict(getattr(u, 'param_defaults', {})))
|
|
304
|
+
for u in units
|
|
305
|
+
}
|
|
279
306
|
emitter.library_mode = library_mode
|
|
280
307
|
emitter.constants = constants or {}
|
|
281
308
|
emitted: dict[str, str] = {}
|
|
@@ -294,6 +321,7 @@ def emit_go(units: list[FuncUnit], entry: str | None,
|
|
|
294
321
|
"package main\n\n"
|
|
295
322
|
'import "fmt"\n'
|
|
296
323
|
'import "strings"\n'
|
|
324
|
+
'import "strconv"\n'
|
|
297
325
|
'import "sort"\n'
|
|
298
326
|
'import "os"\n'
|
|
299
327
|
'import "math"\n'
|
|
@@ -369,6 +397,10 @@ def emit_go(units: list[FuncUnit], entry: str | None,
|
|
|
369
397
|
else:
|
|
370
398
|
wrapper = f"func main() {{\n\t_ = {entry_u.name}()\n}}\n"
|
|
371
399
|
fns.append(wrapper)
|
|
400
|
+
elif not fns:
|
|
401
|
+
# the entry function was rejected during emission; the pipeline
|
|
402
|
+
# already holds the reason, so return what we have
|
|
403
|
+
return prelude + "\n".join(fns), emitted
|
|
372
404
|
else:
|
|
373
405
|
# Go main() must have no arguments and no return values
|
|
374
406
|
# Rename the user's main() to __ge_main() and add a wrapper
|
|
@@ -14,9 +14,13 @@ from ..analyzer import FuncUnit
|
|
|
14
14
|
SPEC = Spec(
|
|
15
15
|
name="kotlin",
|
|
16
16
|
types={"int": "Long", "float": "Double", "bool": "Boolean", "str": "String", "None": "Unit"},
|
|
17
|
-
list_type="MutableList<
|
|
17
|
+
list_type="MutableList<{T}>",
|
|
18
18
|
list_param_type="MutableList<Long>",
|
|
19
19
|
list_elem_type="Long",
|
|
20
|
+
str_split="{x}.split({sep}).toMutableList()",
|
|
21
|
+
str_join="{x}.joinToString({sep})",
|
|
22
|
+
list_slice="{x}.subList(({start}).toInt(), ({stop}).toInt()).toMutableList()",
|
|
23
|
+
list_copy="{x}.toMutableList()",
|
|
20
24
|
borrow_list_arg=False,
|
|
21
25
|
range_call="({lo}..{hi})",
|
|
22
26
|
range_step_call="({lo}..{hi} step {step})",
|
|
@@ -25,8 +29,9 @@ SPEC = Spec(
|
|
|
25
29
|
print_int='println({v})',
|
|
26
30
|
print_float='println({v})',
|
|
27
31
|
print_str='println({v})',
|
|
28
|
-
print_bool='println({v})',
|
|
32
|
+
print_bool='println(if ({v}) "True" else "False")',
|
|
29
33
|
print_generic='println({v})',
|
|
34
|
+
print_list='println({v}.joinToString(", ", "[", "]"))',
|
|
30
35
|
int_cast="{x}.toLong()",
|
|
31
36
|
float_cast="{x}.toDouble()",
|
|
32
37
|
float_div="({l}.toDouble() / {r}.toDouble())",
|
|
@@ -34,9 +39,13 @@ SPEC = Spec(
|
|
|
34
39
|
sum_call="{it}.sum()",
|
|
35
40
|
abs_int="abs({x})",
|
|
36
41
|
abs_float="abs({x})",
|
|
42
|
+
min2_call="minOf({a}, {b})",
|
|
43
|
+
max2_call="maxOf({a}, {b})",
|
|
37
44
|
min_call="{it}.minOrNull()!!",
|
|
38
45
|
max_call="{it}.maxOrNull()!!",
|
|
39
|
-
pow_call="
|
|
46
|
+
pow_call="{l}.toDouble().pow({r}.toDouble()).toLong()",
|
|
47
|
+
pow_int="{l}.toDouble().pow({r}.toDouble()).toLong()",
|
|
48
|
+
pow_float="{l}.pow({r})",
|
|
40
49
|
append_call="{x}.add({v})",
|
|
41
50
|
index_call="{x}[{i}.toInt()]",
|
|
42
51
|
comment="//",
|
|
@@ -58,8 +67,10 @@ SPEC = Spec(
|
|
|
58
67
|
struct_field_template=" val {type}: {name},",
|
|
59
68
|
struct_new_template="fun {name}_new({params}): {name} {{\n{body}\n}}",
|
|
60
69
|
dict_type="HashMap<String, {V}>",
|
|
61
|
-
|
|
70
|
+
set_type="MutableSet<Long>",
|
|
71
|
+
dict_get="{d}[{k}]!!",
|
|
62
72
|
dict_set="{d}[{k}] = {v}",
|
|
73
|
+
dict_keys="{x}.keys",
|
|
63
74
|
dict_contains="{d}.containsKey({k})",
|
|
64
75
|
tuple_type="Pair<{T}, {T}>",
|
|
65
76
|
tuple_get="{t}.{field}",
|
|
@@ -146,8 +157,12 @@ class KotlinEmitter(Emitter):
|
|
|
146
157
|
self.lines.append(f"{ind}for ({var} in {lo} until {hi}) {{")
|
|
147
158
|
else:
|
|
148
159
|
iter_s = self.expr(it)
|
|
149
|
-
self.
|
|
150
|
-
|
|
160
|
+
if self.infer_type(it) == "dict":
|
|
161
|
+
self.var_types[var] = "str"
|
|
162
|
+
self.lines.append(f"{ind}for ({var} in {iter_s}.keys) {{")
|
|
163
|
+
else:
|
|
164
|
+
self.var_types[var] = "long"
|
|
165
|
+
self.lines.append(f"{ind}for ({var} in {iter_s}) {{")
|
|
151
166
|
self.indent_lvl += 1
|
|
152
167
|
for s in node.body:
|
|
153
168
|
self.stmt(s)
|
|
@@ -217,6 +232,11 @@ def emit_kotlin(units: list[FuncUnit], entry: str | None,
|
|
|
217
232
|
extern_fns: functions from other backends that this Kotlin code calls.
|
|
218
233
|
"""
|
|
219
234
|
emitter = KotlinEmitter(SPEC)
|
|
235
|
+
emitter.func_signatures = {
|
|
236
|
+
u.name: ([p for p, _t in u.params],
|
|
237
|
+
dict(getattr(u, 'param_defaults', {})))
|
|
238
|
+
for u in units
|
|
239
|
+
}
|
|
220
240
|
emitter.library_mode = library_mode
|
|
221
241
|
emitter.constants = constants or {}
|
|
222
242
|
emitted: dict[str, str] = {}
|
|
@@ -295,6 +315,9 @@ def emit_kotlin(units: list[FuncUnit], entry: str | None,
|
|
|
295
315
|
else:
|
|
296
316
|
wrapper = f"fun main() {{\n {entry_u.name}()\n}}\n"
|
|
297
317
|
fns.append(wrapper)
|
|
318
|
+
elif not fns:
|
|
319
|
+
# entry rejected during emission; see the pipeline diagnostic
|
|
320
|
+
return prelude + "\n".join(fns), emitted
|
|
298
321
|
else:
|
|
299
322
|
# Kotlin main() must return Unit (void)
|
|
300
323
|
# Rename the user's main() to __ge_main() and add a wrapper
|
|
@@ -12,6 +12,10 @@ SPEC = Spec(
|
|
|
12
12
|
list_type="Vec<{T}>",
|
|
13
13
|
list_param_type="&[i64]",
|
|
14
14
|
list_elem_type="i64",
|
|
15
|
+
str_split="{x}.split({sep}.as_str()).map(|s| s.to_string()).collect::<Vec<String>>()",
|
|
16
|
+
str_join="{x}.join({sep}.as_str())",
|
|
17
|
+
list_slice="{x}[({start}) as usize..({stop}) as usize].to_vec()",
|
|
18
|
+
list_copy="{x}.clone()",
|
|
15
19
|
borrow_list_arg=True,
|
|
16
20
|
range_call="({lo}..{hi})",
|
|
17
21
|
range_step_call="({lo}..{hi}).step_by({step} as usize)",
|
|
@@ -20,25 +24,32 @@ SPEC = Spec(
|
|
|
20
24
|
print_int='println!("{{}}", {v})',
|
|
21
25
|
print_float='println!("{{}}", {v})',
|
|
22
26
|
print_str='println!("{{}}", {v})',
|
|
23
|
-
print_bool='println!("{{}}", {v})',
|
|
27
|
+
print_bool='println!("{{}}", if {v} {{ "True" }} else {{ "False" }})',
|
|
24
28
|
print_generic='println!("{{:?}}", {v})',
|
|
29
|
+
print_list='println!("{{:?}}", {v})',
|
|
25
30
|
int_cast="{x} as i64",
|
|
26
31
|
float_cast="{x} as f64",
|
|
27
32
|
float_div="({l} as f64 / {r} as f64)",
|
|
28
33
|
floor_div="({l} / {r})",
|
|
29
34
|
sum_call="{it}.iter().sum::<i64>()",
|
|
30
|
-
abs_int="{x}
|
|
35
|
+
abs_int="i64::abs({x})",
|
|
31
36
|
abs_float="{x}.abs()",
|
|
37
|
+
min2_call="std::cmp::min({a}, {b})",
|
|
38
|
+
max2_call="std::cmp::max({a}, {b})",
|
|
32
39
|
min_call="{it}.iter().min().unwrap()",
|
|
33
40
|
max_call="{it}.iter().max().unwrap()",
|
|
34
41
|
pow_call="{l}.pow({r} as u32)",
|
|
42
|
+
pow_int="i64::pow({l}, {r} as u32)",
|
|
43
|
+
pow_float="f64::powf({l}, {r})",
|
|
35
44
|
append_call="{x}.push({v})",
|
|
36
45
|
index_call="{x}[{i} as usize]",
|
|
37
46
|
comment="//",
|
|
38
47
|
fn_template="{sig} {{\n{body}\n}}",
|
|
39
48
|
main_template="",
|
|
40
49
|
ffi_prefix="#[no_mangle]\npub extern \"C\" ",
|
|
41
|
-
|
|
50
|
+
# `String + &String` is not Add in Rust and `a + b` moves `a`, so a
|
|
51
|
+
# format! is used instead — always correct, and it accepts &str too.
|
|
52
|
+
str_concat="format!(\"{{}}{{}}\", {l}, {r})",
|
|
42
53
|
str_len="{x}.len() as i64",
|
|
43
54
|
str_index="{x}.as_bytes()[{i} as usize] as i64",
|
|
44
55
|
str_slice="{x}[{start}..{end}].to_string()",
|
|
@@ -52,8 +63,11 @@ SPEC = Spec(
|
|
|
52
63
|
struct_field_template=" {type}: {name},",
|
|
53
64
|
struct_new_template="fn {name}_new({params}) -> {name} {{\n{body}\n}}",
|
|
54
65
|
dict_type="std::collections::HashMap<String, {V}>",
|
|
66
|
+
set_type="std::collections::HashSet<i64>",
|
|
55
67
|
dict_get="{d}.get(&{k}).copied().unwrap_or(0)",
|
|
56
68
|
dict_set="{d}.insert({k}, {v})",
|
|
69
|
+
dict_keys="{x}.keys().cloned().collect::<Vec<String>>()",
|
|
70
|
+
foreach_dict_template="for {var} in {iter}",
|
|
57
71
|
dict_contains="{d}.contains_key(&{k})",
|
|
58
72
|
tuple_type="({T}, {T})",
|
|
59
73
|
tuple_get="{t}.{i}",
|
|
@@ -188,6 +202,11 @@ def emit_rust(units: list[FuncUnit], entry: str | None,
|
|
|
188
202
|
constants: module-level constants to inline (name -> value).
|
|
189
203
|
"""
|
|
190
204
|
emitter = RustEmitter(SPEC)
|
|
205
|
+
emitter.func_signatures = {
|
|
206
|
+
u.name: ([p for p, _t in u.params],
|
|
207
|
+
dict(getattr(u, 'param_defaults', {})))
|
|
208
|
+
for u in units
|
|
209
|
+
}
|
|
191
210
|
emitter.library_mode = library_mode
|
|
192
211
|
emitter.export_names = set(export_fns) if export_fns else set()
|
|
193
212
|
emitter.constants = constants or {}
|
|
@@ -13,9 +13,11 @@ from ..analyzer import FuncUnit
|
|
|
13
13
|
SPEC = Spec(
|
|
14
14
|
name="zig",
|
|
15
15
|
types={"int": "i64", "float": "f64", "bool": "bool", "str": "[]const u8", "None": "void"},
|
|
16
|
-
list_type="[]const
|
|
16
|
+
list_type="[]const {T}",
|
|
17
17
|
list_param_type="[]const i64",
|
|
18
18
|
list_elem_type="i64",
|
|
19
|
+
list_slice="{x}[@intCast({start})..@intCast({stop})]",
|
|
20
|
+
list_copy="{x}",
|
|
19
21
|
borrow_list_arg=False,
|
|
20
22
|
range_call="({lo}..{hi})",
|
|
21
23
|
range_step_call="({lo}..{hi})",
|
|
@@ -24,19 +26,26 @@ SPEC = Spec(
|
|
|
24
26
|
print_int='std.io.getStdOut().writer().print("{{d}}\\n", .{{{v}}}) catch unreachable',
|
|
25
27
|
print_float='std.io.getStdOut().writer().print("{{d}}\\n", .{{{v}}}) catch unreachable',
|
|
26
28
|
print_str='std.io.getStdOut().writer().print("{{s}}\\n", .{{{v}}}) catch unreachable',
|
|
27
|
-
print_bool='std.io.getStdOut().writer().print("{}\\n", .{{{v}}}) catch unreachable',
|
|
29
|
+
print_bool='std.io.getStdOut().writer().print("{{s}}\\n", .{{if ({v}) "True" else "False"}}) catch unreachable',
|
|
28
30
|
print_generic='std.io.getStdOut().writer().print("{{any}}\\n", .{{{v}}}) catch unreachable',
|
|
31
|
+
print_list='gePrintList({v})',
|
|
29
32
|
int_cast="@as(i64, {x})",
|
|
30
33
|
float_cast="@as(f64, {x})",
|
|
31
34
|
float_div="(@as(f64, {l}) / @as(f64, {r}))",
|
|
32
35
|
floor_div="@divFloor({l}, {r})",
|
|
33
36
|
sum_call="blk: {{ var s: i64 = 0; for ({it}) |v| {{ s += v; }} break :blk s; }}",
|
|
34
|
-
abs_int="@intCast(if ({x} < 0) -{x} else {x})",
|
|
37
|
+
abs_int="@as(i64, @intCast(if ({x} < 0) -{x} else {x}))",
|
|
35
38
|
abs_float="@abs({x})",
|
|
39
|
+
min2_call="@min({a}, {b})",
|
|
40
|
+
max2_call="@max({a}, {b})",
|
|
36
41
|
min_call="@min({it}...)",
|
|
37
42
|
max_call="@max({it}...)",
|
|
38
43
|
pow_call="std.math.pow(i64, {l}, {r})",
|
|
39
|
-
|
|
44
|
+
pow_int="std.math.pow(i64, {l}, {r})",
|
|
45
|
+
pow_float="std.math.pow(f64, {l}, {r})",
|
|
46
|
+
# Zig lists are `[]const i64` slices and cannot grow; list.append
|
|
47
|
+
# is rejected with a clear diagnostic rather than emitting invalid Zig.
|
|
48
|
+
append_call="",
|
|
40
49
|
index_call="{x}[@as(usize, @intCast({i}))]",
|
|
41
50
|
comment="//",
|
|
42
51
|
fn_template="{sig} {{\n{body}\n}}",
|
|
@@ -194,6 +203,7 @@ class ZigEmitter(Emitter):
|
|
|
194
203
|
self.lines.append(f"{ind}_ = {self.expr(node.value)};")
|
|
195
204
|
return
|
|
196
205
|
if isinstance(node, ast.AnnAssign) and node.value is not None:
|
|
206
|
+
# bare `list` gets its element type from the value
|
|
197
207
|
target = node.target.id if isinstance(node.target, ast.Name) else self.expr(node.target)
|
|
198
208
|
t = self._ann_type(node.annotation) if isinstance(node.target, ast.Name) else self.infer_type(node.value)
|
|
199
209
|
if isinstance(node.target, ast.Name):
|
|
@@ -244,8 +254,16 @@ class ZigEmitter(Emitter):
|
|
|
244
254
|
self.lines.append(f"{ind}while ({var} < {hi}) : ({var} += 1) {{")
|
|
245
255
|
else:
|
|
246
256
|
iter_s = self.expr(it)
|
|
247
|
-
self.
|
|
248
|
-
|
|
257
|
+
if self.infer_type(it) == "dict":
|
|
258
|
+
# Zig has no allocator here, so walk the map's key iterator
|
|
259
|
+
# instead of materialising a key list.
|
|
260
|
+
self.var_types[var] = "str"
|
|
261
|
+
self.lines.append(f"{ind}var __keys_{var} = {iter_s}.keyIterator();")
|
|
262
|
+
self.lines.append(f"{ind}while (__keys_{var}.next()) |__kp_{var}| {{")
|
|
263
|
+
self.lines.append(f"{ind} const {var} = __kp_{var}.*;")
|
|
264
|
+
else:
|
|
265
|
+
self.var_types[var] = "long"
|
|
266
|
+
self.lines.append(f"{ind}for ({iter_s}) |{var}| {{")
|
|
249
267
|
self.indent_lvl += 1
|
|
250
268
|
for s in node.body:
|
|
251
269
|
self.stmt(s)
|
|
@@ -315,6 +333,11 @@ def emit_zig(units: list[FuncUnit], entry: str | None,
|
|
|
315
333
|
extern_fns: functions from other backends that this Zig code calls.
|
|
316
334
|
"""
|
|
317
335
|
emitter = ZigEmitter(SPEC)
|
|
336
|
+
emitter.func_signatures = {
|
|
337
|
+
u.name: ([p for p, _t in u.params],
|
|
338
|
+
dict(getattr(u, 'param_defaults', {})))
|
|
339
|
+
for u in units
|
|
340
|
+
}
|
|
318
341
|
emitter.library_mode = library_mode
|
|
319
342
|
emitter.constants = constants or {}
|
|
320
343
|
emitted: dict[str, str] = {}
|
|
@@ -392,6 +415,10 @@ def emit_zig(units: list[FuncUnit], entry: str | None,
|
|
|
392
415
|
else:
|
|
393
416
|
wrapper = f"pub fn main() void {{\n _ = {entry_u.name}();\n}}\n"
|
|
394
417
|
fns.append(wrapper)
|
|
418
|
+
elif not fns:
|
|
419
|
+
# the entry function was rejected during emission; the pipeline
|
|
420
|
+
# already has the reason, so emit a stub rather than crashing
|
|
421
|
+
return prelude, emitted
|
|
395
422
|
else:
|
|
396
423
|
# Zig main() must return void (or u8 for exit code)
|
|
397
424
|
# Rename the user's main() to __ge_main() and add a wrapper
|
package/python/pyeffic/stdlib.py
CHANGED
|
@@ -156,6 +156,64 @@ fn ge_exp(x: f64) -> f64 { x.exp() }
|
|
|
156
156
|
''',
|
|
157
157
|
"cpp": '''
|
|
158
158
|
// GE Math runtime (C++)
|
|
159
|
+
std::string geStrUpper(std::string s) {
|
|
160
|
+
for (auto& c : s) c = (char)std::toupper((unsigned char)c);
|
|
161
|
+
return s;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
std::string geStrLower(std::string s) {
|
|
165
|
+
for (auto& c : s) c = (char)std::tolower((unsigned char)c);
|
|
166
|
+
return s;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
std::string geStrReplace(std::string s, const std::string& from,
|
|
170
|
+
const std::string& to) {
|
|
171
|
+
if (from.empty()) return s;
|
|
172
|
+
size_t pos = 0;
|
|
173
|
+
while ((pos = s.find(from, pos)) != std::string::npos) {
|
|
174
|
+
s.replace(pos, from.size(), to);
|
|
175
|
+
pos += to.size();
|
|
176
|
+
}
|
|
177
|
+
return s;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
std::vector<std::string> geStrSplit(const std::string& s, const std::string& sep) {
|
|
181
|
+
std::vector<std::string> out;
|
|
182
|
+
if (sep.empty()) { out.push_back(s); return out; }
|
|
183
|
+
size_t pos = 0, found;
|
|
184
|
+
while ((found = s.find(sep, pos)) != std::string::npos) {
|
|
185
|
+
out.push_back(s.substr(pos, found - pos));
|
|
186
|
+
pos = found + sep.size();
|
|
187
|
+
}
|
|
188
|
+
out.push_back(s.substr(pos));
|
|
189
|
+
return out;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
std::string geStrJoin(const std::vector<std::string>& v, const std::string& sep) {
|
|
193
|
+
std::string out;
|
|
194
|
+
for (size_t i = 0; i < v.size(); ++i) {
|
|
195
|
+
if (i) out += sep;
|
|
196
|
+
out += v[i];
|
|
197
|
+
}
|
|
198
|
+
return out;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
std::vector<std::string> geDictKeys(const std::map<std::string, int64_t>& d) {
|
|
202
|
+
std::vector<std::string> ks;
|
|
203
|
+
ks.reserve(d.size());
|
|
204
|
+
for (const auto& kv : d) ks.push_back(kv.first);
|
|
205
|
+
return ks;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
std::string geListStr(const std::vector<int64_t>& v) {
|
|
209
|
+
std::string s = "[";
|
|
210
|
+
for (size_t i = 0; i < v.size(); ++i) {
|
|
211
|
+
if (i) s += ", ";
|
|
212
|
+
s += std::to_string(v[i]);
|
|
213
|
+
}
|
|
214
|
+
return s + "]";
|
|
215
|
+
}
|
|
216
|
+
|
|
159
217
|
double ge_sqrt(double x) { return std::sqrt(x); }
|
|
160
218
|
double ge_floor(double x) { return std::floor(x); }
|
|
161
219
|
double ge_ceil(double x) { return std::ceil(x); }
|
|
@@ -180,6 +238,42 @@ static double GeExp(double x) { return System.Math.Exp(x); }
|
|
|
180
238
|
''',
|
|
181
239
|
"go": '''
|
|
182
240
|
// GE Math runtime (Go)
|
|
241
|
+
func geAbsInt(x int64) int64 {
|
|
242
|
+
if x < 0 {
|
|
243
|
+
return -x
|
|
244
|
+
}
|
|
245
|
+
return x
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
func geMin2(a, b int64) int64 {
|
|
249
|
+
if a < b {
|
|
250
|
+
return a
|
|
251
|
+
}
|
|
252
|
+
return b
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
func geMax2(a, b int64) int64 {
|
|
256
|
+
if a > b {
|
|
257
|
+
return a
|
|
258
|
+
}
|
|
259
|
+
return b
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
func geListStr(v []int64) string {
|
|
263
|
+
parts := make([]string, len(v))
|
|
264
|
+
for i, x := range v {
|
|
265
|
+
parts[i] = strconv.FormatInt(x, 10)
|
|
266
|
+
}
|
|
267
|
+
return "[" + strings.Join(parts, ", ") + "]"
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
func geBoolStr(b bool) string {
|
|
271
|
+
if b {
|
|
272
|
+
return "True"
|
|
273
|
+
}
|
|
274
|
+
return "False"
|
|
275
|
+
}
|
|
276
|
+
|
|
183
277
|
func geSqrt(x float64) float64 { return math.Sqrt(x) }
|
|
184
278
|
func geFloor(x float64) float64 { return math.Floor(x) }
|
|
185
279
|
func geCeil(x float64) float64 { return math.Ceil(x) }
|
|
@@ -205,6 +299,16 @@ fun ge_exp(x: Double): Double = exp(x)
|
|
|
205
299
|
''',
|
|
206
300
|
"zig": '''
|
|
207
301
|
// GE Math runtime (Zig)
|
|
302
|
+
fn gePrintList(v: []const i64) void {
|
|
303
|
+
const w = std.io.getStdOut().writer();
|
|
304
|
+
w.print("[", .{}) catch unreachable;
|
|
305
|
+
for (v, 0..) |x, i| {
|
|
306
|
+
if (i > 0) w.print(", ", .{}) catch unreachable;
|
|
307
|
+
w.print("{d}", .{x}) catch unreachable;
|
|
308
|
+
}
|
|
309
|
+
w.print("]\\n", .{}) catch unreachable;
|
|
310
|
+
}
|
|
311
|
+
|
|
208
312
|
fn ge_sqrt(x: f64) f64 { return @sqrt(x); }
|
|
209
313
|
fn ge_floor(x: f64) f64 { return @floor(x); }
|
|
210
314
|
fn ge_ceil(x: f64) f64 { return @ceil(x); }
|
|
@@ -34,6 +34,8 @@ class TypeChecker:
|
|
|
34
34
|
self.file = file
|
|
35
35
|
self.errors = ErrorReporter()
|
|
36
36
|
self.func_sigs: dict[str, tuple[list[str], str]] = {} # name -> (param_types, ret_type)
|
|
37
|
+
|
|
38
|
+
self.func_required: dict[str, int] = {}
|
|
37
39
|
self.func_names: set[str] = set()
|
|
38
40
|
self.class_names: set[str] = set()
|
|
39
41
|
|
|
@@ -49,6 +51,8 @@ class TypeChecker:
|
|
|
49
51
|
if u.supported and u.body:
|
|
50
52
|
param_types = [t for _, t in u.params]
|
|
51
53
|
self.func_sigs[u.name] = (param_types, u.ret_type)
|
|
54
|
+
self.func_required[u.name] = getattr(
|
|
55
|
+
u, "n_required_params", len(param_types)) or len(param_types)
|
|
52
56
|
self.func_names.add(u.name)
|
|
53
57
|
# collect class names
|
|
54
58
|
if classes:
|
|
@@ -97,16 +101,25 @@ class TypeChecker:
|
|
|
97
101
|
# check if calling a known function with wrong number of args
|
|
98
102
|
if fname in self.func_sigs:
|
|
99
103
|
param_types, _ = self.func_sigs[fname]
|
|
100
|
-
|
|
104
|
+
n_req = self.func_required.get(fname, len(param_types))
|
|
105
|
+
n_given = len(node.args)
|
|
106
|
+
if n_given < n_req or n_given > len(param_types):
|
|
107
|
+
if n_req == len(param_types):
|
|
108
|
+
expect = f"{len(param_types)} args"
|
|
109
|
+
else:
|
|
110
|
+
expect = (f"between {n_req} and "
|
|
111
|
+
f"{len(param_types)} args")
|
|
101
112
|
self.errors.error(
|
|
102
113
|
"GE002",
|
|
103
|
-
f"function '{fname}' expects {
|
|
104
|
-
f"got {
|
|
114
|
+
f"function '{fname}' expects {expect}, "
|
|
115
|
+
f"got {n_given}",
|
|
105
116
|
file=self.file, line=node.lineno,
|
|
106
117
|
)
|
|
107
118
|
# check for undefined function calls
|
|
108
119
|
elif fname not in ("print", "len", "range", "abs", "sum", "min", "max",
|
|
109
120
|
"float", "int", "str", "bool", "dict", "list", "tuple",
|
|
121
|
+
"round", "sorted", "reversed", "enumerate", "zip",
|
|
122
|
+
"any", "all", "pow", "divmod", "ord", "chr",
|
|
110
123
|
"ge_inline", "ge_raw", "ge_preamble"):
|
|
111
124
|
if fname not in self.class_names:
|
|
112
125
|
self.errors.error(
|