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:
@@ -3181,9 +3181,10 @@ pub const Parser = struct {
|
||||
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) {
|
||||
if (rangeTokenInfo(self.current.tag)) |rt| {
|
||||
it.is_range = true;
|
||||
it.inclusive = self.current.tag == .dot_dot_eq;
|
||||
it.start_exclusive = rt.start_exclusive;
|
||||
it.end_inclusive = rt.end_inclusive;
|
||||
self.advance();
|
||||
// End expression — absent for the open range `a..`, i.e. when
|
||||
// the header continues (`,`), the body starts (`{` / `=>`),
|
||||
@@ -3194,7 +3195,7 @@ pub const Parser = struct {
|
||||
else => false,
|
||||
};
|
||||
if (open) {
|
||||
if (it.inclusive) return self.fail("'..=' requires an end expression — the open form is 'a..'");
|
||||
if (rt.end_marked) return self.fail("a range with an explicit end marker ('..=' / '..<') requires an end expression — the open form is 'a..'");
|
||||
} else {
|
||||
it.range_end = try self.parseExpr();
|
||||
}
|
||||
@@ -3831,6 +3832,32 @@ pub const Parser = struct {
|
||||
return after == .l_brace or after == .fat_arrow;
|
||||
}
|
||||
|
||||
const RangeTokenInfo = struct {
|
||||
start_exclusive: bool,
|
||||
end_inclusive: bool,
|
||||
/// True when the lexeme carries an explicit end marker (`=` / `<`
|
||||
/// after the dots) — the end expression is then mandatory.
|
||||
end_marked: bool,
|
||||
};
|
||||
|
||||
/// Range lexemes: each side of `..` takes an optional bound marker, `=`
|
||||
/// inclusive / `<` exclusive, defaulting to start-inclusive,
|
||||
/// end-exclusive (`a..b` ≡ `a=..<b`).
|
||||
fn rangeTokenInfo(tag: Tag) ?RangeTokenInfo {
|
||||
return switch (tag) {
|
||||
.dot_dot => .{ .start_exclusive = false, .end_inclusive = false, .end_marked = false },
|
||||
.dot_dot_eq => .{ .start_exclusive = false, .end_inclusive = true, .end_marked = true },
|
||||
.dot_dot_lt => .{ .start_exclusive = false, .end_inclusive = false, .end_marked = true },
|
||||
.lt_dot_dot => .{ .start_exclusive = true, .end_inclusive = false, .end_marked = false },
|
||||
.lt_dot_dot_eq => .{ .start_exclusive = true, .end_inclusive = true, .end_marked = true },
|
||||
.lt_dot_dot_lt => .{ .start_exclusive = true, .end_inclusive = false, .end_marked = true },
|
||||
.eq_dot_dot => .{ .start_exclusive = false, .end_inclusive = false, .end_marked = false },
|
||||
.eq_dot_dot_eq => .{ .start_exclusive = false, .end_inclusive = true, .end_marked = true },
|
||||
.eq_dot_dot_lt => .{ .start_exclusive = false, .end_inclusive = false, .end_marked = true },
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
|
||||
fn advance(self: *Parser) void {
|
||||
self.prev_end = self.current.loc.end;
|
||||
self.current = self.lexer.next();
|
||||
|
||||
Reference in New Issue
Block a user