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
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
|
@@ -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),
|