gelang 0.1.3 → 0.1.5
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 +112 -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 +319 -27
- package/python/pyeffic/emitters/cpp.py +31 -3
- package/python/pyeffic/emitters/csharp.py +37 -3
- package/python/pyeffic/emitters/go.py +46 -7
- package/python/pyeffic/emitters/kotlin.py +34 -6
- package/python/pyeffic/emitters/rust.py +30 -4
- package/python/pyeffic/emitters/zig.py +122 -73
- package/python/pyeffic/ge_cli.py +111 -5
- package/python/pyeffic/stdlib.py +213 -1
- package/python/pyeffic/typecheck.py +16 -3
|
@@ -48,6 +48,40 @@ 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
|
+
# str.upper() / str.lower(). Empty falls back to the per-backend
|
|
67
|
+
# branch in the dispatcher.
|
|
68
|
+
str_upper: str = ""
|
|
69
|
+
str_lower: str = ""
|
|
70
|
+
# str.replace(a, b). Empty falls back to the generic method call.
|
|
71
|
+
str_replace: str = ""
|
|
72
|
+
# in-place list.sort() / list.reverse()
|
|
73
|
+
list_sort: str = ""
|
|
74
|
+
list_reverse: str = ""
|
|
75
|
+
list_slice: str = ""
|
|
76
|
+
list_copy: str = ""
|
|
77
|
+
# Iterating a dict in Python yields its keys. `dict_keys` is an
|
|
78
|
+
# expression producing those keys; `foreach_dict_template` is the
|
|
79
|
+
# loop head to use with it (Go ranges a map differently).
|
|
80
|
+
dict_keys: str = ""
|
|
81
|
+
foreach_dict_template: str = ""
|
|
82
|
+
# Printing a sequence. Python renders lists as `[1, 2, 3]`, so every
|
|
83
|
+
# backend must match that rather than its own native format.
|
|
84
|
+
print_list: str = ""
|
|
51
85
|
# string operations
|
|
52
86
|
str_concat: str = "{l} + {r}" # string concatenation
|
|
53
87
|
str_len: str = "{x}.length()" # string length (C++ style)
|
|
@@ -74,6 +108,9 @@ class Spec:
|
|
|
74
108
|
dict_contains: str = "({d}.find({k}) != {d}.end())"
|
|
75
109
|
# tuple support
|
|
76
110
|
tuple_type: str = "std::tuple<{T}>"
|
|
111
|
+
set_type: str = "" # native set type
|
|
112
|
+
set_len: str = "" # len(set)
|
|
113
|
+
dict_len: str = "" # len(dict)
|
|
77
114
|
tuple_get: str = "std::get<{i}>({t})"
|
|
78
115
|
# list length (separate from len_call which may be used for strings)
|
|
79
116
|
list_len: str = "{x}.size()"
|
|
@@ -90,6 +127,9 @@ class Emitter:
|
|
|
90
127
|
self.params: set[str] = set() # param names (already borrowed if list)
|
|
91
128
|
self.library_mode: bool = False # emit C-ABI exports instead of a main()
|
|
92
129
|
self.extern_names: set[str] = set() # extern "C" fns from other backends
|
|
130
|
+
# {name: ([param names], {param: default source text})} so a call site
|
|
131
|
+
# that omits defaults can fill them in.
|
|
132
|
+
self.func_signatures: dict[str, tuple[list[str], dict[str, str]]] = {}
|
|
93
133
|
self.force_extern_c: bool = False # force extern "C" on all functions (multi-backend)
|
|
94
134
|
self.export_names: set[str] = set() # Rust fns called from C++ — export them
|
|
95
135
|
self.class_names: set[str] = set() # known class names for constructor calls
|
|
@@ -179,6 +219,10 @@ class Emitter:
|
|
|
179
219
|
return self.spec.dict_type.format(V=self.spec.list_elem_type)
|
|
180
220
|
if t == "tuple":
|
|
181
221
|
return self.spec.tuple_type.format(T=self.spec.list_elem_type)
|
|
222
|
+
if t == "set":
|
|
223
|
+
if self.spec.set_type:
|
|
224
|
+
return self.spec.set_type
|
|
225
|
+
return self.spec.list_type.format(T=self.spec.list_elem_type)
|
|
182
226
|
# class type — use the class name directly
|
|
183
227
|
if t in self.class_names:
|
|
184
228
|
return t
|
|
@@ -197,6 +241,10 @@ class Emitter:
|
|
|
197
241
|
elem_t = self._native_elem_type(self.param_elem_types[pname])
|
|
198
242
|
if self.spec.name == "rust":
|
|
199
243
|
return f"&[{elem_t}]"
|
|
244
|
+
if self.spec.name == "zig":
|
|
245
|
+
# a list parameter is always a pointer to the caller's
|
|
246
|
+
# ArrayList so mutation is visible to it
|
|
247
|
+
return f"*std.ArrayList({elem_t})"
|
|
200
248
|
return self.spec.list_type.format(T=elem_t)
|
|
201
249
|
return self.spec.list_param_type
|
|
202
250
|
if t == "dict":
|
|
@@ -220,6 +268,12 @@ class Emitter:
|
|
|
220
268
|
return self.spec.dict_type.format(V=self.spec.list_elem_type)
|
|
221
269
|
if t == "tuple":
|
|
222
270
|
return self.spec.tuple_type.format(T=self.spec.list_elem_type)
|
|
271
|
+
if t == "set":
|
|
272
|
+
if self.spec.set_type:
|
|
273
|
+
if self.spec.name == "cpp":
|
|
274
|
+
return f"const {self.spec.set_type}&"
|
|
275
|
+
return self.spec.set_type
|
|
276
|
+
return self.spec.list_param_type
|
|
223
277
|
# class type — pass by value (or const ref in C++)
|
|
224
278
|
if t in self.class_names:
|
|
225
279
|
if self.spec.name == "cpp":
|
|
@@ -334,6 +388,12 @@ class Emitter:
|
|
|
334
388
|
self.var_types[target] = t
|
|
335
389
|
self.declared.add(target)
|
|
336
390
|
nt = self.py_to_native(t)
|
|
391
|
+
if t == "list":
|
|
392
|
+
# a bare `list` annotation carries no element type; take it
|
|
393
|
+
# from the value so `xs = "a,b".split(",")` is a string list
|
|
394
|
+
elem = self._list_elem_of_value(node.value)
|
|
395
|
+
if elem and elem != self.spec.list_elem_type:
|
|
396
|
+
nt = self.spec.list_type.format(T=elem)
|
|
337
397
|
if self.spec.name == "rust":
|
|
338
398
|
self.lines.append(f"{ind}let mut {target}: {nt} = {self.expr(node.value)};")
|
|
339
399
|
else:
|
|
@@ -754,7 +814,12 @@ class Emitter:
|
|
|
754
814
|
msg = f"{self.current_func}: {feature}"
|
|
755
815
|
if msg not in self.unsupported_emissions:
|
|
756
816
|
self.unsupported_emissions.append(msg)
|
|
757
|
-
|
|
817
|
+
# Use the backend's own comment syntax: a C-style comment is not
|
|
818
|
+
# valid in Zig, and the placeholder still has to parse.
|
|
819
|
+
marker = getattr(self.spec, "comment", "//") or "//"
|
|
820
|
+
if marker.startswith("/*"):
|
|
821
|
+
return f"/*unsupported: {feature}*/"
|
|
822
|
+
return f"{marker} unsupported: {feature}"
|
|
758
823
|
|
|
759
824
|
def _ident(self, name: str) -> str:
|
|
760
825
|
"""Escape a local/parameter name that collides with a target keyword.
|
|
@@ -858,12 +923,21 @@ class Emitter:
|
|
|
858
923
|
self._for_zip(node, ind)
|
|
859
924
|
return
|
|
860
925
|
else:
|
|
861
|
-
# iterate a container
|
|
926
|
+
# iterate a container
|
|
862
927
|
iter_s = self.expr(it)
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
928
|
+
if self.infer_type(it) == "dict" and self.spec.dict_keys:
|
|
929
|
+
# Python iterates dict keys, not (key, value) pairs
|
|
930
|
+
iter_s = self.spec.dict_keys.format(x=iter_s)
|
|
931
|
+
tmpl = (self.spec.foreach_dict_template
|
|
932
|
+
or self.spec.foreach_template)
|
|
933
|
+
self.var_types[var] = "str"
|
|
934
|
+
head = tmpl.format(var=var, iter=iter_s,
|
|
935
|
+
etype=self.spec.list_elem_type)
|
|
936
|
+
else:
|
|
937
|
+
elem_type = self._iter_elem_type(it)
|
|
938
|
+
self.var_types[var] = elem_type
|
|
939
|
+
head = self.spec.foreach_template.format(
|
|
940
|
+
var=var, iter=iter_s, etype=self.spec.list_elem_type)
|
|
867
941
|
self.lines.append(f"{ind}{head} {{")
|
|
868
942
|
self.indent_lvl += 1
|
|
869
943
|
for s in node.body:
|
|
@@ -991,7 +1065,7 @@ class Emitter:
|
|
|
991
1065
|
if isinstance(node.op, ast.FloorDiv):
|
|
992
1066
|
return self.spec.floor_div.format(l=left, r=right)
|
|
993
1067
|
if isinstance(node.op, ast.Pow):
|
|
994
|
-
return self.
|
|
1068
|
+
return self._pow(left, right, node)
|
|
995
1069
|
if isinstance(node.op, ast.Add):
|
|
996
1070
|
lt = self.infer_type(node.left)
|
|
997
1071
|
rt = self.infer_type(node.right)
|
|
@@ -1079,8 +1153,11 @@ class Emitter:
|
|
|
1079
1153
|
orelse = self.expr(node.orelse)
|
|
1080
1154
|
if self.spec.name == "rust":
|
|
1081
1155
|
return f"if {cond} {{ {body} }} else {{ {orelse} }}"
|
|
1082
|
-
if self.spec.name in ("cpp", "csharp"
|
|
1156
|
+
if self.spec.name in ("cpp", "csharp"):
|
|
1083
1157
|
return f"({cond} ? {body} : {orelse})"
|
|
1158
|
+
if self.spec.name == "kotlin":
|
|
1159
|
+
# Kotlin has no ?: operator — it uses an if expression
|
|
1160
|
+
return f"(if ({cond}) {body} else {orelse})"
|
|
1084
1161
|
if self.spec.name == "go":
|
|
1085
1162
|
return f"(func() int64 {{ if {cond} {{ return {body} }}; return {orelse} }}())"
|
|
1086
1163
|
if self.spec.name == "zig":
|
|
@@ -1104,9 +1181,18 @@ class Emitter:
|
|
|
1104
1181
|
if stop:
|
|
1105
1182
|
return self.spec.str_slice_end.format(x=base, end=stop)
|
|
1106
1183
|
return base # full slice s[:] = s
|
|
1107
|
-
# list slicing —
|
|
1108
|
-
if
|
|
1109
|
-
|
|
1184
|
+
# list slicing — xs[a:b], xs[a:], xs[:b], xs[:]
|
|
1185
|
+
if self.spec.list_slice:
|
|
1186
|
+
length = self.spec.len_call.format(x=base)
|
|
1187
|
+
if not start:
|
|
1188
|
+
start = self.spec.int_cast.format(x="0")
|
|
1189
|
+
if not stop:
|
|
1190
|
+
stop = length
|
|
1191
|
+
if not sl.lower and not sl.upper:
|
|
1192
|
+
if self.spec.list_copy:
|
|
1193
|
+
return self.spec.list_copy.format(x=base)
|
|
1194
|
+
return self.spec.list_slice.format(
|
|
1195
|
+
x=base, start=start, stop=stop)
|
|
1110
1196
|
return self._mark_unsupported(f"list slice {base}")
|
|
1111
1197
|
idx = self.expr(node.slice)
|
|
1112
1198
|
if base_type == "dict":
|
|
@@ -1135,17 +1221,25 @@ class Emitter:
|
|
|
1135
1221
|
return self.spec.index_call.format(x=base, i=idx)
|
|
1136
1222
|
if isinstance(node, ast.List):
|
|
1137
1223
|
elems = ", ".join(self.expr(e) for e in node.elts)
|
|
1224
|
+
# a literal of strings needs a string element type, not the
|
|
1225
|
+
# backend's default integer element
|
|
1226
|
+
elem = self._list_elem_of_value(node) or self.spec.list_elem_type
|
|
1138
1227
|
if self.spec.name == "rust":
|
|
1139
1228
|
return f"vec![{elems}]"
|
|
1140
1229
|
if self.spec.name == "go":
|
|
1141
|
-
return f"[]{
|
|
1230
|
+
return f"[]{elem}{{{elems}}}"
|
|
1142
1231
|
if self.spec.name == "kotlin":
|
|
1143
1232
|
return f"mutableListOf({elems})"
|
|
1144
1233
|
if self.spec.name == "zig":
|
|
1145
|
-
|
|
1234
|
+
# Zig lists are growable ArrayLists; build one from the literal
|
|
1235
|
+
if not node.elts:
|
|
1236
|
+
return f"geListNew({elem})"
|
|
1237
|
+
return f"geListFrom({elem}, &[_]{elem}{{ {elems} }})"
|
|
1146
1238
|
if self.spec.name == "csharp":
|
|
1147
|
-
|
|
1148
|
-
|
|
1239
|
+
lt = self.spec.list_type.format(T=elem)
|
|
1240
|
+
return f"new {lt}{{{elems}}}"
|
|
1241
|
+
lt = self.spec.list_type.format(T=elem)
|
|
1242
|
+
return f"{lt}{{{elems}}}"
|
|
1149
1243
|
if isinstance(node, ast.ListComp):
|
|
1150
1244
|
return self._list_comp(node)
|
|
1151
1245
|
if isinstance(node, ast.Dict):
|
|
@@ -1171,11 +1265,20 @@ class Emitter:
|
|
|
1171
1265
|
for k, v in zip(node.keys, node.values))
|
|
1172
1266
|
return f"hashMapOf({pairs})"
|
|
1173
1267
|
if self.spec.name == "zig":
|
|
1174
|
-
# Zig
|
|
1175
|
-
#
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1268
|
+
# Zig has no map literal; build one with the arena allocator.
|
|
1269
|
+
# An empty literal needs no block at all, and a block whose
|
|
1270
|
+
# local is never mutated is rejected by Zig.
|
|
1271
|
+
if not node.keys:
|
|
1272
|
+
return (f"std.StringHashMap({self.spec.list_elem_type})"
|
|
1273
|
+
f".init(ge_alloc)")
|
|
1274
|
+
puts = "\n".join(
|
|
1275
|
+
f" __m.put({self.expr(k)}, {self.expr(v)}) catch unreachable;"
|
|
1276
|
+
for k, v in zip(node.keys, node.values))
|
|
1277
|
+
return (f"blk: {{\n"
|
|
1278
|
+
f" var __m = std.StringHashMap({self.spec.list_elem_type}).init(ge_alloc);\n"
|
|
1279
|
+
f"{puts}\n"
|
|
1280
|
+
f" break :blk __m;\n"
|
|
1281
|
+
f"}}")
|
|
1179
1282
|
return f"{{{pairs}}}"
|
|
1180
1283
|
if isinstance(node, ast.Set):
|
|
1181
1284
|
elems = ", ".join(self.expr(e) for e in node.elts)
|
|
@@ -1186,15 +1289,44 @@ class Emitter:
|
|
|
1186
1289
|
if self.spec.name == "csharp":
|
|
1187
1290
|
return f"new HashSet<long>{{{elems}}}"
|
|
1188
1291
|
if self.spec.name == "go":
|
|
1189
|
-
|
|
1292
|
+
# Go has no set type; GE uses map[elem]bool. Go rejects
|
|
1293
|
+
# duplicate *constant* keys at compile time, so literals are
|
|
1294
|
+
# deduplicated here (a runtime set would collapse them anyway).
|
|
1295
|
+
seen: list[str] = []
|
|
1296
|
+
for e in node.elts:
|
|
1297
|
+
rendered = self.expr(e)
|
|
1298
|
+
if rendered not in seen:
|
|
1299
|
+
seen.append(rendered)
|
|
1300
|
+
if not seen:
|
|
1301
|
+
return "map[int64]bool{}"
|
|
1302
|
+
pairs = ", ".join(f"{v}: true" for v in seen)
|
|
1303
|
+
return f"map[int64]bool{{{pairs}}}"
|
|
1190
1304
|
if self.spec.name == "kotlin":
|
|
1191
1305
|
return f"hashSetOf({elems})"
|
|
1306
|
+
if self.spec.name == "zig":
|
|
1307
|
+
# Zig has no set type; GE uses AutoHashMap(elem, void) over
|
|
1308
|
+
# the arena allocator
|
|
1309
|
+
if not node.elts:
|
|
1310
|
+
return "geSetNew(i64)"
|
|
1311
|
+
seen: list[str] = []
|
|
1312
|
+
for e in node.elts:
|
|
1313
|
+
rendered = self.expr(e)
|
|
1314
|
+
if rendered not in seen:
|
|
1315
|
+
seen.append(rendered)
|
|
1316
|
+
return f"geSetFrom(i64, &[_]i64{{ {', '.join(seen)} }})"
|
|
1192
1317
|
return f"std::set<int64_t>{{{elems}}}"
|
|
1193
1318
|
if isinstance(node, ast.SetComp):
|
|
1194
1319
|
return self._set_comp(node)
|
|
1195
1320
|
if isinstance(node, ast.DictComp):
|
|
1196
1321
|
return self._dict_comp(node)
|
|
1197
1322
|
if isinstance(node, ast.Tuple):
|
|
1323
|
+
# GE models tuples as fixed-width pairs. Anything else would need
|
|
1324
|
+
# a per-arity native type (Go structs, Kotlin Triple, ...), so it
|
|
1325
|
+
# is rejected loudly rather than emitted as broken code.
|
|
1326
|
+
if len(node.elts) != 2:
|
|
1327
|
+
return self._mark_unsupported(
|
|
1328
|
+
f"tuple of {len(node.elts)} elements "
|
|
1329
|
+
f"(only 2-element tuples are supported)")
|
|
1198
1330
|
elems = ", ".join(self.expr(e) for e in node.elts)
|
|
1199
1331
|
if self.spec.name == "rust":
|
|
1200
1332
|
return f"({elems})"
|
|
@@ -1265,6 +1397,10 @@ class Emitter:
|
|
|
1265
1397
|
return self.spec.str_len.format(x=self.expr(arg))
|
|
1266
1398
|
if t == "list":
|
|
1267
1399
|
return self.spec.list_len.format(x=self.expr(arg))
|
|
1400
|
+
if t == "set" and self.spec.set_len:
|
|
1401
|
+
return self.spec.set_len.format(x=self.expr(arg))
|
|
1402
|
+
if t == "dict" and self.spec.dict_len:
|
|
1403
|
+
return self.spec.dict_len.format(x=self.expr(arg))
|
|
1268
1404
|
return self.spec.len_call.format(x=self.expr(arg))
|
|
1269
1405
|
if fname == "abs":
|
|
1270
1406
|
t = self.infer_type(node.args[0])
|
|
@@ -1273,10 +1409,16 @@ class Emitter:
|
|
|
1273
1409
|
if fname == "sum":
|
|
1274
1410
|
return self.spec.sum_call.format(it=self.expr(node.args[0]))
|
|
1275
1411
|
if fname in ("min", "max"):
|
|
1276
|
-
|
|
1412
|
+
is_min = fname == "min"
|
|
1413
|
+
tmpl = self.spec.min_call if is_min else self.spec.max_call
|
|
1277
1414
|
raw = [self.expr(a) for a in node.args]
|
|
1278
1415
|
if len(raw) == 1:
|
|
1279
1416
|
return tmpl.format(it=raw[0])
|
|
1417
|
+
if len(raw) == 2:
|
|
1418
|
+
pair = (self.spec.min2_call if is_min
|
|
1419
|
+
else self.spec.max2_call)
|
|
1420
|
+
if pair:
|
|
1421
|
+
return pair.format(a=raw[0], b=raw[1])
|
|
1280
1422
|
return f"{fname}({', '.join(raw)})"
|
|
1281
1423
|
if fname == "pow":
|
|
1282
1424
|
return self.spec.pow_call.format(l=self.expr(node.args[0]), r=self.expr(node.args[1]))
|
|
@@ -1584,8 +1726,26 @@ class Emitter:
|
|
|
1584
1726
|
return self._stdlib_call("ge_log", node.args)
|
|
1585
1727
|
if fname == "exp":
|
|
1586
1728
|
return self._stdlib_call("ge_exp", node.args)
|
|
1729
|
+
if (isinstance(node.func, ast.Attribute) and node.func.attr == "split"
|
|
1730
|
+
and len(node.args) == 1):
|
|
1731
|
+
if not self.spec.str_split:
|
|
1732
|
+
return self._mark_unsupported(
|
|
1733
|
+
f"str.split (not available on the {self.spec.name} backend)")
|
|
1734
|
+
return self.spec.str_split.format(
|
|
1735
|
+
x=self.expr(node.func.value), sep=self.expr(node.args[0]))
|
|
1736
|
+
if (isinstance(node.func, ast.Attribute) and node.func.attr == "join"
|
|
1737
|
+
and len(node.args) == 1):
|
|
1738
|
+
if not self.spec.str_join:
|
|
1739
|
+
return self._mark_unsupported(
|
|
1740
|
+
f"str.join (not available on the {self.spec.name} backend)")
|
|
1741
|
+
return self.spec.str_join.format(
|
|
1742
|
+
x=self.expr(node.args[0]), sep=self.expr(node.func.value))
|
|
1587
1743
|
if isinstance(node.func, ast.Attribute) and node.func.attr == "append":
|
|
1588
1744
|
base = self.expr(node.func.value)
|
|
1745
|
+
if not self.spec.append_call:
|
|
1746
|
+
return self._mark_unsupported(
|
|
1747
|
+
f"list.append (the {self.spec.name} backend models lists "
|
|
1748
|
+
f"as fixed slices)")
|
|
1589
1749
|
return self.spec.append_call.format(x=base, v=self.expr(node.args[0]))
|
|
1590
1750
|
# super().method(args) -> ParentClass_method(_self, args)
|
|
1591
1751
|
if (isinstance(node.func, ast.Attribute) and
|
|
@@ -1617,10 +1777,12 @@ class Emitter:
|
|
|
1617
1777
|
if base_type == "str" or (isinstance(node.func.value, ast.Constant) and isinstance(node.func.value.value, str)):
|
|
1618
1778
|
method = node.func.attr
|
|
1619
1779
|
if method == "upper":
|
|
1780
|
+
if self.spec.str_upper:
|
|
1781
|
+
return self.spec.str_upper.format(x=base)
|
|
1620
1782
|
if self.spec.name == "rust":
|
|
1621
1783
|
return f"{base}.to_uppercase()"
|
|
1622
1784
|
if self.spec.name == "cpp":
|
|
1623
|
-
return f"
|
|
1785
|
+
return f"geStrUpper({base})"
|
|
1624
1786
|
if self.spec.name == "csharp":
|
|
1625
1787
|
return f"{base}.ToUpper()"
|
|
1626
1788
|
if self.spec.name == "go":
|
|
@@ -1629,8 +1791,12 @@ class Emitter:
|
|
|
1629
1791
|
return f"{base}.uppercase()"
|
|
1630
1792
|
return f"{base}.upper()"
|
|
1631
1793
|
if method == "lower":
|
|
1794
|
+
if self.spec.str_lower:
|
|
1795
|
+
return self.spec.str_lower.format(x=base)
|
|
1632
1796
|
if self.spec.name == "rust":
|
|
1633
1797
|
return f"{base}.to_lowercase()"
|
|
1798
|
+
if self.spec.name == "cpp":
|
|
1799
|
+
return f"geStrLower({base})"
|
|
1634
1800
|
if self.spec.name == "csharp":
|
|
1635
1801
|
return f"{base}.ToLower()"
|
|
1636
1802
|
if self.spec.name == "go":
|
|
@@ -1638,6 +1804,19 @@ class Emitter:
|
|
|
1638
1804
|
if self.spec.name == "kotlin":
|
|
1639
1805
|
return f"{base}.lowercase()"
|
|
1640
1806
|
return f"{base}.lower()"
|
|
1807
|
+
if method == "replace" and len(node.args) == 2:
|
|
1808
|
+
a = self.expr(node.args[0])
|
|
1809
|
+
b = self.expr(node.args[1])
|
|
1810
|
+
if self.spec.str_replace:
|
|
1811
|
+
return self.spec.str_replace.format(x=base, a=a, b=b)
|
|
1812
|
+
if self.spec.name == "rust":
|
|
1813
|
+
return f"{base}.replace({a}.as_str(), {b}.as_str())"
|
|
1814
|
+
if self.spec.name == "csharp":
|
|
1815
|
+
return f"{base}.Replace({a}, {b})"
|
|
1816
|
+
if self.spec.name == "go":
|
|
1817
|
+
return f"strings.ReplaceAll({base}, {a}, {b})"
|
|
1818
|
+
if self.spec.name == "kotlin":
|
|
1819
|
+
return f"{base}.replace({a}, {b})"
|
|
1641
1820
|
if method == "strip":
|
|
1642
1821
|
if self.spec.name == "rust":
|
|
1643
1822
|
return f"{base}.trim().to_string()"
|
|
@@ -1648,6 +1827,18 @@ class Emitter:
|
|
|
1648
1827
|
if self.spec.name == "kotlin":
|
|
1649
1828
|
return f"{base}.trim()"
|
|
1650
1829
|
return f"{base}.strip()"
|
|
1830
|
+
# in-place list operations: xs.sort() / xs.reverse()
|
|
1831
|
+
if (isinstance(node.func, ast.Attribute)
|
|
1832
|
+
and node.func.attr in ("sort", "reverse")
|
|
1833
|
+
and not node.args):
|
|
1834
|
+
list_base = self.expr(node.func.value)
|
|
1835
|
+
tpl = (self.spec.list_sort if node.func.attr == "sort"
|
|
1836
|
+
else self.spec.list_reverse)
|
|
1837
|
+
if tpl:
|
|
1838
|
+
return tpl.format(x=list_base)
|
|
1839
|
+
return self._mark_unsupported(
|
|
1840
|
+
f"list.{node.func.attr} (not available on the "
|
|
1841
|
+
f"{self.spec.name} backend)")
|
|
1651
1842
|
# generic method call
|
|
1652
1843
|
return f"{base}.{node.func.attr}({', '.join(args)})"
|
|
1653
1844
|
# generic (user) function call: borrow list args where appropriate
|
|
@@ -1666,6 +1857,20 @@ class Emitter:
|
|
|
1666
1857
|
else:
|
|
1667
1858
|
args.append(f"&mut {self.expr(a)}")
|
|
1668
1859
|
continue
|
|
1860
|
+
if self.spec.name == "zig" and self.infer_type(a) == "list":
|
|
1861
|
+
# Zig list params are *std.ArrayList, so the caller passes a
|
|
1862
|
+
# pointer to its own list (mutations must be visible).
|
|
1863
|
+
if isinstance(a, ast.Name):
|
|
1864
|
+
args.append(f"&{self.expr(a)}")
|
|
1865
|
+
else:
|
|
1866
|
+
# a literal or call result is an rvalue and cannot be
|
|
1867
|
+
# addressed; bind it to a temporary first
|
|
1868
|
+
tmp = f"__ge_arg{i}_{len(self.lines)}"
|
|
1869
|
+
self.lines.append(
|
|
1870
|
+
f"{self.spec.indent * self.indent_lvl}"
|
|
1871
|
+
f"var {tmp} = {self.expr(a)};")
|
|
1872
|
+
args.append(f"&{tmp}")
|
|
1873
|
+
continue
|
|
1669
1874
|
args.append(self._arg(a))
|
|
1670
1875
|
# handle keyword arguments: map to positional by reordering
|
|
1671
1876
|
# (native languages don't support keyword args, so we just append them)
|
|
@@ -1674,12 +1879,36 @@ class Emitter:
|
|
|
1674
1879
|
# **kwargs — not supported, skip
|
|
1675
1880
|
continue
|
|
1676
1881
|
args.append(self.expr(kw.value))
|
|
1882
|
+
args = self._fill_defaults(fname, args, node)
|
|
1677
1883
|
call_str = f"{fname}({', '.join(args)})"
|
|
1678
1884
|
# wrap extern "C" calls in unsafe block (Rust requires this)
|
|
1679
1885
|
if fname in self.extern_names and self.spec.name == "rust":
|
|
1680
1886
|
return f"unsafe {{ {call_str} }}"
|
|
1681
1887
|
return call_str
|
|
1682
1888
|
|
|
1889
|
+
def _fill_defaults(self, fname: str, args: list[str],
|
|
1890
|
+
node: ast.Call) -> list[str]:
|
|
1891
|
+
"""Append default arguments a call site omitted.
|
|
1892
|
+
|
|
1893
|
+
`def f(a, b=10)` called as `f(5)` must emit `f(5, 10)` in the target
|
|
1894
|
+
language, which has no notion of Python default parameters.
|
|
1895
|
+
"""
|
|
1896
|
+
if node.keywords:
|
|
1897
|
+
return args
|
|
1898
|
+
sig = self.func_signatures.get(fname)
|
|
1899
|
+
if not sig:
|
|
1900
|
+
return args
|
|
1901
|
+
names, defaults = sig
|
|
1902
|
+
if len(args) >= len(names):
|
|
1903
|
+
return args
|
|
1904
|
+
filled = list(args)
|
|
1905
|
+
for name in names[len(filled):]:
|
|
1906
|
+
d = defaults.get(name)
|
|
1907
|
+
if d is None or d == "":
|
|
1908
|
+
break
|
|
1909
|
+
filled.append(d)
|
|
1910
|
+
return filled
|
|
1911
|
+
|
|
1683
1912
|
def _str_call(self, arg: ast.AST) -> str:
|
|
1684
1913
|
"""Convert a value to string."""
|
|
1685
1914
|
t = self.infer_type(arg)
|
|
@@ -1993,11 +2222,17 @@ class Emitter:
|
|
|
1993
2222
|
f"for _, {var} := range {iter_src} {{ __v = append(__v, int64({elem})) }}; "
|
|
1994
2223
|
f"return __v; }}()")
|
|
1995
2224
|
if self.spec.name == "zig":
|
|
2225
|
+
# build an ArrayList (the GE list type), not a slice
|
|
2226
|
+
et = self.spec.list_elem_type
|
|
1996
2227
|
if lo is not None:
|
|
1997
|
-
return (f"blk: {{ var __v =
|
|
1998
|
-
f"var {var}: i64 = {lo}; while ({var} < {hi}) : ({var} += 1) {{
|
|
1999
|
-
f"
|
|
2000
|
-
|
|
2228
|
+
return (f"blk: {{ var __v = geListNew({et}); "
|
|
2229
|
+
f"var {var}: i64 = {lo}; while ({var} < {hi}) : ({var} += 1) {{ "
|
|
2230
|
+
f"__v.append(@as({et}, @intCast({elem}))) catch unreachable; }} "
|
|
2231
|
+
f"break :blk __v; }}")
|
|
2232
|
+
return (f"blk: {{ var __v = geListNew({et}); "
|
|
2233
|
+
f"for ({iter_src}.items) |{var}| {{ "
|
|
2234
|
+
f"__v.append(@as({et}, @intCast({elem}))) catch unreachable; }} "
|
|
2235
|
+
f"break :blk __v; }}")
|
|
2001
2236
|
if self.spec.name == "kotlin":
|
|
2002
2237
|
if lo is not None:
|
|
2003
2238
|
return (f"run {{ val __v = mutableListOf<Long>(); "
|
|
@@ -2201,6 +2436,8 @@ class Emitter:
|
|
|
2201
2436
|
return self.spec.print_float.format(v=v)
|
|
2202
2437
|
if t == "bool":
|
|
2203
2438
|
return self.spec.print_bool.format(v=v)
|
|
2439
|
+
if t in ("list", "tuple", "set") and self.spec.print_list:
|
|
2440
|
+
return self.spec.print_list.format(v=v)
|
|
2204
2441
|
# fallback to heuristics on the source text
|
|
2205
2442
|
if any(c in v for c in ".") and not v.startswith('"'):
|
|
2206
2443
|
return self.spec.print_float.format(v=v)
|
|
@@ -2226,6 +2463,50 @@ class Emitter:
|
|
|
2226
2463
|
return base if base in ("list", "dict", "tuple", "set") else "list"
|
|
2227
2464
|
return "int"
|
|
2228
2465
|
|
|
2466
|
+
def _list_elem_of_value(self, node: ast.AST) -> str:
|
|
2467
|
+
"""Native element type of a list-producing expression, or "".
|
|
2468
|
+
|
|
2469
|
+
A bare `list` annotation says nothing about the element type, so it is
|
|
2470
|
+
taken from the value: `"a,b".split(",")` yields strings, not ints.
|
|
2471
|
+
"""
|
|
2472
|
+
if (isinstance(node, ast.Call)
|
|
2473
|
+
and isinstance(node.func, ast.Attribute)):
|
|
2474
|
+
attr = node.func.attr
|
|
2475
|
+
if attr in ("split", "splitlines", "keys"):
|
|
2476
|
+
return self.spec.types.get("str") or self._native_elem_type("str")
|
|
2477
|
+
if attr == "values":
|
|
2478
|
+
return self.spec.list_elem_type
|
|
2479
|
+
if attr in ("copy", "sorted", "reverse"):
|
|
2480
|
+
return self._list_elem_of_value(node.func.value) or ""
|
|
2481
|
+
if attr == "split" or attr == "items":
|
|
2482
|
+
return self.spec.types.get("str", "String")
|
|
2483
|
+
if isinstance(node, ast.List):
|
|
2484
|
+
types = [self.infer_type(e) for e in node.elts]
|
|
2485
|
+
types = [t for t in types if t]
|
|
2486
|
+
if types and all(t == types[0] for t in types):
|
|
2487
|
+
return self._native_elem_type(types[0])
|
|
2488
|
+
if isinstance(node, ast.ListComp):
|
|
2489
|
+
return self._native_elem_type(self.infer_type(node.elt))
|
|
2490
|
+
if isinstance(node, ast.Subscript):
|
|
2491
|
+
return self._list_elem_of_value(node.value)
|
|
2492
|
+
return ""
|
|
2493
|
+
|
|
2494
|
+
def _pow(self, left: str, right: str, node: ast.AST) -> str:
|
|
2495
|
+
"""Render `a ** b`.
|
|
2496
|
+
|
|
2497
|
+
Integer and float exponentiation need different target syntax, and a
|
|
2498
|
+
bare literal like `2 ** 10` is ambiguous in Rust, so the operand type
|
|
2499
|
+
decides which template is used.
|
|
2500
|
+
"""
|
|
2501
|
+
lt = self.infer_type(node.left)
|
|
2502
|
+
rt = self.infer_type(node.right)
|
|
2503
|
+
is_float = lt == "float" or rt == "float"
|
|
2504
|
+
if is_float and self.spec.pow_float:
|
|
2505
|
+
return self.spec.pow_float.format(l=left, r=right)
|
|
2506
|
+
if not is_float and self.spec.pow_int:
|
|
2507
|
+
return self.spec.pow_int.format(l=left, r=right)
|
|
2508
|
+
return self.spec.pow_call.format(l=left, r=right)
|
|
2509
|
+
|
|
2229
2510
|
def infer_type(self, node: ast.AST) -> str:
|
|
2230
2511
|
if isinstance(node, ast.Constant):
|
|
2231
2512
|
if isinstance(node.value, bool):
|
|
@@ -2319,6 +2600,17 @@ class Emitter:
|
|
|
2319
2600
|
key = f"{node.func.value.id}.{node.func.attr}"
|
|
2320
2601
|
if key in self.func_return_types:
|
|
2321
2602
|
return self.func_return_types[key]
|
|
2603
|
+
# str methods that return a string. Without this, print() falls
|
|
2604
|
+
# back to the generic format and a Zig []const u8 renders as a
|
|
2605
|
+
# byte array.
|
|
2606
|
+
if (isinstance(node.func, ast.Attribute)
|
|
2607
|
+
and self.infer_type(node.func.value) == "str"
|
|
2608
|
+
and node.func.attr in ("upper", "lower", "strip", "replace",
|
|
2609
|
+
"join", "title", "capitalize")):
|
|
2610
|
+
return "str"
|
|
2611
|
+
if (isinstance(node.func, ast.Attribute)
|
|
2612
|
+
and node.func.attr in ("split", "splitlines")):
|
|
2613
|
+
return "list"
|
|
2322
2614
|
if isinstance(node, ast.Set):
|
|
2323
2615
|
return "set"
|
|
2324
2616
|
if isinstance(node, ast.SetComp):
|
|
@@ -12,6 +12,15 @@ 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
|
+
list_sort="std::sort({x}.begin(), {x}.end())",
|
|
16
|
+
list_reverse="std::reverse({x}.begin(), {x}.end())",
|
|
17
|
+
str_split="geStrSplit({x}, {sep})",
|
|
18
|
+
str_upper="geStrUpper({x})",
|
|
19
|
+
str_lower="geStrLower({x})",
|
|
20
|
+
str_replace="geStrReplace({x}, {a}, {b})",
|
|
21
|
+
str_join="geStrJoin({x}, {sep})",
|
|
22
|
+
list_slice="std::vector<int64_t>({x}.begin() + ({start}), {x}.begin() + ({stop}))",
|
|
23
|
+
list_copy="std::vector<int64_t>({x})",
|
|
15
24
|
borrow_list_arg=False,
|
|
16
25
|
range_call="for_range({lo}, {hi})",
|
|
17
26
|
range_step_call="for_range({lo}, {hi}, {step})",
|
|
@@ -20,8 +29,9 @@ SPEC = Spec(
|
|
|
20
29
|
print_int='std::cout << ({v}) << std::endl',
|
|
21
30
|
print_float='std::cout << ({v}) << std::endl',
|
|
22
31
|
print_str='std::cout << ({v}) << std::endl',
|
|
23
|
-
print_bool='std::cout << ({v}) << std::endl',
|
|
32
|
+
print_bool='std::cout << (({v}) ? "True" : "False") << std::endl',
|
|
24
33
|
print_generic='std::cout << ({v}) << std::endl',
|
|
34
|
+
print_list='std::cout << geListStr({v}) << std::endl',
|
|
25
35
|
int_cast="static_cast<int64_t>({x})",
|
|
26
36
|
float_cast="static_cast<double>({x})",
|
|
27
37
|
float_div="(static_cast<double>({l}) / static_cast<double>({r}))",
|
|
@@ -29,9 +39,13 @@ SPEC = Spec(
|
|
|
29
39
|
sum_call="std::accumulate({it}.begin(), {it}.end(), 0LL)",
|
|
30
40
|
abs_int="std::abs({x})",
|
|
31
41
|
abs_float="std::fabs({x})",
|
|
42
|
+
min2_call="std::min({a}, {b})",
|
|
43
|
+
max2_call="std::max({a}, {b})",
|
|
32
44
|
min_call="*std::min_element({it}.begin(), {it}.end())",
|
|
33
45
|
max_call="*std::max_element({it}.begin(), {it}.end())",
|
|
34
46
|
pow_call="std::pow({l}, {r})",
|
|
47
|
+
pow_int="static_cast<int64_t>(std::pow(static_cast<double>({l}), static_cast<double>({r})))",
|
|
48
|
+
pow_float="std::pow({l}, {r})",
|
|
35
49
|
append_call="{x}.push_back({v})",
|
|
36
50
|
index_call="{x}[{i}]",
|
|
37
51
|
comment="//",
|
|
@@ -51,8 +65,10 @@ SPEC = Spec(
|
|
|
51
65
|
struct_field_template=" {type} {name};",
|
|
52
66
|
struct_new_template="{name} {name}_new({params}) {{\n{body}\n}}",
|
|
53
67
|
dict_type="std::map<std::string, {V}>",
|
|
68
|
+
set_type="std::set<int64_t>",
|
|
54
69
|
dict_get="{d}.at({k})",
|
|
55
70
|
dict_set="{d}[{k}] = {v}",
|
|
71
|
+
dict_keys="geDictKeys({x})",
|
|
56
72
|
dict_contains="({d}.find({k}) != {d}.end())",
|
|
57
73
|
tuple_type="std::tuple<{T}, {T}>",
|
|
58
74
|
tuple_get="std::get<{i}>({t})",
|
|
@@ -80,8 +96,13 @@ class CppEmitter(Emitter):
|
|
|
80
96
|
)
|
|
81
97
|
else:
|
|
82
98
|
iter_s = self.expr(it)
|
|
83
|
-
self.
|
|
84
|
-
|
|
99
|
+
if self.infer_type(it) == "dict":
|
|
100
|
+
self.var_types[var] = "str"
|
|
101
|
+
self.lines.append(
|
|
102
|
+
f"{ind}for (auto {var} : geDictKeys({iter_s})) {{")
|
|
103
|
+
else:
|
|
104
|
+
self.var_types[var] = "int"
|
|
105
|
+
self.lines.append(f"{ind}for (auto& {var} : {iter_s}) {{")
|
|
85
106
|
self.indent_lvl += 1
|
|
86
107
|
for s in node.body:
|
|
87
108
|
self.stmt(s)
|
|
@@ -135,6 +156,11 @@ def emit_cpp(units: list[FuncUnit], entry: str | None,
|
|
|
135
156
|
constants: dict | None = None,
|
|
136
157
|
preamble: dict | None = None) -> tuple[str, dict[str, str]]:
|
|
137
158
|
emitter = CppEmitter(SPEC)
|
|
159
|
+
emitter.func_signatures = {
|
|
160
|
+
u.name: ([p for p, _t in u.params],
|
|
161
|
+
dict(getattr(u, 'param_defaults', {})))
|
|
162
|
+
for u in units
|
|
163
|
+
}
|
|
138
164
|
emitter.library_mode = library_mode
|
|
139
165
|
emitter.constants = constants or {}
|
|
140
166
|
# in multi-backend mode, force extern "C" on all functions so Rust can link
|
|
@@ -160,6 +186,8 @@ def emit_cpp(units: list[FuncUnit], entry: str | None,
|
|
|
160
186
|
"#include <cmath>\n"
|
|
161
187
|
"#include <numeric>\n"
|
|
162
188
|
"#include <algorithm>\n"
|
|
189
|
+
"#include <set>\n"
|
|
190
|
+
"#include <cctype>\n"
|
|
163
191
|
"#include <string>\n\n"
|
|
164
192
|
"using std::cout;\n"
|
|
165
193
|
"using std::endl;\n\n"
|