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:
@@ -1,5 +1,5 @@
|
||||
// Range-based for loops: `for start..end: (i) { }` (cursor optional, `end`
|
||||
// exclusive) is a runtime counting loop; `inline for start..end: (i) { }`
|
||||
// Range-based for loops: `for start..end (i) { }` (cursor optional, `end`
|
||||
// exclusive) is a runtime counting loop; `inline for start..end (i) { }`
|
||||
// is comptime-unrolled — the cursor is a compile-time constant each
|
||||
// iteration, so `xs[i]` over a heterogeneous pack substitutes the concrete
|
||||
// per-position element (this is what drives pack iteration).
|
||||
@@ -16,14 +16,14 @@ impl Show for B { show :: (self: *B) -> string => "B"; }
|
||||
|
||||
// Comptime-unrolled iteration over a pack; cursor `i` indexes the pack.
|
||||
each :: (..xs: Show) -> void {
|
||||
inline for 0..xs.len: (i) {
|
||||
inline for 0..xs.len (i) {
|
||||
print("[{}]={}\n", i, xs[i].show());
|
||||
}
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
// Runtime range, cursor used.
|
||||
for 0..3: (i) { print("i={}\n", i); }
|
||||
for 0..3 (i) { print("i={}\n", i); }
|
||||
|
||||
// Runtime range, no cursor — body runs `end - start` times.
|
||||
n := 0;
|
||||
@@ -31,7 +31,7 @@ main :: () -> s32 {
|
||||
print("n={}\n", n);
|
||||
|
||||
// Non-zero start.
|
||||
for 2..5: (j) { print("j={}\n", j); }
|
||||
for 2..5 (j) { print("j={}\n", j); }
|
||||
|
||||
// Inline unroll over a heterogeneous pack.
|
||||
each(A.{ x = 1 }, B.{ s = "hi" }, A.{ x = 3 });
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// `for xs: (*x)` binds each element by pointer — no per-element copy.
|
||||
// `for xs (*x)` binds each element by pointer — no per-element copy.
|
||||
// Mutations write back, and a pointer subject matches through the deref.
|
||||
#import "modules/std.sx";
|
||||
|
||||
@@ -10,12 +10,12 @@ Shape :: enum {
|
||||
main :: () -> s32 {
|
||||
// By-ref mutation writes back into the array (impossible with a value copy).
|
||||
xs : [3]s64 = .[1, 2, 3];
|
||||
for xs: (*x) { x.* = x + 100; }
|
||||
for xs (*x) { x.* = x + 100; }
|
||||
print("{} {} {}\n", xs[0], xs[1], xs[2]);
|
||||
|
||||
// Pointer subject matches through the deref; payload reads through the ref.
|
||||
shapes : [2]Shape = .[.circle(2.0), .none];
|
||||
for shapes: (*s) {
|
||||
for shapes (*s) {
|
||||
if s == {
|
||||
case .circle: (r) { print("circle {}\n", r); }
|
||||
case .none: { print("none\n"); }
|
||||
|
||||
@@ -10,7 +10,7 @@ Box :: struct {
|
||||
|
||||
sum_ptr :: (xs: *List(s64)) -> s64 {
|
||||
total : s64 = 0;
|
||||
for xs: (n) { total = total + n; } // iterate through a *List
|
||||
for xs (n) { total = total + n; } // iterate through a *List
|
||||
total
|
||||
}
|
||||
|
||||
@@ -21,12 +21,12 @@ main :: () -> s32 {
|
||||
xs.append(30);
|
||||
|
||||
s : s64 = 0;
|
||||
for xs: (n) { s = s + n; } // value capture
|
||||
for xs (n) { s = s + n; } // value capture
|
||||
print("sum {}\n", s); // 60
|
||||
|
||||
for xs: (*n) { n.* = n + 100; } // by-ref: writes back
|
||||
for xs (*n) { n.* = n + 100; } // by-ref: writes back
|
||||
s = 0;
|
||||
for xs: (n) { s = s + n; }
|
||||
for xs (n) { s = s + n; }
|
||||
print("sum2 {}\n", s); // 360
|
||||
|
||||
print("via ptr {}\n", sum_ptr(@xs)); // 360
|
||||
@@ -34,7 +34,7 @@ main :: () -> s32 {
|
||||
bs := List(Box).{};
|
||||
bs.append(.{ v = 7 });
|
||||
bt : s64 = 0;
|
||||
for bs: (*b) { bt = bt + b.boxed(); } // *Box receiver, value-self method
|
||||
for bs (*b) { bt = bt + b.boxed(); } // *Box receiver, value-self method
|
||||
print("boxes {}\n", bt); // 7
|
||||
0
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ main :: () {
|
||||
// For loop basic
|
||||
farr : [4]s32 = .[10, 20, 30, 40];
|
||||
out("for:");
|
||||
for farr: (it) {
|
||||
for farr (it) {
|
||||
out(" ");
|
||||
out(int_to_string(it));
|
||||
}
|
||||
@@ -126,14 +126,14 @@ main :: () {
|
||||
|
||||
// For with print
|
||||
out("for-print:");
|
||||
for farr: (it) {
|
||||
for farr (it) {
|
||||
print(" {}", it);
|
||||
}
|
||||
out("\n");
|
||||
|
||||
// For with index
|
||||
out("for-idx:");
|
||||
for farr: (_, ix) {
|
||||
for farr, 0.. (_, ix) {
|
||||
out(" ");
|
||||
out(int_to_string(ix));
|
||||
}
|
||||
@@ -141,14 +141,14 @@ main :: () {
|
||||
|
||||
// For with print two args
|
||||
out("for-2arg:");
|
||||
for farr: (it, ix) {
|
||||
for farr, 0.. (it, ix) {
|
||||
print(" {}@{}", it, ix);
|
||||
}
|
||||
out("\n");
|
||||
|
||||
// For with break
|
||||
out("for-break:");
|
||||
for farr: (it) {
|
||||
for farr (it) {
|
||||
if it == 30 { break; }
|
||||
print(" {}", it);
|
||||
}
|
||||
@@ -156,7 +156,7 @@ main :: () {
|
||||
|
||||
// For with continue
|
||||
out("for-continue:");
|
||||
for farr: (it) {
|
||||
for farr (it) {
|
||||
if it == 20 { continue; }
|
||||
print(" {}", it);
|
||||
}
|
||||
@@ -165,14 +165,14 @@ main :: () {
|
||||
// For on slice
|
||||
fsl : []s32 = .[10, 20, 30];
|
||||
out("for-slice:");
|
||||
for fsl: (it) {
|
||||
for fsl (it) {
|
||||
print(" {}", it);
|
||||
}
|
||||
out("\n");
|
||||
|
||||
// For on slice with index
|
||||
out("for-slice-idx:");
|
||||
for fsl: (it, ix) {
|
||||
for fsl, 0.. (it, ix) {
|
||||
print(" {}:{}", ix, it);
|
||||
}
|
||||
out("\n");
|
||||
@@ -181,8 +181,8 @@ main :: () {
|
||||
nf_a : [2]s32 = .[0, 1];
|
||||
nf_b : [2]s32 = .[0, 1];
|
||||
out("for-nested:");
|
||||
for nf_a: (oa) {
|
||||
for nf_b: (ob) {
|
||||
for nf_a (oa) {
|
||||
for nf_b (ob) {
|
||||
print(" ({},{})", oa, ob);
|
||||
}
|
||||
}
|
||||
@@ -191,7 +191,7 @@ main :: () {
|
||||
// For with break preserving index
|
||||
fbi : [5]s32 = .[10, 20, 30, 40, 50];
|
||||
fbi_idx := 0;
|
||||
for fbi: (it, ix) {
|
||||
for fbi, 0.. (it, ix) {
|
||||
if it == 30 { fbi_idx = ix; break; }
|
||||
}
|
||||
print("for-break-idx: {}\n", fbi_idx);
|
||||
|
||||
@@ -16,7 +16,7 @@ pair_add :: (a: $T, b: $U) -> s64 {
|
||||
|
||||
typed_sum :: (..args: []s32) -> s32 {
|
||||
result := 0;
|
||||
for args: (it) { result = result + it; }
|
||||
for args (it) { result = result + it; }
|
||||
result
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
main :: () -> s32 {
|
||||
sum := 0;
|
||||
for 0..1000000: (i) {
|
||||
for 0..1000000 (i) {
|
||||
buf : [128]s64 = ---;
|
||||
buf[0] = i;
|
||||
sum += buf[0];
|
||||
@@ -18,8 +18,8 @@ main :: () -> s32 {
|
||||
print("sum={}\n", sum);
|
||||
|
||||
n := 0;
|
||||
for 0..3000000: (i) {
|
||||
for 0..1: (j) { n += 1; }
|
||||
for 0..3000000 (i) {
|
||||
for 0..1 (j) { n += 1; }
|
||||
}
|
||||
print("n={}\n", n);
|
||||
0
|
||||
|
||||
@@ -13,12 +13,12 @@ main :: () -> s32 {
|
||||
i := 0;
|
||||
while i < 4096 { arr[i] = i; i += 1; }
|
||||
sum := 0;
|
||||
for arr: (x) { sum += x; }
|
||||
for arr (x) { sum += x; }
|
||||
print("sum={}\n", sum);
|
||||
|
||||
// By-value capture is a copy: mutating it leaves the array untouched.
|
||||
small : [3]s64 = .[10, 20, 30];
|
||||
for small: (x) { x += 100; }
|
||||
for small (x) { x += 100; }
|
||||
print("copy-guard: {} {} {}\n", small[0], small[1], small[2]);
|
||||
0
|
||||
}
|
||||
|
||||
@@ -7,14 +7,14 @@
|
||||
#import "modules/std.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
for 0..3: (i) {
|
||||
for 0..3 (i) {
|
||||
defer print("cleanup {}\n", i);
|
||||
if i == 1 { break; }
|
||||
print("body {}\n", i);
|
||||
}
|
||||
print("after break loop\n");
|
||||
|
||||
for 0..3: (i) {
|
||||
for 0..3 (i) {
|
||||
defer print("c2 {}\n", i);
|
||||
if i == 1 { continue; }
|
||||
print("b2 {}\n", i);
|
||||
@@ -33,7 +33,7 @@ main :: () -> s32 {
|
||||
|
||||
// A break inside a nested block drains the nested block's defers AND the
|
||||
// loop body's, in LIFO order.
|
||||
for 0..2: (j) {
|
||||
for 0..2 (j) {
|
||||
defer print("outer {}\n", j);
|
||||
{
|
||||
defer print("inner {}\n", j);
|
||||
|
||||
54
examples/0050-basic-for-multi-iterable.sx
Normal file
54
examples/0050-basic-for-multi-iterable.sx
Normal file
@@ -0,0 +1,54 @@
|
||||
#import "modules/std.sx";
|
||||
|
||||
pair_sum :: (xs: []s64, ys: []s64) -> s64 {
|
||||
total := 0;
|
||||
for xs, ys (x, y) { total += x * y; }
|
||||
total
|
||||
}
|
||||
|
||||
make :: () -> [3]s64 {
|
||||
r : [3]s64 = .[7, 8, 9];
|
||||
r
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
// Agra's example: a 1..5 inclusive, b open-ended following along.
|
||||
for 1..=5, 0.. (a, b) { print("{}:{} ", a, b); }
|
||||
print("\n");
|
||||
|
||||
// Index idiom replacing the old (x, i) form.
|
||||
xs : [3]s64 = .[10, 20, 30];
|
||||
for xs, 0.. (x, i) { print("[{}]={} ", i, x); }
|
||||
print("\n");
|
||||
|
||||
// Parallel slices.
|
||||
a4 : [4]s64 = .[1, 2, 3, 4];
|
||||
b4 : [4]s64 = .[10, 20, 30, 40];
|
||||
print("dot={}\n", pair_sum(a4, b4));
|
||||
|
||||
// Arrow bodies.
|
||||
s := 0;
|
||||
for 0..4 (i) => s += i;
|
||||
print("arrow-range s={}\n", s);
|
||||
t := 0;
|
||||
for xs (x) => t += x;
|
||||
print("arrow-coll t={}\n", t);
|
||||
|
||||
// Call iterable + capture (first group = args, last group = capture).
|
||||
for make() (v) { print("v{} ", v); }
|
||||
print("\n");
|
||||
|
||||
// No-capture call iterable via leading-group escape.
|
||||
n := 0;
|
||||
for (make()) { n += 1; }
|
||||
print("escape n={}\n", n);
|
||||
|
||||
// Three-way zip: two collections + cursor.
|
||||
for a4, b4, 100.. (p, q, k) { print("{}/{}/{} ", p, q, k); }
|
||||
print("\n");
|
||||
|
||||
// By-ref capture in a multi-iterable header.
|
||||
for a4, 0.. (*p, i) { p.* += i; }
|
||||
print("after ref: {} {} {} {}\n", a4[0], a4[1], a4[2], a4[3]);
|
||||
0
|
||||
}
|
||||
@@ -16,7 +16,7 @@ main :: () {
|
||||
spts : [2]Point = .[Point.{1, 2}, Point.{3, 4}];
|
||||
spt2 := spts[1];
|
||||
print("arr-struct-x: {}\n", spt2.x);
|
||||
for spts: (it) {
|
||||
for spts (it) {
|
||||
print("for-struct: {}\n", it);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,6 @@ main :: () {
|
||||
mk : Make(N, s64) = ---; mk[2] = 33; print("vp.typefn.expr: len={} v={}\n", mk.len, mk[2]);
|
||||
|
||||
// inline-for bound — expr const (3) and integral float (4)
|
||||
s := 0; inline for 0..N: (i) { s += i; } print("for.expr: {}\n", s); // 0+1+2 = 3
|
||||
t := 0; inline for 0..F: (i) { t += i; } print("for.float: {}\n", t); // 0+1+2+3 = 6
|
||||
s := 0; inline for 0..N (i) { s += i; } print("for.expr: {}\n", s); // 0+1+2 = 3
|
||||
t := 0; inline for 0..F (i) { t += i; } print("for.float: {}\n", t); // 0+1+2+3 = 6
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ main :: () -> s32 {
|
||||
|
||||
// for capture + index names
|
||||
xs := [3]s64.{ 10, 20, 30 };
|
||||
for xs: (`bool, `u16) {
|
||||
for xs, 0.. (`bool, `u16) {
|
||||
print("for = {} @ {}\n", `bool, `u16);
|
||||
}
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ pair_add :: (a: $T, b: $U) -> s64 {
|
||||
|
||||
typed_sum :: (..args: []s32) -> s32 {
|
||||
result := 0;
|
||||
for args: (it) { result = result + it; }
|
||||
for args (it) { result = result + it; }
|
||||
result
|
||||
}
|
||||
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
|
||||
sum :: (..args: []s32) -> s32 {
|
||||
result := 0;
|
||||
for args: (it) {
|
||||
for args (it) {
|
||||
result = result + it;
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
print_all :: (..args: []s32) {
|
||||
for args: (it) {
|
||||
for args (it) {
|
||||
out(int_to_string(it));
|
||||
out(" ");
|
||||
}
|
||||
@@ -26,7 +26,7 @@ main :: () -> s32 {
|
||||
out(int_to_string(sum(..arr)));
|
||||
out("\n");
|
||||
|
||||
for arr: (it) {
|
||||
for arr (it) {
|
||||
out(int_to_string(it));
|
||||
out(" ");
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ Point :: struct {
|
||||
|
||||
// Print all arguments — accepts any type, dispatches via type-switch
|
||||
print_any :: (..args: []Any) {
|
||||
for args: (it) {
|
||||
for args (it) {
|
||||
type := type_of(it);
|
||||
if type == {
|
||||
case int: out(int_to_string(cast(s32) it));
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
// constant (a literal, or an `inline for` cursor). A runtime index (here a
|
||||
// `while`-loop counter) must produce a clear diagnostic, not the confusing
|
||||
// "unresolved 'args'" the slice-index fall-through used to give. To walk a
|
||||
// pack, use `inline for 0..args.len: (i) { ... }`, which unrolls so each
|
||||
// pack, use `inline for 0..args.len (i) { ... }`, which unrolls so each
|
||||
// `args[i]` is a comptime index.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
@@ -14,7 +14,7 @@ sink :: (v: s64) -> void { _ = v; }
|
||||
storage :: (..xs: Show) -> void { y := xs; _ = y; } // A: store
|
||||
call :: (..xs: Show) -> void { sink(xs); } // B: pass to a call
|
||||
ret :: (..xs: Show) -> s64 { return xs; } // C: return
|
||||
iter :: (..xs: Show) -> void { for xs : (x) { _ = x; } } // D: runtime iterate
|
||||
iter :: (..xs: Show) -> void { for xs (x) { _ = x; } } // D: runtime iterate
|
||||
|
||||
main :: () -> s32 {
|
||||
storage(A.{});
|
||||
|
||||
@@ -14,10 +14,10 @@ M :: 3;
|
||||
|
||||
main :: () {
|
||||
s := 0;
|
||||
inline for 0..M: (i) { s += i; }
|
||||
inline for 0..M (i) { s += i; }
|
||||
print("sum 0..M = {}\n", s); // 0 + 1 + 2 = 3
|
||||
|
||||
t := 0;
|
||||
inline for 0..(M + 1): (i) { t += i; }
|
||||
inline for 0..(M + 1) (i) { t += i; }
|
||||
print("sum 0..(M+1) = {}\n", t); // 0 + 1 + 2 + 3 = 6
|
||||
}
|
||||
|
||||
@@ -9,6 +9,6 @@ M :: 3.0;
|
||||
|
||||
main :: () {
|
||||
s := 0;
|
||||
inline for 0..M: (i) { s += i; }
|
||||
inline for 0..M (i) { s += i; }
|
||||
print("sum 0..M = {}\n", s); // 0 + 1 + 2 = 3
|
||||
}
|
||||
|
||||
@@ -11,14 +11,14 @@
|
||||
|
||||
main :: () {
|
||||
s := 0;
|
||||
inline for -2..1: (i) { s += i; }
|
||||
inline for -2..1 (i) { s += i; }
|
||||
print("inline for -2..1 sum = {}\n", s); // -2 + -1 + 0 = -3
|
||||
|
||||
r := 0;
|
||||
for -2..1: (i) { r += i; }
|
||||
for -2..1 (i) { r += i; }
|
||||
print("for -2..1 sum = {}\n", r); // -2 + -1 + 0 = -3 (runtime parity)
|
||||
|
||||
e := 0;
|
||||
inline for 0..(-2.0): (i) { e += i; }
|
||||
inline for 0..(-2.0) (i) { e += i; }
|
||||
print("inline for 0..(-2.0) sum = {}\n", e); // empty range -> 0 iterations
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
combine :: (x: s64, y: s64) -> s64 { return x + y; }
|
||||
pick :: (..xs: []s64) -> s64 {
|
||||
result := 0;
|
||||
for xs: (it) { result = result + it; }
|
||||
for xs (it) { result = result + it; }
|
||||
result
|
||||
}
|
||||
from_a_combine :: () -> s64 { return combine(10, 20); }
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// pack, pick subtracts its two fixed args.
|
||||
combine :: (..xs: []s64) -> s64 {
|
||||
result := 0;
|
||||
for xs: (it) { result = result + it; }
|
||||
for xs (it) { result = result + it; }
|
||||
result
|
||||
}
|
||||
pick :: (a: s64, b: s64) -> s64 { return b - a; }
|
||||
|
||||
@@ -17,7 +17,7 @@ g :: () -> !E { return; }
|
||||
f :: () -> !E {
|
||||
defer { return; } // ERROR: return in defer body
|
||||
onfail { try g(); } // ERROR: try in onfail body
|
||||
defer { for 0..1: (i) { break; } } // ERROR: break in defer body (transitive through loop)
|
||||
defer { for 0..1 (i) { break; } } // ERROR: break in defer body (transitive through loop)
|
||||
onfail e { if e == error.Bad { continue; } } // ERROR: continue in onfail body
|
||||
try g();
|
||||
return;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// A by-reference loop capture (`for xs: (*m)`) binds `m` to a `*T`.
|
||||
// A by-reference loop capture (`for xs (*m)`) binds `m` to a `*T`.
|
||||
// Passing it where a `T` value is expected used to slip through to the
|
||||
// LLVM verifier ("Call parameter type does not match function signature").
|
||||
// The compiler now reports it at the call site with a fix-it: write `m.*`.
|
||||
@@ -11,7 +11,7 @@ take :: (m: Move) -> s64 { return m.flag; }
|
||||
|
||||
main :: () -> s32 {
|
||||
moves : [2]Move = .[ Move.{ flag = 1 }, Move.{ flag = 2 } ];
|
||||
for moves: (*m) {
|
||||
for moves (*m) {
|
||||
take(m);
|
||||
}
|
||||
return 0;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Passing a `*T` where a `T` value is expected is caught at the call site —
|
||||
// not only for `for xs: (*m)` loop captures (see 215) but for any pointer,
|
||||
// not only for `for xs (*m)` loop captures (see 215) but for any pointer,
|
||||
// here a `*Move` parameter forwarded into a by-value parameter. Without the
|
||||
// check this slipped through to the LLVM verifier as "Call parameter type
|
||||
// does not match function signature".
|
||||
|
||||
@@ -19,8 +19,8 @@ main :: () -> s32 {
|
||||
if u8 := maybe() { } // if optional binding
|
||||
while s16 := maybe() { break; } // while optional binding
|
||||
xs := [3]s64.{ 10, 20, 30 };
|
||||
for xs: (bool) { } // for capture name
|
||||
for xs: (v, s32) { } // for index name
|
||||
for xs (bool) { } // for capture name
|
||||
for xs, 0.. (v, s32) { } // for index name
|
||||
opt: ?s64 = 5;
|
||||
r := if opt == { // match-arm capture
|
||||
case .some: (string) { 0 }
|
||||
|
||||
@@ -9,6 +9,6 @@
|
||||
|
||||
main :: () {
|
||||
s := 0;
|
||||
inline for 0..4.5: (i) { s += i; }
|
||||
inline for 0..4.5 (i) { s += i; }
|
||||
print("unreachable: {}\n", s);
|
||||
}
|
||||
|
||||
9
examples/1149-diagnostics-for-colon-removed.sx
Normal file
9
examples/1149-diagnostics-for-colon-removed.sx
Normal file
@@ -0,0 +1,9 @@
|
||||
// The pre-multi-iterable `for xs: (x)` syntax (colon before the capture) is
|
||||
// rejected with a migration hint.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
main :: () {
|
||||
xs : [2]s64 = .[1, 2];
|
||||
for xs: (x) { }
|
||||
}
|
||||
9
examples/1150-diagnostics-for-capture-arity.sx
Normal file
9
examples/1150-diagnostics-for-capture-arity.sx
Normal file
@@ -0,0 +1,9 @@
|
||||
// A for capture group is positional: one capture per iterable (or none).
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
main :: () {
|
||||
xs : [2]s64 = .[1, 2];
|
||||
ys : [2]s64 = .[3, 4];
|
||||
for xs, ys (x) { }
|
||||
}
|
||||
8
examples/1151-diagnostics-for-open-first.sx
Normal file
8
examples/1151-diagnostics-for-open-first.sx
Normal file
@@ -0,0 +1,8 @@
|
||||
// The FIRST iterable drives the loop, so it must be bounded; an open range
|
||||
// `a..` may only follow it.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
main :: () {
|
||||
for 0.. (i) { }
|
||||
}
|
||||
7
examples/1152-diagnostics-for-inclusive-open.sx
Normal file
7
examples/1152-diagnostics-for-inclusive-open.sx
Normal file
@@ -0,0 +1,7 @@
|
||||
// `..=` is the inclusive bounded form — it requires an end expression.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
main :: () {
|
||||
for 0..= (i) { }
|
||||
}
|
||||
8
examples/1153-diagnostics-for-range-by-ref.sx
Normal file
8
examples/1153-diagnostics-for-range-by-ref.sx
Normal file
@@ -0,0 +1,8 @@
|
||||
// Range elements are synthesized values with no storage — `*` capture is
|
||||
// rejected.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
main :: () {
|
||||
for 0..3 (*i) { }
|
||||
}
|
||||
11
examples/1154-diagnostics-for-call-needs-capture.sx
Normal file
11
examples/1154-diagnostics-for-call-needs-capture.sx
Normal file
@@ -0,0 +1,11 @@
|
||||
// In a for header the trailing paren group is the CAPTURE; a call iterable
|
||||
// therefore needs one too. `()` cannot be a capture — parse error with the
|
||||
// hint (`for f(n) (x) { }` / `for (f(n)) { }` / bind it first).
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
g :: () -> s64 { 1 }
|
||||
|
||||
main :: () {
|
||||
for g() { }
|
||||
}
|
||||
12
examples/1155-diagnostics-for-not-iterable.sx
Normal file
12
examples/1155-diagnostics-for-not-iterable.sx
Normal file
@@ -0,0 +1,12 @@
|
||||
// The collision case of the positional capture rule: `for f(n) { }` reads
|
||||
// `(n)` as the capture, making the iterable `f` itself — not iterable, with
|
||||
// the parenthesize/add-capture hint.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
f :: (n: s64) -> s64 { n }
|
||||
|
||||
main :: () {
|
||||
n := 1;
|
||||
for f(n) { }
|
||||
}
|
||||
1
examples/expected/0050-basic-for-multi-iterable.exit
Normal file
1
examples/expected/0050-basic-for-multi-iterable.exit
Normal file
@@ -0,0 +1 @@
|
||||
0
|
||||
1
examples/expected/0050-basic-for-multi-iterable.stderr
Normal file
1
examples/expected/0050-basic-for-multi-iterable.stderr
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
9
examples/expected/0050-basic-for-multi-iterable.stdout
Normal file
9
examples/expected/0050-basic-for-multi-iterable.stdout
Normal file
@@ -0,0 +1,9 @@
|
||||
1:0 2:1 3:2 4:3 5:4
|
||||
[0]=10 [1]=20 [2]=30
|
||||
dot=300
|
||||
arrow-range s=6
|
||||
arrow-coll t=60
|
||||
v7 v8 v9
|
||||
escape n=3
|
||||
1/10/100 2/20/101 3/30/102 4/40/103
|
||||
after ref: 1 3 5 7
|
||||
@@ -1,5 +1,5 @@
|
||||
error: pack 'xs' has no runtime value — a pack is comptime-only and can't be used as a value here
|
||||
--> /Users/agra/projects/sx/examples/0536-packs-pack-as-value.sx:14:40
|
||||
--> examples/0536-packs-pack-as-value.sx:14:40
|
||||
|
|
||||
14 | storage :: (..xs: Show) -> void { y := xs; _ = y; } // A: store
|
||||
| ^^
|
||||
@@ -10,7 +10,7 @@ help: to store it, materialize a tuple: `(..xs)`
|
||||
| ^^
|
||||
|
||||
error: pack 'xs' has no runtime value — a pack is comptime-only and can't be used as a value here
|
||||
--> /Users/agra/projects/sx/examples/0536-packs-pack-as-value.sx:15:40
|
||||
--> examples/0536-packs-pack-as-value.sx:15:40
|
||||
|
|
||||
15 | call :: (..xs: Show) -> void { sink(xs); } // B: pass to a call
|
||||
| ^^
|
||||
@@ -21,7 +21,7 @@ help: materialize a tuple `(..xs)` to store it, or `xx xs` to convert it to an e
|
||||
| ^^
|
||||
|
||||
error: pack 'xs' has no runtime value — a pack is comptime-only and can't be used as a value here
|
||||
--> /Users/agra/projects/sx/examples/0536-packs-pack-as-value.sx:16:42
|
||||
--> examples/0536-packs-pack-as-value.sx:16:42
|
||||
|
|
||||
16 | ret :: (..xs: Show) -> s64 { return xs; } // C: return
|
||||
| ^^
|
||||
@@ -32,12 +32,12 @@ help: to return it, return a tuple `(..xs)` and make the return type that tuple
|
||||
| ^^
|
||||
|
||||
error: pack 'xs' has no runtime value — a pack is comptime-only and can't be used as a value here
|
||||
--> /Users/agra/projects/sx/examples/0536-packs-pack-as-value.sx:17:39
|
||||
--> examples/0536-packs-pack-as-value.sx:17:39
|
||||
|
|
||||
17 | iter :: (..xs: Show) -> void { for xs : (x) { _ = x; } } // D: runtime iterate
|
||||
17 | iter :: (..xs: Show) -> void { for xs (x) { _ = x; } } // D: runtime iterate
|
||||
| ^^
|
||||
|
||||
help: to iterate at comptime use `inline for 0..xs.len (i)`; for a runtime loop declare it as `..xs: []P` (a protocol slice) instead of a pack
|
||||
|
|
||||
17 | iter :: (..xs: Show) -> void { for xs : (x) { _ = x; } } // D: runtime iterate
|
||||
17 | iter :: (..xs: Show) -> void { for xs (x) { _ = x; } } // D: runtime iterate
|
||||
| ^^
|
||||
|
||||
@@ -17,16 +17,16 @@ error: 's16' is a reserved type name and cannot be used as an identifier
|
||||
| ^^^
|
||||
|
||||
error: 'bool' is a reserved type name and cannot be used as an identifier
|
||||
--> examples/1121-diagnostics-reserved-name-control-flow.sx:22:14
|
||||
--> examples/1121-diagnostics-reserved-name-control-flow.sx:22:13
|
||||
|
|
||||
22 | for xs: (bool) { } // for capture name
|
||||
| ^^^^
|
||||
22 | for xs (bool) { } // for capture name
|
||||
| ^^^^
|
||||
|
||||
error: 's32' is a reserved type name and cannot be used as an identifier
|
||||
--> examples/1121-diagnostics-reserved-name-control-flow.sx:23:17
|
||||
--> examples/1121-diagnostics-reserved-name-control-flow.sx:23:21
|
||||
|
|
||||
23 | for xs: (v, s32) { } // for index name
|
||||
| ^^^
|
||||
23 | for xs, 0.. (v, s32) { } // for index name
|
||||
| ^^^
|
||||
|
||||
error: 'string' is a reserved type name and cannot be used as an identifier
|
||||
--> examples/1121-diagnostics-reserved-name-control-flow.sx:26:22
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
error: inline for: range end is not a compile-time integer
|
||||
--> examples/1138-diagnostics-inline-for-non-integral-bound.sx:12:19
|
||||
|
|
||||
12 | inline for 0..4.5: (i) { s += i; }
|
||||
12 | inline for 0..4.5 (i) { s += i; }
|
||||
| ^^^
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,5 @@
|
||||
error: for-loop syntax: the ':' before the capture was removed — write `for xs (x) { }` (index via `for xs, 0.. (x, i)`)
|
||||
--> examples/1149-diagnostics-for-colon-removed.sx:8:11
|
||||
|
|
||||
8 | for xs: (x) { }
|
||||
| ^
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,5 @@
|
||||
error: for capture count must match the iterable count — one capture per iterable
|
||||
--> examples/1150-diagnostics-for-capture-arity.sx:8:20
|
||||
|
|
||||
8 | for xs, ys (x) { }
|
||||
| ^
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
1
examples/expected/1151-diagnostics-for-open-first.exit
Normal file
1
examples/expected/1151-diagnostics-for-open-first.exit
Normal file
@@ -0,0 +1 @@
|
||||
1
|
||||
5
examples/expected/1151-diagnostics-for-open-first.stderr
Normal file
5
examples/expected/1151-diagnostics-for-open-first.stderr
Normal file
@@ -0,0 +1,5 @@
|
||||
error: the first iterable must have a bounded length (it drives the loop) — an open range 'a..' may only follow it
|
||||
--> examples/1151-diagnostics-for-open-first.sx:7:17
|
||||
|
|
||||
7 | for 0.. (i) { }
|
||||
| ^
|
||||
1
examples/expected/1151-diagnostics-for-open-first.stdout
Normal file
1
examples/expected/1151-diagnostics-for-open-first.stdout
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,5 @@
|
||||
error: '..=' requires an end expression — the open form is 'a..'
|
||||
--> examples/1152-diagnostics-for-inclusive-open.sx:6:14
|
||||
|
|
||||
6 | for 0..= (i) { }
|
||||
| ^
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
1
examples/expected/1153-diagnostics-for-range-by-ref.exit
Normal file
1
examples/expected/1153-diagnostics-for-range-by-ref.exit
Normal file
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,5 @@
|
||||
error: a range element cannot be captured by reference
|
||||
--> examples/1153-diagnostics-for-range-by-ref.sx:7:19
|
||||
|
|
||||
7 | for 0..3 (*i) { }
|
||||
| ^
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,5 @@
|
||||
error: expected capture variable name (a call iterable also needs a capture: `for f(n) (x) { }`)
|
||||
--> examples/1154-diagnostics-for-call-needs-capture.sx:10:11
|
||||
|
|
||||
10 | for g() { }
|
||||
| ^
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
1
examples/expected/1155-diagnostics-for-not-iterable.exit
Normal file
1
examples/expected/1155-diagnostics-for-not-iterable.exit
Normal file
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,5 @@
|
||||
error: cannot iterate this expression — if the parens were call arguments, a call iterable also needs a capture (`for f(n) (x) { }`) or parentheses (`for (f(n)) { }`)
|
||||
--> examples/1155-diagnostics-for-not-iterable.sx:11:9
|
||||
|
|
||||
11 | for f(n) { }
|
||||
| ^
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -340,9 +340,11 @@ if 0 <= x <= 100 { ... }
|
||||
// While
|
||||
while i < 10 { i += 1; }
|
||||
|
||||
// For (arrays and slices)
|
||||
for items: (val) { print("{}\n", val); }
|
||||
for items: (val, idx) { print("[{}] = {}\n", idx, val); }
|
||||
// For — collections, ranges, and parallel iteration
|
||||
for items (val) { print("{}\n", val); }
|
||||
for items, 0.. (val, idx) { print("[{}] = {}\n", idx, val); }
|
||||
for 1..=5, 0.. (a, b) { print("{}:{}\n", a, b); } // a: 1..5, b follows
|
||||
for items (val) => total += val; // arrow body
|
||||
|
||||
// Defer
|
||||
f := open("file.txt");
|
||||
|
||||
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
|
||||
|
||||
54
src/ast.zig
54
src/ast.zig
@@ -642,29 +642,41 @@ pub const WhileExpr = struct {
|
||||
binding_is_raw: bool = false,
|
||||
};
|
||||
|
||||
pub const ForExpr = struct {
|
||||
iterable: *Node,
|
||||
body: *Node,
|
||||
capture_name: []const u8,
|
||||
capture_span: ?Span = null, // span of `capture_name` (null when omitted, e.g. `for 0..N { }`)
|
||||
/// True when `capture_name` was a backtick raw identifier
|
||||
/// (`` for xs: (`s2) ``) — exempt from the reserved-type-name check.
|
||||
capture_is_raw: bool = false,
|
||||
index_name: ?[]const u8 = null,
|
||||
index_span: ?Span = null, // span of `index_name` (set iff `index_name` is)
|
||||
/// True when `index_name` was a backtick raw identifier
|
||||
/// (`` for xs: (x, `s2) ``) — exempt from the reserved-type-name check.
|
||||
index_is_raw: bool = false,
|
||||
/// Range form `for start..end (i) { }`: `iterable` is the start, `range_end`
|
||||
/// the (exclusive) end. Null for the iterate-a-collection form
|
||||
/// (`for coll : (x) { }`). For the range form `capture_name` is the cursor
|
||||
/// (empty when omitted, `for 0..N { }`).
|
||||
/// One position of a (possibly multi-iterable) `for` header.
|
||||
pub const ForIterable = struct {
|
||||
/// Collection expression, or the range START for the range forms.
|
||||
expr: *Node,
|
||||
/// `a..b` / `a..=b` end. Null for a plain collection AND for the
|
||||
/// open-ended range `a..` (distinguished by `is_range`).
|
||||
range_end: ?*Node = null,
|
||||
/// `inline for` — comptime-unrolled (range bounds must be comptime).
|
||||
/// True for any range form (`a..`, `a..b`, `a..=b`).
|
||||
is_range: bool = false,
|
||||
/// `a..=b` — end is inclusive.
|
||||
inclusive: bool = false,
|
||||
};
|
||||
|
||||
/// One capture of a `for` header: `(x)`, `(*x)`, `(x, y, ...)`.
|
||||
pub const ForCapture = struct {
|
||||
name: []const u8,
|
||||
span: ?Span = null,
|
||||
/// True when the name was a backtick raw identifier (`` for xs (`s2) ``)
|
||||
/// — exempt from the reserved-type-name check.
|
||||
is_raw: bool = false,
|
||||
/// `(*x)` — bind a pointer into the collection (no per-element copy).
|
||||
by_ref: bool = false,
|
||||
};
|
||||
|
||||
/// `for it1, it2, ... (c1, c2, ...) { }` — parallel iteration. The FIRST
|
||||
/// iterable's length drives the loop (first-iterable-wins); the others are
|
||||
/// indexed along it, and a non-first range's end is not consulted. The
|
||||
/// capture group is positional: empty (no bindings) or one capture per
|
||||
/// iterable. The body is a block or an `=> expr;` arrow body.
|
||||
pub const ForExpr = struct {
|
||||
iterables: []ForIterable,
|
||||
captures: []ForCapture,
|
||||
body: *Node,
|
||||
/// `inline for` — comptime-unrolled (single bounded range, comptime bounds).
|
||||
is_inline: bool = false,
|
||||
/// `for xs: (*x)` — bind `x` to a pointer into the collection (no per-element
|
||||
/// copy) rather than a value copy of each element.
|
||||
capture_by_ref: bool = false,
|
||||
};
|
||||
|
||||
pub const SpreadExpr = struct {
|
||||
|
||||
@@ -61,8 +61,10 @@ pub const ErrorAnalysis = struct {
|
||||
self.collectErrorSites(w.body, tags, edges);
|
||||
},
|
||||
.for_expr => |f| {
|
||||
self.collectErrorSites(f.iterable, tags, edges);
|
||||
if (f.range_end) |re| self.collectErrorSites(re, tags, edges);
|
||||
for (f.iterables) |it| {
|
||||
self.collectErrorSites(it.expr, tags, edges);
|
||||
if (it.range_end) |re| self.collectErrorSites(re, tags, edges);
|
||||
}
|
||||
self.collectErrorSites(f.body, tags, edges);
|
||||
},
|
||||
.return_stmt => |r| if (r.value) |v| self.collectErrorSites(v, tags, edges),
|
||||
@@ -216,8 +218,10 @@ pub const ErrorAnalysis = struct {
|
||||
self.collectClosureShapes(w.body);
|
||||
},
|
||||
.for_expr => |f| {
|
||||
self.collectClosureShapes(f.iterable);
|
||||
if (f.range_end) |re| self.collectClosureShapes(re);
|
||||
for (f.iterables) |it| {
|
||||
self.collectClosureShapes(it.expr);
|
||||
if (it.range_end) |re| self.collectClosureShapes(re);
|
||||
}
|
||||
self.collectClosureShapes(f.body);
|
||||
},
|
||||
.return_stmt => |r| if (r.value) |v| self.collectClosureShapes(v),
|
||||
|
||||
@@ -152,8 +152,10 @@ pub const ErrorFlow = struct {
|
||||
return false;
|
||||
},
|
||||
.for_expr => |fe| {
|
||||
self.flowExpr(fe.iterable, ctx, proven.*);
|
||||
if (fe.range_end) |re| self.flowExpr(re, ctx, proven.*);
|
||||
for (fe.iterables) |it| {
|
||||
self.flowExpr(it.expr, ctx, proven.*);
|
||||
if (it.range_end) |re| self.flowExpr(re, ctx, proven.*);
|
||||
}
|
||||
var loop_proven = self.provenClone(proven.*);
|
||||
_ = self.flowWalk(fe.body, ctx, &loop_proven);
|
||||
return false;
|
||||
|
||||
@@ -1368,7 +1368,6 @@ pub const Lowering = struct {
|
||||
pub const lowerWhile = lower_control_flow.lowerWhile;
|
||||
pub const listView = lower_control_flow.listView;
|
||||
pub const lowerFor = lower_control_flow.lowerFor;
|
||||
pub const lowerRuntimeRangeFor = lower_control_flow.lowerRuntimeRangeFor;
|
||||
pub const lowerInlineRangeFor = lower_control_flow.lowerInlineRangeFor;
|
||||
pub const lowerMatch = lower_control_flow.lowerMatch;
|
||||
pub const lowerBreak = lower_control_flow.lowerBreak;
|
||||
|
||||
@@ -639,9 +639,12 @@ pub fn collectCaptures(self: *Lowering, node: *const Node, param_names: *std.Str
|
||||
self.collectCaptures(de.operand, param_names, captures);
|
||||
},
|
||||
.for_expr => |fe| {
|
||||
self.collectCaptures(fe.iterable, param_names, captures);
|
||||
// Register capture name as local so it's not captured
|
||||
param_names.put(fe.capture_name, {}) catch {};
|
||||
for (fe.iterables) |it| {
|
||||
self.collectCaptures(it.expr, param_names, captures);
|
||||
if (it.range_end) |re| self.collectCaptures(re, param_names, captures);
|
||||
}
|
||||
// Register capture names as locals so they're not captured
|
||||
for (fe.captures) |cap| param_names.put(cap.name, {}) catch {};
|
||||
self.collectCaptures(fe.body, param_names, captures);
|
||||
},
|
||||
.slice_expr => |se| {
|
||||
|
||||
@@ -277,48 +277,110 @@ pub fn listView(self: *Lowering, value: Ref, ty: TypeId) ?struct { data: Ref, da
|
||||
};
|
||||
}
|
||||
|
||||
/// Lowered prep for one position of a multi-iterable `for` header. Every
|
||||
/// position gets its own s64 cursor slot (ranges start at their `start`,
|
||||
/// collections at 0); all cursors advance by 1 per iteration, and ONLY the
|
||||
/// first position's bound terminates the loop (first-iterable-wins).
|
||||
const IterPrep = struct {
|
||||
is_range: bool,
|
||||
slot: Ref,
|
||||
// Collection-only fields:
|
||||
data: Ref = Ref.none,
|
||||
data_ty: TypeId = .unresolved,
|
||||
elem_ty: TypeId = .unresolved,
|
||||
is_array: bool = false,
|
||||
storage: ?Ref = null, // array's own alloca when addressable (not deref'd)
|
||||
};
|
||||
|
||||
/// `for it1, it2, ... (c1, c2, ...) { }` — parallel iteration. The first
|
||||
/// iterable's length/bound drives the loop; the others follow by position.
|
||||
/// Consequences of first-iterable-wins: a non-first range's end is never
|
||||
/// lowered (its side effects do not run), and a shorter non-first collection
|
||||
/// is read past its length on mismatch — the first iterable is the
|
||||
/// authoritative one.
|
||||
pub fn lowerFor(self: *Lowering, fe: *const ast.ForExpr) Ref {
|
||||
if (fe.range_end) |end_node| {
|
||||
if (fe.is_inline) return self.lowerInlineRangeFor(fe, end_node);
|
||||
return self.lowerRuntimeRangeFor(fe, end_node);
|
||||
}
|
||||
// Collection-form `for xs : (x)` over a pack: a pack has no runtime
|
||||
// value to iterate (Decision 1) — point the user at `inline for`.
|
||||
if (fe.iterable.data == .identifier and self.isPackName(fe.iterable.data.identifier.name)) {
|
||||
return self.diagPackAsValue(fe.iterable.data.identifier.name, fe.iterable.span, .runtime_iter);
|
||||
if (fe.is_inline) return self.lowerInlineRangeFor(fe);
|
||||
|
||||
// A pack has no runtime value to iterate (Decision 1) — point the user
|
||||
// at `inline for`.
|
||||
for (fe.iterables) |it| {
|
||||
if (!it.is_range and it.expr.data == .identifier and self.isPackName(it.expr.data.identifier.name)) {
|
||||
return self.diagPackAsValue(it.expr.data.identifier.name, it.expr.span, .runtime_iter);
|
||||
}
|
||||
}
|
||||
|
||||
// Lower iterable + resolve its static type.
|
||||
var iterable = self.lowerExpr(fe.iterable);
|
||||
var iterable_ty = self.inferExprType(fe.iterable);
|
||||
var preps = std.ArrayList(IterPrep).empty;
|
||||
defer preps.deinit(self.alloc);
|
||||
var limit: Ref = Ref.none; // exclusive bound of position 0
|
||||
|
||||
// `*List` / `*[]T` etc. — deref to the collection value. Tracked because
|
||||
// a deref'd iterable's identifier binding holds the POINTER, so its
|
||||
// alloca is not the collection's storage.
|
||||
var was_deref = false;
|
||||
const ptr_info = if (iterable_ty.isBuiltin()) null else self.module.types.get(iterable_ty);
|
||||
if (ptr_info != null and ptr_info.? == .pointer) {
|
||||
iterable = self.builder.load(iterable, ptr_info.?.pointer.pointee);
|
||||
iterable_ty = ptr_info.?.pointer.pointee;
|
||||
was_deref = true;
|
||||
for (fe.iterables, 0..) |it, i| {
|
||||
if (it.is_range) {
|
||||
const start_ref = self.lowerExpr(it.expr);
|
||||
const slot = self.builder.alloca(.s64);
|
||||
self.builder.store(slot, start_ref);
|
||||
if (i == 0) {
|
||||
// Parser guarantees the first iterable is bounded.
|
||||
var end_ref = self.lowerExpr(it.range_end.?);
|
||||
if (it.inclusive) end_ref = self.builder.add(end_ref, self.builder.constInt(1, .s64), .s64);
|
||||
limit = end_ref;
|
||||
}
|
||||
preps.append(self.alloc, .{ .is_range = true, .slot = slot }) catch unreachable;
|
||||
} else {
|
||||
var data = self.lowerExpr(it.expr);
|
||||
var data_ty = self.inferExprType(it.expr);
|
||||
|
||||
// `*List` / `*[]T` etc. — deref to the collection value. Tracked
|
||||
// because a deref'd iterable's identifier binding holds the
|
||||
// POINTER, so its alloca is not the collection's storage.
|
||||
var was_deref = false;
|
||||
const ptr_info = if (data_ty.isBuiltin()) null else self.module.types.get(data_ty);
|
||||
if (ptr_info != null and ptr_info.? == .pointer) {
|
||||
data = self.builder.load(data, ptr_info.?.pointer.pointee);
|
||||
data_ty = ptr_info.?.pointer.pointee;
|
||||
was_deref = true;
|
||||
}
|
||||
|
||||
// A `List(T)`-like struct iterates its `items[0..len]`;
|
||||
// arrays/slices use their intrinsic length.
|
||||
var len: Ref = Ref.none;
|
||||
if (self.listView(data, data_ty)) |lv| {
|
||||
data = lv.data;
|
||||
data_ty = lv.data_ty;
|
||||
len = lv.len;
|
||||
} else if (i == 0) {
|
||||
len = self.builder.emit(.{ .length = .{ .operand = data } }, .s64);
|
||||
}
|
||||
|
||||
const elem_ty = self.getElementType(data_ty);
|
||||
if (elem_ty == .unresolved) {
|
||||
// Not a collection. The common trip: `for f(n) { }` — the
|
||||
// trailing parens are the CAPTURE, so the iterable is `f`.
|
||||
if (self.diagnostics) |d| {
|
||||
if (data_ty == .unresolved) {
|
||||
d.addFmt(.err, it.expr.span, "cannot iterate this expression — if the parens were call arguments, a call iterable also needs a capture (`for f(n) (x) {{ }}`) or parentheses (`for (f(n)) {{ }}`)", .{});
|
||||
} else {
|
||||
d.addFmt(.err, it.expr.span, "cannot iterate a value of type '{s}' — if the parens were call arguments, a call iterable also needs a capture (`for f(n) (x) {{ }}`) or parentheses (`for (f(n)) {{ }}`)", .{self.module.types.typeName(data_ty)});
|
||||
}
|
||||
}
|
||||
return self.builder.constInt(0, .void);
|
||||
}
|
||||
const is_array = !data_ty.isBuiltin() and self.module.types.get(data_ty) == .array;
|
||||
const storage = if (is_array and !was_deref) self.getExprAlloca(it.expr) else null;
|
||||
const slot = self.builder.alloca(.s64);
|
||||
self.builder.store(slot, self.builder.constInt(0, .s64));
|
||||
if (i == 0) limit = len;
|
||||
preps.append(self.alloc, .{
|
||||
.is_range = false,
|
||||
.slot = slot,
|
||||
.data = data,
|
||||
.data_ty = data_ty,
|
||||
.elem_ty = elem_ty,
|
||||
.is_array = is_array,
|
||||
.storage = storage,
|
||||
}) catch unreachable;
|
||||
}
|
||||
}
|
||||
|
||||
// A `List(T)`-like struct iterates its `items[0..len]`; arrays/slices
|
||||
// use their intrinsic length.
|
||||
var len: Ref = undefined;
|
||||
if (self.listView(iterable, iterable_ty)) |lv| {
|
||||
iterable = lv.data;
|
||||
iterable_ty = lv.data_ty;
|
||||
len = lv.len;
|
||||
} else {
|
||||
len = self.builder.emit(.{ .length = .{ .operand = iterable } }, .s64);
|
||||
}
|
||||
|
||||
// Create index variable
|
||||
const idx_slot = self.builder.alloca(.s64);
|
||||
const zero = self.builder.constInt(0, .s64);
|
||||
self.builder.store(idx_slot, zero);
|
||||
|
||||
const header_bb = self.freshBlock("for.hdr");
|
||||
const body_bb = self.freshBlock("for.body");
|
||||
const inc_bb = self.freshBlock("for.inc");
|
||||
@@ -326,49 +388,44 @@ pub fn lowerFor(self: *Lowering, fe: *const ast.ForExpr) Ref {
|
||||
|
||||
self.builder.br(header_bb, &.{});
|
||||
|
||||
// Header: compare index < length
|
||||
// Header: first cursor against the first bound.
|
||||
self.builder.switchToBlock(header_bb);
|
||||
const idx_val = self.builder.load(idx_slot, .s64);
|
||||
const cmp = self.builder.cmpLt(idx_val, len);
|
||||
const cur0 = self.builder.load(preps.items[0].slot, .s64);
|
||||
const cmp = self.builder.cmpLt(cur0, limit);
|
||||
self.builder.condBr(cmp, body_bb, &.{}, exit_bb, &.{});
|
||||
|
||||
// Body
|
||||
// Body: bind one capture per position (when captures are present).
|
||||
self.builder.switchToBlock(body_bb);
|
||||
|
||||
// Bind element — resolve element type from iterable. `for xs: (*x)`
|
||||
// binds a pointer into the collection (no per-element copy); `(x)`
|
||||
// binds a value copy.
|
||||
const elem_ty = self.getElementType(iterable_ty);
|
||||
const bind_ty = if (fe.capture_by_ref) self.module.types.ptrTo(elem_ty) else elem_ty;
|
||||
const is_array = !iterable_ty.isBuiltin() and self.module.types.get(iterable_ty) == .array;
|
||||
const elem = if (fe.capture_by_ref) blk: {
|
||||
// A slice value carries its backing pointer, so GEP on it writes
|
||||
// through. An array is a value — GEP needs its storage (alloca) or
|
||||
// mutations would hit a copy.
|
||||
const base = if (is_array) (self.getExprAlloca(fe.iterable) orelse iterable) else iterable;
|
||||
break :blk self.builder.emit(.{ .index_gep = .{ .lhs = base, .rhs = idx_val } }, bind_ty);
|
||||
} else blk: {
|
||||
// By-value over an array with addressable storage: GEP + load ONE
|
||||
// element. `index_get` on the array VALUE spills the whole array to
|
||||
// a temp on every iteration — O(N²) bytes copied per loop.
|
||||
if (is_array and !was_deref) {
|
||||
if (self.getExprAlloca(fe.iterable)) |storage| {
|
||||
const elem_ptr = self.builder.emit(.{ .index_gep = .{ .lhs = storage, .rhs = idx_val } }, self.module.types.ptrTo(elem_ty));
|
||||
break :blk self.builder.load(elem_ptr, elem_ty);
|
||||
}
|
||||
}
|
||||
break :blk self.builder.emit(.{ .index_get = .{ .lhs = iterable, .rhs = idx_val } }, bind_ty);
|
||||
};
|
||||
|
||||
var body_scope = Scope.init(self.alloc, self.scope);
|
||||
const old_scope = self.scope;
|
||||
self.scope = &body_scope;
|
||||
|
||||
body_scope.put(fe.capture_name, .{ .ref = elem, .ty = bind_ty, .is_alloca = false, .is_ref_capture = fe.capture_by_ref });
|
||||
|
||||
// Bind index if requested
|
||||
if (fe.index_name) |iname| {
|
||||
body_scope.put(iname, .{ .ref = idx_val, .ty = .s64, .is_alloca = false });
|
||||
for (fe.captures, 0..) |cap, i| {
|
||||
const prep = preps.items[i];
|
||||
const cur = if (i == 0) cur0 else self.builder.load(prep.slot, .s64);
|
||||
if (prep.is_range) {
|
||||
body_scope.put(cap.name, .{ .ref = cur, .ty = .s64, .is_alloca = false });
|
||||
continue;
|
||||
}
|
||||
const bind_ty = if (cap.by_ref) self.module.types.ptrTo(prep.elem_ty) else prep.elem_ty;
|
||||
const elem = if (cap.by_ref) blk: {
|
||||
// A slice value carries its backing pointer, so GEP on it writes
|
||||
// through. An array is a value — GEP needs its storage (alloca)
|
||||
// or mutations would hit a copy.
|
||||
const base = if (prep.is_array) (prep.storage orelse prep.data) else prep.data;
|
||||
break :blk self.builder.emit(.{ .index_gep = .{ .lhs = base, .rhs = cur } }, bind_ty);
|
||||
} else blk: {
|
||||
// By-value over an array with addressable storage: GEP + load ONE
|
||||
// element. `index_get` on the array VALUE spills the whole array
|
||||
// to a temp on every iteration — O(N²) bytes copied per loop.
|
||||
if (prep.storage) |storage| {
|
||||
const elem_ptr = self.builder.emit(.{ .index_gep = .{ .lhs = storage, .rhs = cur } }, self.module.types.ptrTo(prep.elem_ty));
|
||||
break :blk self.builder.load(elem_ptr, prep.elem_ty);
|
||||
}
|
||||
break :blk self.builder.emit(.{ .index_get = .{ .lhs = prep.data, .rhs = cur } }, bind_ty);
|
||||
};
|
||||
body_scope.put(cap.name, .{ .ref = elem, .ty = bind_ty, .is_alloca = false, .is_ref_capture = cap.by_ref });
|
||||
}
|
||||
|
||||
// Save and set loop targets
|
||||
@@ -392,13 +449,15 @@ pub fn lowerFor(self: *Lowering, fe: *const ast.ForExpr) Ref {
|
||||
self.builder.br(inc_bb, &.{});
|
||||
}
|
||||
|
||||
// Increment block: increment index and jump back to header
|
||||
// Increment block: advance every cursor and jump back to header.
|
||||
self.builder.switchToBlock(inc_bb);
|
||||
{
|
||||
const cur_idx = self.builder.load(idx_slot, .s64);
|
||||
const one = self.builder.constInt(1, .s64);
|
||||
const next_idx = self.builder.add(cur_idx, one, .s64);
|
||||
self.builder.store(idx_slot, next_idx);
|
||||
for (preps.items) |prep| {
|
||||
const cur = self.builder.load(prep.slot, .s64);
|
||||
const next = self.builder.add(cur, one, .s64);
|
||||
self.builder.store(prep.slot, next);
|
||||
}
|
||||
self.builder.br(header_bb, &.{});
|
||||
}
|
||||
|
||||
@@ -407,81 +466,26 @@ pub fn lowerFor(self: *Lowering, fe: *const ast.ForExpr) Ref {
|
||||
return self.builder.constInt(0, .void);
|
||||
}
|
||||
|
||||
/// Runtime counting loop `for start..end (i) { }` — `i` (optional) is the
|
||||
/// cursor, `end` is exclusive. Lowers to the same header/inc/exit shape as
|
||||
/// the collection form, minus the element fetch.
|
||||
pub fn lowerRuntimeRangeFor(self: *Lowering, fe: *const ast.ForExpr, end_node: *Node) Ref {
|
||||
const start = self.lowerExpr(fe.iterable);
|
||||
const end = self.lowerExpr(end_node);
|
||||
|
||||
const idx_slot = self.builder.alloca(.s64);
|
||||
self.builder.store(idx_slot, start);
|
||||
|
||||
const header_bb = self.freshBlock("for.hdr");
|
||||
const body_bb = self.freshBlock("for.body");
|
||||
const inc_bb = self.freshBlock("for.inc");
|
||||
const exit_bb = self.freshBlock("for.exit");
|
||||
|
||||
self.builder.br(header_bb, &.{});
|
||||
|
||||
self.builder.switchToBlock(header_bb);
|
||||
const idx_val = self.builder.load(idx_slot, .s64);
|
||||
const cmp = self.builder.cmpLt(idx_val, end);
|
||||
self.builder.condBr(cmp, body_bb, &.{}, exit_bb, &.{});
|
||||
|
||||
self.builder.switchToBlock(body_bb);
|
||||
var body_scope = Scope.init(self.alloc, self.scope);
|
||||
const old_scope = self.scope;
|
||||
self.scope = &body_scope;
|
||||
if (fe.capture_name.len > 0) {
|
||||
body_scope.put(fe.capture_name, .{ .ref = idx_val, .ty = .s64, .is_alloca = false });
|
||||
}
|
||||
|
||||
const old_break = self.break_target;
|
||||
const old_continue = self.continue_target;
|
||||
const old_defer_base = self.loop_defer_base;
|
||||
self.break_target = exit_bb;
|
||||
self.continue_target = inc_bb;
|
||||
self.loop_defer_base = self.defer_stack.items.len;
|
||||
|
||||
self.lowerBlock(fe.body);
|
||||
|
||||
self.break_target = old_break;
|
||||
self.continue_target = old_continue;
|
||||
self.loop_defer_base = old_defer_base;
|
||||
self.scope = old_scope;
|
||||
body_scope.deinit();
|
||||
|
||||
if (!self.currentBlockHasTerminator()) {
|
||||
self.builder.br(inc_bb, &.{});
|
||||
}
|
||||
|
||||
self.builder.switchToBlock(inc_bb);
|
||||
{
|
||||
const cur_idx = self.builder.load(idx_slot, .s64);
|
||||
const one = self.builder.constInt(1, .s64);
|
||||
const next_idx = self.builder.add(cur_idx, one, .s64);
|
||||
self.builder.store(idx_slot, next_idx);
|
||||
self.builder.br(header_bb, &.{});
|
||||
}
|
||||
|
||||
self.builder.switchToBlock(exit_bb);
|
||||
return self.builder.constInt(0, .void);
|
||||
}
|
||||
|
||||
/// Comptime-unrolled `inline for start..end (i) { }`. `start`/`end` must be
|
||||
/// comptime-known. The body is lowered `end - start` times with the cursor
|
||||
/// bound as an `int_val` comptime constant, so `xs[i]` over a pack
|
||||
/// Comptime-unrolled `inline for start..end (i) { }`. A single bounded range
|
||||
/// with comptime-known bounds. The body is lowered once per value with the
|
||||
/// cursor bound as an `int_val` comptime constant, so `xs[i]` over a pack
|
||||
/// substitutes the concrete per-position argument each iteration.
|
||||
pub fn lowerInlineRangeFor(self: *Lowering, fe: *const ast.ForExpr, end_node: *Node) Ref {
|
||||
const start = self.evalComptimeInt(fe.iterable) orelse {
|
||||
if (self.diagnostics) |d| d.addFmt(.err, fe.iterable.span, "inline for: range start is not a compile-time integer", .{});
|
||||
pub fn lowerInlineRangeFor(self: *Lowering, fe: *const ast.ForExpr) Ref {
|
||||
const it = fe.iterables[0];
|
||||
if (fe.iterables.len != 1 or !it.is_range or it.range_end == null) {
|
||||
if (self.diagnostics) |d| d.addFmt(.err, it.expr.span, "inline for: a single bounded range is required — `inline for 0..N (i) {{ }}`", .{});
|
||||
return self.builder.constInt(0, .void);
|
||||
}
|
||||
const start = self.evalComptimeInt(it.expr) orelse {
|
||||
if (self.diagnostics) |d| d.addFmt(.err, it.expr.span, "inline for: range start is not a compile-time integer", .{});
|
||||
return self.builder.constInt(0, .void);
|
||||
};
|
||||
const end = self.evalComptimeInt(end_node) orelse {
|
||||
if (self.diagnostics) |d| d.addFmt(.err, end_node.span, "inline for: range end is not a compile-time integer", .{});
|
||||
var end = self.evalComptimeInt(it.range_end.?) orelse {
|
||||
if (self.diagnostics) |d| d.addFmt(.err, it.range_end.?.span, "inline for: range end is not a compile-time integer", .{});
|
||||
return self.builder.constInt(0, .void);
|
||||
};
|
||||
if (it.inclusive) end += 1;
|
||||
const capture_name = if (fe.captures.len > 0) fe.captures[0].name else "";
|
||||
|
||||
var i: i64 = start;
|
||||
while (i < end) : (i += 1) {
|
||||
@@ -493,22 +497,22 @@ pub fn lowerInlineRangeFor(self: *Lowering, fe: *const ast.ForExpr, end_node: *N
|
||||
// `print(i)`) and as a comptime constant (for `xs[i]` substitution).
|
||||
var had_prev = false;
|
||||
var prev: ComptimeValue = undefined;
|
||||
if (fe.capture_name.len > 0) {
|
||||
body_scope.put(fe.capture_name, .{ .ref = self.builder.constInt(i, .s64), .ty = .s64, .is_alloca = false });
|
||||
if (self.comptime_constants.get(fe.capture_name)) |p| {
|
||||
if (capture_name.len > 0) {
|
||||
body_scope.put(capture_name, .{ .ref = self.builder.constInt(i, .s64), .ty = .s64, .is_alloca = false });
|
||||
if (self.comptime_constants.get(capture_name)) |p| {
|
||||
had_prev = true;
|
||||
prev = p;
|
||||
}
|
||||
self.comptime_constants.put(fe.capture_name, .{ .int_val = i }) catch {};
|
||||
self.comptime_constants.put(capture_name, .{ .int_val = i }) catch {};
|
||||
}
|
||||
|
||||
self.lowerBlock(fe.body);
|
||||
|
||||
if (fe.capture_name.len > 0) {
|
||||
if (capture_name.len > 0) {
|
||||
if (had_prev) {
|
||||
self.comptime_constants.put(fe.capture_name, prev) catch {};
|
||||
self.comptime_constants.put(capture_name, prev) catch {};
|
||||
} else {
|
||||
_ = self.comptime_constants.remove(fe.capture_name);
|
||||
_ = self.comptime_constants.remove(capture_name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -158,10 +158,13 @@ pub const UnknownTypeChecker = struct {
|
||||
self.checkBindingNames(we.body);
|
||||
},
|
||||
.for_expr => |fe| {
|
||||
if (fe.capture_name.len != 0) self.checkBindingName(fe.capture_name, fe.capture_span, fe.capture_is_raw);
|
||||
if (fe.index_name) |idx| self.checkBindingName(idx, fe.index_span, fe.index_is_raw);
|
||||
self.checkBindingNames(fe.iterable);
|
||||
if (fe.range_end) |re| self.checkBindingNames(re);
|
||||
for (fe.captures) |cap| {
|
||||
if (cap.name.len != 0) self.checkBindingName(cap.name, cap.span, cap.is_raw);
|
||||
}
|
||||
for (fe.iterables) |it| {
|
||||
self.checkBindingNames(it.expr);
|
||||
if (it.range_end) |re| self.checkBindingNames(re);
|
||||
}
|
||||
self.checkBindingNames(fe.body);
|
||||
},
|
||||
.match_expr => |me| {
|
||||
@@ -417,8 +420,10 @@ pub const UnknownTypeChecker = struct {
|
||||
self.harvestScopeDecls(we.body, out);
|
||||
},
|
||||
.for_expr => |fe| {
|
||||
self.harvestScopeDecls(fe.iterable, out);
|
||||
if (fe.range_end) |re| self.harvestScopeDecls(re, out);
|
||||
for (fe.iterables) |it| {
|
||||
self.harvestScopeDecls(it.expr, out);
|
||||
if (it.range_end) |re| self.harvestScopeDecls(re, out);
|
||||
}
|
||||
self.harvestScopeDecls(fe.body, out);
|
||||
},
|
||||
.match_expr => |me| {
|
||||
@@ -566,8 +571,10 @@ pub const UnknownTypeChecker = struct {
|
||||
self.walkBodyTypes(we.body, declared, in_scope, type_vals);
|
||||
},
|
||||
.for_expr => |fe| {
|
||||
self.walkBodyTypes(fe.iterable, declared, in_scope, type_vals);
|
||||
if (fe.range_end) |re| self.walkBodyTypes(re, declared, in_scope, type_vals);
|
||||
for (fe.iterables) |it| {
|
||||
self.walkBodyTypes(it.expr, declared, in_scope, type_vals);
|
||||
if (it.range_end) |re| self.walkBodyTypes(re, declared, in_scope, type_vals);
|
||||
}
|
||||
self.walkBodyTypes(fe.body, declared, in_scope, type_vals);
|
||||
},
|
||||
.match_expr => |me| {
|
||||
|
||||
@@ -146,6 +146,10 @@ pub const Lexer = struct {
|
||||
'.' => {
|
||||
if (self.peek() == '.') {
|
||||
self.index += 1;
|
||||
if (self.peek() == '=') {
|
||||
self.index += 1;
|
||||
return self.makeToken(.dot_dot_eq, start, self.index);
|
||||
}
|
||||
return self.makeToken(.dot_dot, start, self.index);
|
||||
}
|
||||
return self.makeToken(.dot, start, self.index);
|
||||
|
||||
@@ -1282,8 +1282,10 @@ pub const Server = struct {
|
||||
collectInlayHints(allocator, we.body, symbols, fn_signatures, source, hints);
|
||||
},
|
||||
.for_expr => |fe| {
|
||||
if (!std.mem.eql(u8, fe.capture_name, "_")) {
|
||||
addForCaptureHint(allocator, fe.capture_name, node.span, symbols, source, hints);
|
||||
for (fe.captures) |cap| {
|
||||
if (!std.mem.eql(u8, cap.name, "_")) {
|
||||
addForCaptureHint(allocator, cap.name, node.span, symbols, source, hints);
|
||||
}
|
||||
}
|
||||
collectInlayHints(allocator, fe.body, symbols, fn_signatures, source, hints);
|
||||
},
|
||||
@@ -1534,7 +1536,10 @@ pub const Server = struct {
|
||||
self.collectCallHints(doc, we.body, hints);
|
||||
},
|
||||
.for_expr => |fe| {
|
||||
self.collectCallHints(doc, fe.iterable, hints);
|
||||
for (fe.iterables) |it| {
|
||||
self.collectCallHints(doc, it.expr, hints);
|
||||
if (it.range_end) |re| self.collectCallHints(doc, re, hints);
|
||||
}
|
||||
self.collectCallHints(doc, fe.body, hints);
|
||||
},
|
||||
.var_decl => |vd| {
|
||||
@@ -1765,6 +1770,7 @@ pub const Server = struct {
|
||||
.comma,
|
||||
.dot,
|
||||
.dot_dot,
|
||||
.dot_dot_eq,
|
||||
.dollar,
|
||||
.l_paren,
|
||||
.r_paren,
|
||||
@@ -3297,7 +3303,7 @@ test "analyzeDocument: for-loop capture variables are registered" {
|
||||
const src: [:0]const u8 =
|
||||
\\main :: () {
|
||||
\\ arr : [3]s32 = ---;
|
||||
\\ for arr: (it, ix) {
|
||||
\\ for arr, 0.. (it, ix) {
|
||||
\\ x := it + ix;
|
||||
\\ }
|
||||
\\}
|
||||
@@ -3323,7 +3329,7 @@ test "analyzeDocument: for-loop with underscore capture" {
|
||||
const src: [:0]const u8 =
|
||||
\\main :: () {
|
||||
\\ arr : [3]s32 = ---;
|
||||
\\ for arr: (_, ix) {
|
||||
\\ for arr, 0.. (_, ix) {
|
||||
\\ x := ix;
|
||||
\\ }
|
||||
\\}
|
||||
@@ -3348,7 +3354,7 @@ test "analyzeDocument: for-loop value-only capture" {
|
||||
const src: [:0]const u8 =
|
||||
\\main :: () {
|
||||
\\ arr : [3]s32 = ---;
|
||||
\\ for arr: (val) {
|
||||
\\ for arr (val) {
|
||||
\\ x := val;
|
||||
\\ }
|
||||
\\}
|
||||
@@ -3463,7 +3469,7 @@ test "lsp/inlayHint: a for-loop capture in a struct method shows its element typ
|
||||
\\Game :: struct {
|
||||
\\ legal: List(Move);
|
||||
\\ scan :: (self: *Game) {
|
||||
\\ for self.legal: (m) { x := m.flag; }
|
||||
\\ for self.legal (m) { x := m.flag; }
|
||||
\\ }
|
||||
\\}
|
||||
;
|
||||
|
||||
203
src/parser.zig
203
src/parser.zig
@@ -24,10 +24,13 @@ pub const Parser = struct {
|
||||
/// a `.compiler_expr` body so the per-method `#compiler` suffix can be
|
||||
/// omitted.
|
||||
struct_default_compiler: bool = false,
|
||||
/// When true, parsePostfix does not treat a trailing `(` as a call. Set
|
||||
/// while parsing a `for` range bound so `for 0..N (i)` reads `N` as the
|
||||
/// end and leaves `(i)` for the cursor rather than parsing `N(i)`.
|
||||
suppress_call: bool = false,
|
||||
/// When true (set while parsing a `for` header's iterable expressions),
|
||||
/// a top-level `(` group immediately followed by `{` or `=>` is the loop
|
||||
/// CAPTURE, never call arguments — `for xs (x) { }` reads `(x)` as the
|
||||
/// capture, while `for zip(a, b) (x, y) { }` still calls `zip(a, b)`
|
||||
/// because that group is not the trailing one. Cleared inside any nested
|
||||
/// bracket/paren/argument context.
|
||||
in_for_header: bool = false,
|
||||
/// When true (set while parsing an `onfail` body), a `raise` statement is
|
||||
/// rejected — an error during cleanup has no propagation target. E1.7
|
||||
/// extends this to the full {try, return, break, continue} set.
|
||||
@@ -2477,9 +2480,13 @@ pub const Parser = struct {
|
||||
var expr = try self.parsePrimary();
|
||||
|
||||
while (true) {
|
||||
if (self.current.tag == .l_paren and !self.suppress_call) {
|
||||
// Call
|
||||
if (self.current.tag == .l_paren and !self.parenGroupIsForCapture()) {
|
||||
// Call. Argument expressions are an ordinary nested context —
|
||||
// the for-header capture rule does not apply inside them.
|
||||
self.advance();
|
||||
const saved_hdr_args = self.in_for_header;
|
||||
self.in_for_header = false;
|
||||
defer self.in_for_header = saved_hdr_args;
|
||||
var args = std.ArrayList(*Node).empty;
|
||||
while (self.current.tag != .r_paren and self.current.tag != .eof) {
|
||||
if (args.items.len > 0) {
|
||||
@@ -2564,10 +2571,10 @@ pub const Parser = struct {
|
||||
} else if (self.current.tag == .l_bracket) {
|
||||
// Index or slice access: expr[expr] or expr[start..end]
|
||||
self.advance();
|
||||
// Inside `[...]`, calls parse normally even within a range bound.
|
||||
const saved_suppress_idx = self.suppress_call;
|
||||
self.suppress_call = false;
|
||||
defer self.suppress_call = saved_suppress_idx;
|
||||
// Inside `[...]`, calls parse normally even within a for header.
|
||||
const saved_hdr_idx = self.in_for_header;
|
||||
self.in_for_header = false;
|
||||
defer self.in_for_header = saved_hdr_idx;
|
||||
if (self.current.tag == .dot_dot) {
|
||||
// [..end]
|
||||
self.advance();
|
||||
@@ -2794,11 +2801,11 @@ pub const Parser = struct {
|
||||
}
|
||||
self.advance(); // skip '('
|
||||
|
||||
// A `(` here opens a grouping/tuple, not a `for` range bound, so
|
||||
// calls inside it parse normally even within a range bound.
|
||||
const saved_suppress_grp = self.suppress_call;
|
||||
self.suppress_call = false;
|
||||
defer self.suppress_call = saved_suppress_grp;
|
||||
// A `(` here opens a grouping/tuple, so calls inside it parse
|
||||
// normally even within a for header.
|
||||
const saved_hdr_grp = self.in_for_header;
|
||||
self.in_for_header = false;
|
||||
defer self.in_for_header = saved_hdr_grp;
|
||||
|
||||
// Check for named tuple: (name: expr, ...)
|
||||
if (self.current.tag == .identifier and self.peekNext() == .colon) {
|
||||
@@ -3163,79 +3170,94 @@ pub const Parser = struct {
|
||||
const start = self.current.loc.start;
|
||||
self.advance(); // skip 'for'
|
||||
|
||||
const iterable = try self.parseExpr();
|
||||
var iterables = std.ArrayList(ast.ForIterable).empty;
|
||||
var captures = std.ArrayList(ast.ForCapture).empty;
|
||||
|
||||
// Range form: `for start..end (i)? { }`. The `..` only appears here for a
|
||||
// range (slice ranges live inside `[]`), so it's unambiguous.
|
||||
var range_end: ?*Node = null;
|
||||
if (self.current.tag == .dot_dot) {
|
||||
self.advance(); // skip '..'
|
||||
const saved_suppress = self.suppress_call;
|
||||
self.suppress_call = true;
|
||||
range_end = try self.parseExpr();
|
||||
self.suppress_call = saved_suppress;
|
||||
// Header: comma-separated iterables, each a collection expression or
|
||||
// a range (`a..b`, `a..=b`, open `a..`). Top-level trailing call
|
||||
// parens are read as the capture (see parenGroupIsForCapture).
|
||||
const saved_hdr = self.in_for_header;
|
||||
self.in_for_header = true;
|
||||
while (true) {
|
||||
const expr = try self.parseExpr();
|
||||
var it = ast.ForIterable{ .expr = expr };
|
||||
if (self.current.tag == .dot_dot or self.current.tag == .dot_dot_eq) {
|
||||
it.is_range = true;
|
||||
it.inclusive = self.current.tag == .dot_dot_eq;
|
||||
self.advance();
|
||||
// End expression — absent for the open range `a..`, i.e. when
|
||||
// the header continues (`,`), the body starts (`{` / `=>`),
|
||||
// or the capture group follows.
|
||||
const open = switch (self.current.tag) {
|
||||
.comma, .l_brace, .fat_arrow => true,
|
||||
.l_paren => self.parenGroupIsForCapture(),
|
||||
else => false,
|
||||
};
|
||||
if (open) {
|
||||
if (it.inclusive) return self.fail("'..=' requires an end expression — the open form is 'a..'");
|
||||
} else {
|
||||
it.range_end = try self.parseExpr();
|
||||
}
|
||||
}
|
||||
try iterables.append(self.allocator, it);
|
||||
if (self.current.tag != .comma) break;
|
||||
self.advance();
|
||||
}
|
||||
self.in_for_header = saved_hdr;
|
||||
|
||||
// Migration aid for the pre-multi-iterable syntax.
|
||||
if (self.current.tag == .colon) {
|
||||
return self.fail("for-loop syntax: the ':' before the capture was removed — write `for xs (x) { }` (index via `for xs, 0.. (x, i)`)");
|
||||
}
|
||||
|
||||
var capture_name: []const u8 = "";
|
||||
var capture_span: ?ast.Span = null;
|
||||
var capture_is_raw = false;
|
||||
var index_name: ?[]const u8 = null;
|
||||
var index_span: ?ast.Span = null;
|
||||
var index_is_raw = false;
|
||||
var capture_by_ref = false;
|
||||
|
||||
if (range_end != null) {
|
||||
// Optional cursor, introduced by `:` for symmetry with the
|
||||
// collection form: `for 0..N: (i)` (or `for 0..N` with no cursor).
|
||||
// The colon is required when a cursor is present.
|
||||
if (self.current.tag == .colon) {
|
||||
self.advance();
|
||||
try self.expect(.l_paren);
|
||||
if (self.current.tag != .identifier) return self.fail("expected cursor variable name");
|
||||
capture_name = self.tokenSlice(self.current);
|
||||
capture_span = .{ .start = self.current.loc.start, .end = self.current.loc.end };
|
||||
capture_is_raw = self.current.is_raw;
|
||||
self.advance();
|
||||
try self.expect(.r_paren);
|
||||
}
|
||||
} else {
|
||||
// Collection form: `: (capture, index?)`. A leading `*` on the
|
||||
// capture (`(*x)`) binds it by pointer into the collection.
|
||||
try self.expect(.colon);
|
||||
try self.expect(.l_paren);
|
||||
if (self.current.tag == .star) {
|
||||
capture_by_ref = true;
|
||||
self.advance();
|
||||
}
|
||||
if (self.current.tag != .identifier) return self.fail("expected capture variable name");
|
||||
capture_name = self.tokenSlice(self.current);
|
||||
capture_span = .{ .start = self.current.loc.start, .end = self.current.loc.end };
|
||||
capture_is_raw = self.current.is_raw;
|
||||
// Capture group: `(x)`, `(*x)`, `(a, b, ...)` — positional, one
|
||||
// capture per iterable.
|
||||
if (self.current.tag == .l_paren) {
|
||||
self.advance();
|
||||
if (self.current.tag == .comma) {
|
||||
while (true) {
|
||||
var cap = ast.ForCapture{ .name = "" };
|
||||
if (self.current.tag == .star) {
|
||||
cap.by_ref = true;
|
||||
self.advance();
|
||||
}
|
||||
if (self.current.tag != .identifier) return self.fail("expected capture variable name (a call iterable also needs a capture: `for f(n) (x) { }`)");
|
||||
cap.name = self.tokenSlice(self.current);
|
||||
cap.span = .{ .start = self.current.loc.start, .end = self.current.loc.end };
|
||||
cap.is_raw = self.current.is_raw;
|
||||
self.advance();
|
||||
if (self.current.tag != .identifier) return self.fail("expected index variable name");
|
||||
index_name = self.tokenSlice(self.current);
|
||||
index_span = .{ .start = self.current.loc.start, .end = self.current.loc.end };
|
||||
index_is_raw = self.current.is_raw;
|
||||
try captures.append(self.allocator, cap);
|
||||
if (self.current.tag != .comma) break;
|
||||
self.advance();
|
||||
}
|
||||
try self.expect(.r_paren);
|
||||
}
|
||||
|
||||
const body = try self.parseBlock();
|
||||
if (captures.items.len != 0 and captures.items.len != iterables.items.len) {
|
||||
return self.fail("for capture count must match the iterable count — one capture per iterable");
|
||||
}
|
||||
if (iterables.items[0].is_range and iterables.items[0].range_end == null) {
|
||||
return self.fail("the first iterable must have a bounded length (it drives the loop) — an open range 'a..' may only follow it");
|
||||
}
|
||||
for (iterables.items, 0..) |it, i| {
|
||||
if (it.is_range and i < captures.items.len and captures.items[i].by_ref) {
|
||||
return self.fail("a range element cannot be captured by reference");
|
||||
}
|
||||
}
|
||||
|
||||
// Body: a block, or the arrow form `=> stmt` (a full statement, so
|
||||
// assignments like `=> s += x;` work; parseStmt owns the `;`).
|
||||
var body: *Node = undefined;
|
||||
if (self.current.tag == .fat_arrow) {
|
||||
self.advance();
|
||||
body = try self.parseStmt();
|
||||
} else {
|
||||
body = try self.parseBlock();
|
||||
}
|
||||
|
||||
return try self.createNode(start, .{ .for_expr = .{
|
||||
.iterable = iterable,
|
||||
.iterables = try iterables.toOwnedSlice(self.allocator),
|
||||
.captures = try captures.toOwnedSlice(self.allocator),
|
||||
.body = body,
|
||||
.capture_name = capture_name,
|
||||
.capture_span = capture_span,
|
||||
.capture_is_raw = capture_is_raw,
|
||||
.index_name = index_name,
|
||||
.index_span = index_span,
|
||||
.index_is_raw = index_is_raw,
|
||||
.range_end = range_end,
|
||||
.capture_by_ref = capture_by_ref,
|
||||
} });
|
||||
}
|
||||
|
||||
@@ -3780,6 +3802,35 @@ pub const Parser = struct {
|
||||
return tok.tag;
|
||||
}
|
||||
|
||||
/// With `current` on `(`: the tag of the token right after the matching
|
||||
/// `)`, scanning a throwaway copy of the lexer. Only parens are counted —
|
||||
/// they must balance lexically regardless of what nests inside.
|
||||
fn tagAfterParenGroup(self: *Parser) Tag {
|
||||
var lex = self.lexer;
|
||||
var depth: u32 = 1;
|
||||
while (true) {
|
||||
const tok = lex.next();
|
||||
switch (tok.tag) {
|
||||
.l_paren => depth += 1,
|
||||
.r_paren => {
|
||||
depth -= 1;
|
||||
if (depth == 0) return lex.next().tag;
|
||||
},
|
||||
.eof => return .eof,
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// For-header capture rule: a top-level `(` group immediately followed by
|
||||
/// `{` or `=>` is the loop capture, so parsePostfix must not consume it
|
||||
/// as call arguments.
|
||||
fn parenGroupIsForCapture(self: *Parser) bool {
|
||||
if (!self.in_for_header) return false;
|
||||
const after = self.tagAfterParenGroup();
|
||||
return after == .l_brace or after == .fat_arrow;
|
||||
}
|
||||
|
||||
fn advance(self: *Parser) void {
|
||||
self.prev_end = self.current.loc.end;
|
||||
self.current = self.lexer.next();
|
||||
@@ -4541,7 +4592,7 @@ test "E1.7 try rejected inside an onfail body" {
|
||||
test "E1.7 break rejected inside a defer body (transitive through a loop)" {
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
var parser = Parser.init(arena.allocator(), "f :: () { defer { for 0..1: (i) { break; } } }");
|
||||
var parser = Parser.init(arena.allocator(), "f :: () { defer { for 0..1 (i) { break; } } }");
|
||||
try std.testing.expectError(error.ParseError, parser.parse());
|
||||
}
|
||||
|
||||
|
||||
35
src/sema.zig
35
src/sema.zig
@@ -1186,24 +1186,24 @@ pub const Analyzer = struct {
|
||||
}
|
||||
},
|
||||
.for_expr => |fe| {
|
||||
try self.analyzeNode(fe.iterable);
|
||||
for (fe.iterables) |it| {
|
||||
try self.analyzeNode(it.expr);
|
||||
if (it.range_end) |re| try self.analyzeNode(re);
|
||||
}
|
||||
try self.pushScope();
|
||||
if (!std.mem.eql(u8, fe.capture_name, "_")) {
|
||||
for (fe.captures, 0..) |cap, i| {
|
||||
if (std.mem.eql(u8, cap.name, "_")) continue;
|
||||
const it = fe.iterables[i];
|
||||
var cap_ty: ?Type = null;
|
||||
if (fe.range_end != null) {
|
||||
if (it.is_range) {
|
||||
cap_ty = .{ .signed = 64 };
|
||||
} else if (self.elementTypeOf(self.inferExprType(fe.iterable))) |elem| {
|
||||
cap_ty = if (fe.capture_by_ref)
|
||||
} else if (self.elementTypeOf(self.inferExprType(it.expr))) |elem| {
|
||||
cap_ty = if (cap.by_ref)
|
||||
(if (elem.toName()) |en| Type{ .pointer_type = .{ .pointee_name = en, .is_raw = innerNameIsRaw(elem) } } else elem)
|
||||
else
|
||||
elem;
|
||||
}
|
||||
try self.addSymbol(fe.capture_name, .variable, cap_ty, node.span);
|
||||
}
|
||||
if (fe.index_name) |idx_name| {
|
||||
if (!std.mem.eql(u8, idx_name, "_")) {
|
||||
try self.addSymbol(idx_name, .variable, .{ .signed = 64 }, node.span);
|
||||
}
|
||||
try self.addSymbol(cap.name, .variable, cap_ty, node.span);
|
||||
}
|
||||
try self.analyzeNode(fe.body);
|
||||
self.popScope();
|
||||
@@ -1680,7 +1680,12 @@ pub fn findNodeAtOffset(node: *Node, offset: u32) ?*Node {
|
||||
if (findNodeAtOffset(we.body, offset)) |found| return found;
|
||||
},
|
||||
.for_expr => |fe| {
|
||||
if (findNodeAtOffset(fe.iterable, offset)) |found| return found;
|
||||
for (fe.iterables) |it| {
|
||||
if (findNodeAtOffset(it.expr, offset)) |found| return found;
|
||||
if (it.range_end) |re| {
|
||||
if (findNodeAtOffset(re, offset)) |found| return found;
|
||||
}
|
||||
}
|
||||
if (findNodeAtOffset(fe.body, offset)) |found| return found;
|
||||
},
|
||||
.spread_expr => |se| {
|
||||
@@ -2242,9 +2247,9 @@ test "sema: for-loop captures resolve element, by-ref pointer, and range cursor"
|
||||
"List :: struct ($T: Type) { items: [*]T = null; len: s64 = 0; }" ++
|
||||
"Game :: struct { legal: List(Move);" ++
|
||||
" scan :: (self: *Game) {" ++
|
||||
" for self.legal: (m) { a := m.flag; }" ++
|
||||
" for self.legal: (*p) { b := p.flag; }" ++
|
||||
" for 0..10: (i) { c := i; }" ++
|
||||
" for self.legal (m) { a := m.flag; }" ++
|
||||
" for self.legal (*p) { b := p.flag; }" ++
|
||||
" for 0..10 (i) { c := i; }" ++
|
||||
" } }";
|
||||
var parser = parser_mod.Parser.init(alloc, source);
|
||||
const root = try parser.parse();
|
||||
|
||||
@@ -52,6 +52,7 @@ pub const Tag = enum {
|
||||
comma, // ,
|
||||
dot, // .
|
||||
dot_dot, // ..
|
||||
dot_dot_eq, // ..=
|
||||
dollar, // $
|
||||
|
||||
// Operators
|
||||
@@ -150,6 +151,7 @@ pub const Tag = enum {
|
||||
.comma => ",",
|
||||
.dot => ".",
|
||||
.dot_dot => "..",
|
||||
.dot_dot_eq => "..=",
|
||||
.dollar => "$",
|
||||
.plus => "+",
|
||||
.minus => "-",
|
||||
|
||||
Reference in New Issue
Block a user