lang: multi-iterable for loops — drop ':', add '..=', open ranges, arrow bodies
The for header is now a comma-separated list of iterables with a
positional capture group and no ':' separator:
for xs (x) { } // collection
for 0..n (i) { } // range (end exclusive)
for 1..=5 (a) { } // ..= inclusive end
for xs, 0.. (x, i) { } // index idiom (replaces (x, i))
for xs, ys (x, y) { } // parallel (zip) iteration
for xs (x) => sum += x; // arrow body (full statement)
First-iterable-wins: the first iterable's length drives the loop and
must be bounded; the other positions follow by their own cursors (a
non-first range's end is not consulted or evaluated; a shorter
non-first collection is read past its length on mismatch). The old
single-iterable index capture is replaced by the trailing open range.
Capture/call disambiguation is positional: the paren group immediately
before '{' or '=>' is the capture, every earlier top-level group is a
call. 'for zip(a, b) (x, y)' calls zip; 'for f(n) { }' reads (n) as
the capture and errors with a parenthesize/add-capture hint. The old
':' form errors with a migration hint.
Lowering is unified across forms: one cursor slot per position (ranges
start at their start, collections at 0), all advanced together, the
first position's bound terminating. inline for keeps the single
bounded comptime range.
Migrated the full corpus (examples, library modules, issue repros,
in-source test strings). New coverage: examples/0050 (the full feature
surface) and examples/1149-1155 (seven diagnostic faces). specs.md For
Loop section + grammar rewritten; readme teaser updated.
This commit is contained in:
87
specs.md
87
specs.md
@@ -108,7 +108,7 @@ M :: union { `s1: s32; } // union tag
|
||||
`u16 :: enum { A; B; } // type-declaration name
|
||||
`u8, rest := pair(); // destructure name
|
||||
if `s16 := maybe() { } // optional binding
|
||||
for xs: (`bool, `u16) { } // for capture + index
|
||||
for xs, 0.. (`bool, `u16) { } // for captures
|
||||
x catch `s2 { } // catch tag binding
|
||||
```
|
||||
|
||||
@@ -1370,7 +1370,7 @@ suggestion:
|
||||
variadic `..xs: []P` (a runtime slice) instead of a pack `..xs: P`;
|
||||
- returning it (`return xs;`) → return a tuple `(..xs)` (and make the return
|
||||
type that tuple);
|
||||
- iterating it (`for xs : (x)`, `xs[runtime_i]`) → `inline for 0..xs.len (i)`
|
||||
- iterating it (`for xs (x)`, `xs[runtime_i]`) → `inline for 0..xs.len (i)`
|
||||
for a comptime unroll, or take `..xs: []P` for a runtime loop.
|
||||
|
||||
The recurring runtime escape hatch is the **slice-of-protocol variadic**
|
||||
@@ -1940,33 +1940,55 @@ while i < 10 {
|
||||
|
||||
### For Loop
|
||||
|
||||
#### Range form
|
||||
```sx
|
||||
for start..end: (i) { } // counting loop, cursor `i` (s64), `end` exclusive
|
||||
for start..end { } // no cursor — body runs `end - start` times
|
||||
inline for start..end: (i) { } // comptime-unrolled; `i` is a comptime constant per iteration
|
||||
for it1, it2, ... (c1, c2, ...) { } // parallel iteration, one capture per iterable
|
||||
for it1, it2, ... (c1, c2, ...) => stmt; // arrow body — a single statement
|
||||
```
|
||||
`start` and `end` are `s64` expressions; the loop counts `start, start+1, …, end-1`.
|
||||
The cursor is optional — omit `: (i)` entirely when the body doesn't need the index
|
||||
(`for 0..n { … }`). When present it is introduced by `:`, matching the collection
|
||||
form (`for xs: (x)`).
|
||||
The `inline` variant requires comptime-known bounds and unrolls the body once per
|
||||
value, binding the cursor as a compile-time constant (so it can index a pack:
|
||||
`inline for 0..xs.len: (i) { xs[i].m() }`). `break;` / `continue;` work in the
|
||||
runtime form.
|
||||
|
||||
#### Collection form
|
||||
A `for` header is a comma-separated list of **iterables** followed by an
|
||||
optional **capture group** and the body. Each iterable is a collection
|
||||
(array, slice, string, `List(T)`-like struct) or a range:
|
||||
|
||||
```sx
|
||||
for iterable: (elem) { } // element alias (no copy)
|
||||
for iterable: (elem, ix) { } // element + index
|
||||
for iterable: (_, ix) { } // index only
|
||||
for iterable: (*elem) { } // element pointer (*T) — by-reference
|
||||
for iterable: (*elem, ix) { } // element pointer + index
|
||||
for xs (x) { } // collection, element capture
|
||||
for 0..n (i) { } // range, `end` exclusive; cursor i (s64)
|
||||
for 1..=5 (a) { } // `..=` — end inclusive: 1 2 3 4 5
|
||||
for 0..5 { } // no captures — body runs 5 times
|
||||
for xs { } // no captures — body runs xs.len times
|
||||
for xs, 0.. (x, i) { } // THE index idiom: open range follows along
|
||||
for xs, ys (x, y) { } // parallel (zip) iteration
|
||||
for 1..=5, 0.. (a, b) { } // a: 1..5, b: 0..4 (end inferred)
|
||||
for a4, b4, 100.. (p, q, k) { } // any number of positions
|
||||
for xs (x) => sum += x; // arrow body
|
||||
inline for 0..n (i) { } // comptime-unrolled single bounded range
|
||||
```
|
||||
Iterates over arrays and slices. The capture clause after `:` binds loop variables:
|
||||
- The first name is the element capture (non-reassignable alias into the array/slice)
|
||||
- The optional second name is the index (s64, starting at 0, also non-reassignable)
|
||||
- Use `_` to discard a capture
|
||||
|
||||
**First-iterable-wins.** The FIRST iterable's length drives the loop: a
|
||||
bounded range runs `end - start` times (`..=`: `end - start + 1`), a
|
||||
collection runs `len` times. The first iterable must be bounded — an open
|
||||
range `a..` may only follow it. Every other position simply follows along by
|
||||
its own cursor; consequences:
|
||||
- a non-first range's end is **not consulted** (and not evaluated — write
|
||||
`start..` for clarity);
|
||||
- a non-first collection shorter than the first is read **past its length**
|
||||
on mismatch — the first iterable is the authoritative one.
|
||||
|
||||
**Captures are positional**: the group binds one name per iterable, in
|
||||
order — range positions bind the cursor value (s64), collection positions
|
||||
bind the element. An empty group is omitted entirely (no parens). Capture
|
||||
names shadow outer bindings, like any inner declaration. Use `_` to discard
|
||||
a position. The old single-iterable index form `for xs: (x, i)` is gone —
|
||||
write `for xs, 0.. (x, i)`.
|
||||
|
||||
**The capture/call rule.** In a for header, the parenthesized group
|
||||
immediately before `{` or `=>` is the capture; every earlier top-level paren
|
||||
group is ordinary call syntax. So `for zip(a, b) (x, y) { }` calls
|
||||
`zip(a, b)` and captures `(x, y)`, while `for f(n) { }` reads `(n)` as the
|
||||
capture — making the iterable `f` itself, which errors ("cannot iterate")
|
||||
with a hint. A call iterable therefore always needs a capture group; to
|
||||
iterate a call result without one, parenthesize (`for (f(n)) { }`) or bind
|
||||
it to a local first. A leading paren group is a normal grouped expression
|
||||
(`for (a ++ b) (x)` iterates the grouped value).
|
||||
|
||||
The element capture is a direct alias — reads and field writes go to the original array element. Direct reassignment of the capture (`elem = x`) is a compile error.
|
||||
|
||||
@@ -1974,18 +1996,25 @@ The element capture is a direct alias — reads and field writes go to the origi
|
||||
- Passing it onward is zero-copy — `f(elem)` where `f` takes `*T` hands over the pointer, not a copy.
|
||||
- Writes through it land in the original: `elem.* = v` (or `elem.field = v`).
|
||||
- In a value position the pointer auto-derefs to the element: `elem + 1` reads the value, and `if elem == { … }` matches the pointee (a pointer subject matches through the deref). Where a `*T` is expected, the pointer is passed as-is.
|
||||
- Range positions have no storage — `*` on a range capture is a compile error.
|
||||
|
||||
```sx
|
||||
events := plat.poll_events(); // []Event
|
||||
for events: (*ev) { // ev : *Event — no copy
|
||||
for events (*ev) { // ev : *Event — no copy
|
||||
pipeline.dispatch_event(ev); // passes the pointer
|
||||
}
|
||||
```
|
||||
|
||||
`break;` exits the loop. `continue;` skips to the next iteration.
|
||||
The `inline` variant requires a single bounded range with comptime-known
|
||||
bounds and unrolls the body once per value, binding the cursor as a
|
||||
compile-time constant (so it can index a pack:
|
||||
`inline for 0..xs.len (i) { xs[i].m() }`).
|
||||
|
||||
`break;` exits the loop. `continue;` skips to the next iteration. Both run
|
||||
the iteration's pending `defer`s first (see Defer).
|
||||
```sx
|
||||
arr : [5]s32 = .[1, 2, 3, 4, 5];
|
||||
for arr: (val, ix) {
|
||||
for arr, 0.. (val, ix) {
|
||||
if ix == 2 { continue; }
|
||||
print("{}\n", val);
|
||||
}
|
||||
@@ -3039,7 +3068,9 @@ multi_assign = lvalue (',' lvalue)+ '=' expr (',' expr)+
|
||||
lvalue = IDENT | postfix '.' IDENT
|
||||
expr = if_expr | match_expr | while_expr | for_expr | lambda | binary
|
||||
while_expr = 'while' expr block
|
||||
for_expr = 'for' expr ':' '(' IDENT [',' IDENT] ')' block
|
||||
for_expr = 'for' for_iter (',' for_iter)* [for_capture] (block | '=>' stmt)
|
||||
for_iter = expr [('..' | '..=') [expr]]
|
||||
for_capture = '(' ['*'] IDENT (',' ['*'] IDENT)* ')'
|
||||
binary = catch_expr (binop catch_expr)* // binop includes `or` (fallback / chain)
|
||||
catch_expr = unary ('catch' IDENT? (block | '==' '{' case_arm* else_arm? '}' | unary))?
|
||||
unary = ('-' | '!' | 'xx' | 'try' | 'cast' '(' type ')') postfix
|
||||
|
||||
Reference in New Issue
Block a user