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.
@@ -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
- return f"/*unsupported: {feature}*/"
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 (list)
926
+ # iterate a container
862
927
  iter_s = self.expr(it)
863
- elem_type = self._iter_elem_type(it)
864
- self.var_types[var] = elem_type
865
- head = self.spec.foreach_template.format(var=var, iter=iter_s,
866
- etype=self.spec.list_elem_type)
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.spec.pow_call.format(l=left, r=right)
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", "kotlin"):
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 — not fully supported, mark as unsupported
1108
- if start and stop:
1109
- return self._mark_unsupported(f"list slice {base}[{start}:{stop}]")
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"[]{self.spec.list_elem_type}{{{elems}}}"
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
- return f"&[_]i64{{ {elems} }}"
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
- return f"new {self.spec.list_type}{{{elems}}}"
1148
- return f"std::vector<{self.spec.list_elem_type}>{{{elems}}}"
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 doesn't have map literals — emit a block expression
1175
- # that creates and populates the map
1176
- puts = "\n".join(f" m.put({self.expr(k)}, {self.expr(v)}) catch unreachable;"
1177
- for k, v in zip(node.keys, node.values))
1178
- return f"blk: {{\n var m = std.StringHashMap({self.spec.list_elem_type}).init(std.heap.page_allocator);\n{puts}\n break :blk m;\n }}"
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
- return f"map[int64_t]struct{{}}{{}}"
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
- tmpl = self.spec.min_call if fname == "min" else self.spec.max_call
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"/*str.upper()*/"
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 = std.ArrayList({self.spec.list_elem_type}).init(std.heap.page_allocator); "
1998
- f"var {var}: i64 = {lo}; while ({var} < {hi}) : ({var} += 1) {{ __v.append({elem}) catch unreachable; }} "
1999
- f"break :blk __v.toOwnedSlice() catch unreachable; }}")
2000
- return self._mark_unsupported('for-in comprehension in Zig')
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.var_types[var] = "int"
84
- self.lines.append(f"{ind}for (auto& {var} : {iter_s}) {{")
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"