lang F1: range-based for + inline-for unroll over packs
Add range loop syntax:
- runtime for start..end (i) { } counting loop, cursor optional, end exclusive
- comptime inline for start..end (i) { } comptime-unrolled body
The inline form binds the cursor as an int_val comptime constant per
iteration, so xs[i] over a heterogeneous pack substitutes the concrete
per-position element -- the canonical's pack-iteration vehicle
(inline for 0..sources.len (i) { sources[i].addListener(...) }).
- AST: ForExpr.range_end, ForExpr.is_inline
- parser: parseForExpr range vs collection form; suppress_call flag so
N (i) is not read as a call N(i) while parsing a range bound
- lower: lowerRuntimeRangeFor / lowerInlineRangeFor; evalComptimeInt;
comptimeIndexOf extends pack-index resolution beyond int literals
Revises spec's inline for i in 0..N to the no-in, range-first, paren-cursor
form. Regression: examples/200-for-range.sx.
This commit is contained in:
@@ -23,6 +23,10 @@ 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,
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, source: [:0]const u8) Parser {
|
||||
var lexer = Lexer.init(source);
|
||||
@@ -1995,6 +1999,13 @@ pub const Parser = struct {
|
||||
try self.expectSemicolonAfter(expr);
|
||||
return expr;
|
||||
}
|
||||
if (self.peekNext() == .kw_for) {
|
||||
self.advance(); // skip 'inline'
|
||||
const expr = try self.parseForExpr();
|
||||
expr.data.for_expr.is_inline = true;
|
||||
try self.expectSemicolonAfter(expr);
|
||||
return expr;
|
||||
}
|
||||
}
|
||||
|
||||
// Block-form if/while/for as statements — parse directly to prevent
|
||||
@@ -2187,7 +2198,7 @@ pub const Parser = struct {
|
||||
var expr = try self.parsePrimary();
|
||||
|
||||
while (true) {
|
||||
if (self.current.tag == .l_paren) {
|
||||
if (self.current.tag == .l_paren and !self.suppress_call) {
|
||||
// Call
|
||||
self.advance();
|
||||
var args = std.ArrayList(*Node).empty;
|
||||
@@ -2274,6 +2285,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;
|
||||
if (self.current.tag == .dot_dot) {
|
||||
// [..end]
|
||||
self.advance();
|
||||
@@ -2458,6 +2473,12 @@ 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;
|
||||
|
||||
// Check for named tuple: (name: expr, ...)
|
||||
if (self.current.tag == .identifier and self.peekNext() == .colon) {
|
||||
return self.parseTupleLiteralNamed(start);
|
||||
@@ -2803,25 +2824,45 @@ pub const Parser = struct {
|
||||
|
||||
const iterable = try self.parseExpr();
|
||||
|
||||
// Expect ': (' capture clause
|
||||
try self.expect(.colon);
|
||||
try self.expect(.l_paren);
|
||||
|
||||
// Capture variable name
|
||||
if (self.current.tag != .identifier) return self.fail("expected capture variable name");
|
||||
const capture_name = self.tokenSlice(self.current);
|
||||
self.advance();
|
||||
|
||||
// Optional ', index_name'
|
||||
var index_name: ?[]const u8 = null;
|
||||
if (self.current.tag == .comma) {
|
||||
self.advance();
|
||||
if (self.current.tag != .identifier) return self.fail("expected index variable name");
|
||||
index_name = self.tokenSlice(self.current);
|
||||
self.advance();
|
||||
// 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;
|
||||
}
|
||||
|
||||
var capture_name: []const u8 = "";
|
||||
var index_name: ?[]const u8 = null;
|
||||
|
||||
if (range_end != null) {
|
||||
// Range capture is the optional cursor: `(i)` or nothing.
|
||||
if (self.current.tag == .l_paren) {
|
||||
self.advance();
|
||||
if (self.current.tag != .identifier) return self.fail("expected cursor variable name");
|
||||
capture_name = self.tokenSlice(self.current);
|
||||
self.advance();
|
||||
try self.expect(.r_paren);
|
||||
}
|
||||
} else {
|
||||
// Collection form: `: (capture, index?)`.
|
||||
try self.expect(.colon);
|
||||
try self.expect(.l_paren);
|
||||
if (self.current.tag != .identifier) return self.fail("expected capture variable name");
|
||||
capture_name = self.tokenSlice(self.current);
|
||||
self.advance();
|
||||
if (self.current.tag == .comma) {
|
||||
self.advance();
|
||||
if (self.current.tag != .identifier) return self.fail("expected index variable name");
|
||||
index_name = self.tokenSlice(self.current);
|
||||
self.advance();
|
||||
}
|
||||
try self.expect(.r_paren);
|
||||
}
|
||||
|
||||
try self.expect(.r_paren);
|
||||
const body = try self.parseBlock();
|
||||
|
||||
return try self.createNode(start, .{ .for_expr = .{
|
||||
@@ -2829,6 +2870,7 @@ pub const Parser = struct {
|
||||
.body = body,
|
||||
.capture_name = capture_name,
|
||||
.index_name = index_name,
|
||||
.range_end = range_end,
|
||||
} });
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user