plum

#treesitter#compiler#wasm

git clone https://git.pyrossh.dev/plum

A statically typed, imperative programming language inspired by rust, python


5308f5f — Peter John 2026-10-26T14:38:28+05:30
docs(plans): T8 = for x := form(...) iteration forms + inline user forms
docs/plans/2026-10-26-zig-generics-and-numbers.md CHANGED
@@ -171,17 +171,40 @@ Tests:
171
171
 
172
172
  Update the README's Error handling section, plum-examples/error_propagation.plum (to `T ! [..]` with auto-wrap) and safe_navigation.plum (to `T?`). Migrate std signatures to the sugar where it reads better.
173
173
 
174
- ## Later task T8: near-free closures — ON HOLD, ask the user before running (risk of code bloat; may be replaced by Pyret-style `for` iteration)
175
-
176
- - Every closure literal gets its own type, as in Rust and C++.
177
- - A closure literal passed directly to a generic or higher-order function (`units.filter(|u| u.hp > minHp)`) specializes that function's instantiation per closure literal and inlines the call. There is no `call_indirect`, and no heap env when the closure doesn't escape (captures become extra params or locals of the specialized copy).
178
- - Escape analysis: a closure stored in a field, returned, captured by another escaping closure, or passed to an fn-typed param of a non-specialized (extern or recursive-cycle) fn keeps today's heap env plus typed `call_indirect`.
179
- - Guard: the per-generic instantiation warning (200) counts closure specializations too.
174
+ ## Task T8: `for` forms and inline user forms. This replaces the old "near-free closures" idea, which the user dropped over the risk of code bloat. Not run yet.
175
+
176
+ The user decided: Pyret-style iteration forms written with Plum's existing `for x := …` syntax, including custom forms written by users. Code size stays equal to hand-written loops.
177
+
178
+ 1. **Syntax.** `for <binders> := <source>[, <clause>]*` followed by an indented body.
179
+ - Binders: one name, or several comma-separated (`acc, u`, `i, u`, `a, b`).
180
+ - Sources:
181
+ - `range n`, which is unchanged;
182
+ - a bare iterable (`for u := units`), meaning anything with `len()` and `get(i)`;
183
+ - a form call (`filter(units)`, `fold(0.0, units)`, `zip(xs, ys)`).
184
+ - Clauses: `if cond` skips the element, and `name := expr` binds a value for the later clauses and the body. Binders are in scope in the clauses.
185
+ - `for` is an expression when its form returns a value (`alive := for u := filter(units)`), with the body's last expression as the block result. When the form returns Unit it is a statement.
186
+ 2. **Inline forms.** `inline fun f[..](params…, body: block(A, B) -> R) -> Out = …`.
187
+ - The last parameter must be a `block`. Its parameter count equals the binder count, and its return type is the type of the body's last expression (Unit for statement bodies).
188
+ - Calling `for a, b := f(args)` expands `f`'s body at the call site, with every `body(x, y)` replaced by the user's body, binders bound to `x, y`, and clauses applied in front.
189
+ - Rules:
190
+ - a `block` value can't escape: it can't be stored, returned or captured, and can't be passed anywhere except another inline form's block parameter, which is the composition case;
191
+ - inline fns can't be recursive (rejected);
192
+ - `break` and `continue` in the user body target the innermost loop the form uses to call `body` (documented per form), and `return` returns from the enclosing non-inline fn;
193
+ - the use-site chain errors apply as with generics.
194
+ 3. **Std forms, written in Plum in `plum-std/Iter.plum`:** `filter` (→ List[T]), `map` (→ List[U]), `find` (→ `T?`), `fold(init, xs)` (binders `acc, x`), `any`, `all`, `count`, `indexed` (binders `i, x`), `zip` (binders `a, b`), `each`. None of them is special-cased in the compiler.
195
+ 4. **Resolution.** In `for x := f(args)`: if `f` is an inline fn with a final block param, it is expanded. Otherwise the expression must be iterable (`len`/`get`). Otherwise the error is `'f' is not a for-form or iterable`.
196
+ 5. **The closure methods stay** (`xs.map(|x| …)`), for passing behaviour around as values.
180
197
 
181
198
  Tests:
182
- - `filter_closure_inlined`: the `List[Int].filter` copy for that literal has no `call_indirect`;
199
+ - `filter_form_no_closure`: the wasm for `for u := filter(units)` has no closure struct.new and no call_indirect;
183
- - `escaping_closure_still_works` (stored in a field and called later);
200
+ - `map_with_clauses_fused` (one loop, no intermediate List);
201
+ - `fold_two_binders`;
184
- - `capture_by_value_semantics_unchanged`;
202
+ - `find_returns_nullable`;
203
+ - `user_form_sumBy` and `user_form_neighbours_grid` (two binders, statement body);
204
+ - `block_escape_rejected` (storing, returning or capturing a block);
185
- - `closure_specializations_counted`.
205
+ - `inline_recursion_rejected`;
186
-
206
+ - `break_continue_return_semantics`;
207
+ - `plain_iterable_for`;
208
+ - `not_a_form_error`.
209
+
187
- Bench: compare the hot `update`-style loop (10k elements, map+filter+reduce) before and after, and record compile-time impact.
210
+ Bench: a loop over 10k units doing filter, then map, then fold. Compare the closure-method chain against the fused `for` form, and record the wasm size difference.