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 CHANGED
@@ -53,6 +53,118 @@ All notable changes to GE. Format follows
53
53
  single-flavour files; `.ts.ge.py` is accepted as a legacy alias.
54
54
  - Build root defaults to `build/` (was `ge_build/`).
55
55
  - Package name on npm is `gelang`.
56
+ ## [0.1.5] — 2026-09-13
57
+
58
+ ### Zig is now a full backend
59
+
60
+ Zig modelled a GE list as a `[]const i64` slice, which cannot grow, so
61
+ `append`, `sort`, `split`, list comprehensions and `min`/`max` over a list
62
+ were either rejected or emitted Zig that did not compile. Research into
63
+ Zig's allocator conventions settled the design: a generated program runs
64
+ start to finish, so a single **arena** is the right fit — not
65
+ `page_allocator` (a syscall per allocation) and not
66
+ `GeneralPurposeAllocator` (a debugging allocator).
67
+
68
+ Lists are now `std.ArrayList(T)` over an arena that `main()` releases:
69
+
70
+ - literals become `geListFrom(elem, &[_]elem{...})`; an empty literal is
71
+ `geListNew(elem)`
72
+ - append, sort, reverse, index, len, iteration, slicing and printing all work
73
+ - a list parameter is `*std.ArrayList`, so mutation in the callee is visible
74
+ to the caller, matching Python's reference semantics. Call sites pass
75
+ `&name`; an rvalue argument is bound to a temporary first.
76
+ - passing a list to a mutating callee marks the local `var` (Zig rejects an
77
+ unmutated `var`)
78
+
79
+ Also implemented for Zig: `str.split`, `str.join`, `str.replace`,
80
+ `str.upper`, `str.lower` (all arena-allocated), sets as
81
+ `AutoHashMap(elem, void)` with a deduplicating literal, and dict literals
82
+ over the arena.
83
+
84
+ ### Fixed
85
+
86
+ - Iterating a Rust list yields `&i64`, so `for x in xs: if x == n` did not
87
+ compile. The loop now uses `.cloned()`, which yields owned values for both
88
+ `i64` and `String`. Python semantics are unchanged.
89
+ - Go rejects `int64(3.9)` for an untyped constant; `int()` now truncates
90
+ through `math.Trunc`, matching Python.
91
+ - `list.sort()` and `list.reverse()` had no implementation and fell through
92
+ to `xs.sort()`, valid only in Rust and Kotlin.
93
+ - `ge analyze` parsed hybrid `.ge` files as plain Python, reporting a syntax
94
+ error on the first block tag, and never lowered `.ge.ts` at all.
95
+ - `ge react` wrote to `web/` instead of the documented `build/web/`.
96
+ - `ge flutter` handed a `.ge.ui` file to the module resolver, which tried to
97
+ parse `Window {` as Python.
98
+ - The return type of `str.upper()` and friends was not inferred, so `print`
99
+ used the generic format — a Zig `[]const u8` rendered as a byte array.
100
+ - `scripts/e2e.py` aborted the whole run with `UnicodeEncodeError` when a
101
+ compiler diagnostic contained a character the console codec cannot encode.
102
+
103
+ ### Added
104
+
105
+ - `scripts/e2e.py` — 118 end-to-end cases that compile and run real programs
106
+ on every installed backend and compare stdout with CPython.
107
+ - `str_upper`, `str_lower`, `str_replace`, `set_len` and `dict_len` on the
108
+ shared `Spec`, so every backend expresses these the same way.
109
+
110
+ ### Known limitations
111
+
112
+ - Tuples are supported at arity 2; other arities are rejected with
113
+ `error[GE009]` rather than emitting broken code.
114
+ ## [0.1.4] — 2026-09-13
115
+
116
+ Found by `scripts/e2e.py`, a new harness that compiles and runs 118
117
+ programs on every installed backend and compares stdout with CPython.
118
+
119
+ ### Fixed
120
+
121
+ - **Wrong output.** `print(True)` printed lowercase `true` on Rust, C++,
122
+ Go, Zig and Kotlin; list printing used each target's native format
123
+ (`[1 2 3]` in Go, `{ 1, 2, 3 }` in Zig). Both now match Python.
124
+ - **Dict iteration** walked `(key, value)` pairs instead of keys, so
125
+ `for k in d: d[k]` failed to compile on every backend.
126
+ - **List slicing** was marked unsupported, which dropped the whole
127
+ function and surfaced as the misleading `main function not found`.
128
+ `xs[a:b]`, `xs[a:]`, `xs[:b]` and `xs[:]` are implemented everywhere.
129
+ - **`str.split` / `str.join`** had no implementation at all.
130
+ - **`str.replace`** had no implementation at all.
131
+ - **`min(a, b)` / `max(a, b)`** emitted a bare call no target provides.
132
+ - **`abs()`** on a literal was ambiguous in Rust, untyped in Zig, and
133
+ absent for `int64` in Go.
134
+ - **Default arguments** were rejected outright. A call may now supply
135
+ between `n_required` and `len(params)` arguments.
136
+ - **`set` annotations** became `f64`, because `set` was missing from the
137
+ native type mapping. Added `set_type` per backend.
138
+ - **Lists of strings** could not be expressed: only Rust and C++ had a
139
+ `{T}` placeholder in `list_type`.
140
+ - **C++ `std::sqrt`** appeared before `#include <cmath>`.
141
+ - **Kotlin** used `Math.pow` (no such class in Kotlin/Native) and a
142
+ C-style ternary (Kotlin has no `?:`).
143
+ - **C++ `upper()` / `lower()`** emitted a bare comment, so the program
144
+ compiled but did nothing.
145
+ - **Rust string concatenation** moved the left operand.
146
+ - **Emitter crashes**: when the entry function was rejected, zig, go,
147
+ kotlin and csharp raised `IndexError` instead of reporting the reason.
148
+ - **`ge tools check`** disagreed with `ge doctor` because `CompilerInfo`
149
+ field names (`dotnet`, `kotlinc`) differ from the public toolchain
150
+ names.
151
+ - **Zig** emitted a raw newline inside a string literal, and
152
+ `_mark_unsupported` produced a C-style comment, which Zig rejects.
153
+
154
+ ### Added
155
+
156
+ - `scripts/e2e.py` — end-to-end tests against real toolchains.
157
+ - `ge doctor` — runtime and toolchain check with install hints.
158
+
159
+ ### Known limitations
160
+
161
+ - Zig models lists as fixed slices, so `list.append` is rejected with a
162
+ clear diagnostic rather than compiling. Switching the backend to
163
+ `std.ArrayList` needs an allocator threaded through every signature.
164
+ - Tuples are supported at arity 2; other arities are rejected with a
165
+ diagnostic instead of emitting broken code.
166
+
167
+
56
168
 
57
169
  ## [0.1.0] — 2026-09-12
58
170
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gelang",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "One typed source language, compiled to Rust, C++, C#, Zig, Go, or Kotlin — chosen per function. Generates Flutter and React UIs from one DSL.",
5
5
  "keywords": [
6
6
  "ge",
@@ -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
- available = is_toolchain_available(name) or getattr(detected, name, None) is not None
299
- path = get_toolchain_path(name) or getattr(detected, name, None)
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),