lang: range bound markers — '=' inclusive / '<' exclusive on either side of '..'

Each side of '..' takes an optional bound marker, defaulting to
start-inclusive, end-exclusive (a..b == a=..<b; a..=b stays the short
end-inclusive spelling):

    for 0<..<N (i) { }   // 1 .. N-1   (both exclusive)
    for 0=..=N (i) { }   // 0 .. N     (both inclusive)
    for 0<..=N (i) { }   // 1 .. N
    for 0..<N  (i) { }   // 0 .. N-1   (explicit default)
    for xs, 2<.. (x, i)  // open range, exclusive start: i = 3, 4, ...

The nine lexemes are single tokens (maximal munch on '<'/'='/'..'), so
expression parsing never sees the leading marker as a comparison; '<',
'<<', '<=', '==', '=>' lex unchanged. An explicit end marker makes the
end expression mandatory; open forms are a.. / a<.. / a=... Works in
runtime, multi-iterable, and inline-for headers.

Regression: examples/0051-basic-for-range-bounds.sx (full matrix, open
start-marked ranges, comptime unroll, runtime bounds, lexer
non-regression); 1152's pinned message generalized.
This commit is contained in:
agra
2026-06-10 20:55:31 +03:00
parent 116af2359e
commit fd14ab5694
14 changed files with 194 additions and 17 deletions

View File

@@ -1963,6 +1963,23 @@ for xs (x) => sum += x; // arrow body
inline for 0..n (i) { } // comptime-unrolled single bounded range
```
**Range bound markers.** Each side of `..` takes an optional marker — `=`
inclusive, `<` exclusive — with defaults start-inclusive, end-exclusive
(`a..b` ≡ `a=..<b`; `a..=b` is the short end-inclusive spelling):
```sx
for 0<..<5 (i) { } // 1 2 3 4 — both ends exclusive
for 0=..=5 (i) { } // 0 1 2 3 4 5 — both ends inclusive
for 0<..=5 (i) { } // 1 2 3 4 5
for 0=..<5 (i) { } // 0 1 2 3 4 — explicit spelling of `0..5`
for 0..<5 (i) { } // 0 1 2 3 4 — explicit spelling of `0..5`
for xs, 2<.. (x, i) { } // open range with an exclusive start: i = 3, 4, …
```
A marker after the dots (`..=` / `..<`) makes the end expression mandatory;
the open form is `a..` (or `a<..` / `a=..`). The lexemes are single tokens —
no whitespace inside (`0 <..< N` is fine, `0 < ..` is not a range).
**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
@@ -3069,7 +3086,8 @@ lvalue = IDENT | postfix '.' IDENT
expr = if_expr | match_expr | while_expr | for_expr | lambda | binary
while_expr = 'while' expr block
for_expr = 'for' for_iter (',' for_iter)* [for_capture] (block | '=>' stmt)
for_iter = expr [('..' | '..=') [expr]]
for_iter = expr [range_op [expr]]
range_op = '..' | '..=' | '..<' | '<..' | '<..=' | '<..<' | '=..' | '=..=' | '=..<'
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))?