tuples
This commit is contained in:
119
src/parser.zig
119
src/parser.zig
@@ -165,6 +165,18 @@ pub const Parser = struct {
|
||||
return self.parseUnionDecl(name, start_pos);
|
||||
}
|
||||
|
||||
// UFCS alias: name :: ufcs target;
|
||||
if (self.current.tag == .kw_ufcs) {
|
||||
self.advance();
|
||||
if (self.current.tag != .identifier) {
|
||||
return self.fail("expected function name after 'ufcs'");
|
||||
}
|
||||
const target = self.tokenSlice(self.current);
|
||||
self.advance();
|
||||
try self.expect(.semicolon);
|
||||
return try self.createNode(start_pos, .{ .ufcs_alias = .{ .name = name, .target = target } });
|
||||
}
|
||||
|
||||
// Function declaration: (params) -> type { body } or () { body }
|
||||
if (self.current.tag == .l_paren) {
|
||||
// Look ahead: is this a function or an expression starting with `(`?
|
||||
@@ -307,25 +319,32 @@ pub const Parser = struct {
|
||||
self.advance();
|
||||
return try self.createNode(start, .{ .type_expr = .{ .name = name, .is_generic = true } });
|
||||
}
|
||||
// Function pointer type: (ParamTypes) -> ReturnType
|
||||
// Function type: (ParamTypes) -> ReturnType
|
||||
// Tuple type: (T1, T2) or (T1) — no '->' after ')'
|
||||
if (self.current.tag == .l_paren) {
|
||||
self.advance(); // skip '('
|
||||
var param_types = std.ArrayList(*Node).empty;
|
||||
while (self.current.tag != .r_paren and self.current.tag != .eof) {
|
||||
if (param_types.items.len > 0) {
|
||||
try self.expect(.comma);
|
||||
if (self.current.tag == .r_paren) break; // trailing comma ok
|
||||
}
|
||||
try param_types.append(self.allocator, try self.parseTypeExpr());
|
||||
}
|
||||
try self.expect(.r_paren);
|
||||
var return_type: ?*Node = null;
|
||||
if (self.current.tag == .arrow) {
|
||||
// '->' present: function type
|
||||
self.advance(); // skip '->'
|
||||
return_type = try self.parseTypeExpr();
|
||||
const return_type = try self.parseTypeExpr();
|
||||
return try self.createNode(start, .{ .function_type_expr = .{
|
||||
.param_types = try param_types.toOwnedSlice(self.allocator),
|
||||
.return_type = return_type,
|
||||
} });
|
||||
}
|
||||
return try self.createNode(start, .{ .function_type_expr = .{
|
||||
.param_types = try param_types.toOwnedSlice(self.allocator),
|
||||
.return_type = return_type,
|
||||
// No '->': tuple type (even for single element)
|
||||
return try self.createNode(start, .{ .tuple_type_expr = .{
|
||||
.field_types = try param_types.toOwnedSlice(self.allocator),
|
||||
.field_names = null,
|
||||
} });
|
||||
}
|
||||
|
||||
@@ -1162,14 +1181,18 @@ pub const Parser = struct {
|
||||
// Dereference: expr.*
|
||||
self.advance();
|
||||
expr = try self.createNode(expr.span.start, .{ .deref_expr = .{ .operand = expr } });
|
||||
} else {
|
||||
// Field access
|
||||
if (self.current.tag != .identifier) {
|
||||
return self.fail("expected field name after '.'");
|
||||
}
|
||||
} else if (self.current.tag == .identifier) {
|
||||
// Named field access: expr.field
|
||||
const field = self.tokenSlice(self.current);
|
||||
self.advance();
|
||||
expr = try self.createNode(expr.span.start, .{ .field_access = .{ .object = expr, .field = field } });
|
||||
} else if (self.current.tag == .int_literal) {
|
||||
// Numeric field access: tuple.0, tuple.1
|
||||
const field = self.tokenSlice(self.current);
|
||||
self.advance();
|
||||
expr = try self.createNode(expr.span.start, .{ .field_access = .{ .object = expr, .field = field } });
|
||||
} else {
|
||||
return self.fail("expected field name or index after '.'");
|
||||
}
|
||||
} else if (self.current.tag == .l_bracket) {
|
||||
// Index or slice access: expr[expr] or expr[start..end]
|
||||
@@ -1314,11 +1337,30 @@ pub const Parser = struct {
|
||||
if (self.isLambda()) {
|
||||
return self.parseLambda();
|
||||
}
|
||||
// Grouped expression
|
||||
self.advance();
|
||||
const expr = try self.parseExpr();
|
||||
self.advance(); // skip '('
|
||||
|
||||
// Check for named tuple: (name: expr, ...)
|
||||
if (self.current.tag == .identifier and self.peekNext() == .colon) {
|
||||
return self.parseTupleLiteralNamed(start);
|
||||
}
|
||||
|
||||
// Empty parens or first expression
|
||||
if (self.current.tag == .r_paren) {
|
||||
self.advance();
|
||||
// () — empty tuple
|
||||
return try self.createNode(start, .{ .tuple_literal = .{ .elements = &.{} } });
|
||||
}
|
||||
|
||||
const first = try self.parseExpr();
|
||||
|
||||
// Check for comma → tuple
|
||||
if (self.current.tag == .comma) {
|
||||
return self.finishTupleAfterFirst(start, first);
|
||||
}
|
||||
|
||||
// No comma → grouping
|
||||
try self.expect(.r_paren);
|
||||
return expr;
|
||||
return first;
|
||||
},
|
||||
.kw_f32, .kw_f64, .kw_Type => {
|
||||
// Type keyword used as expression (for type aliases: SOME_TYPE :: f64;)
|
||||
@@ -1607,6 +1649,42 @@ pub const Parser = struct {
|
||||
return try self.createNode(start_pos, .{ .match_expr = .{ .subject = subject, .arms = try arms.toOwnedSlice(self.allocator) } });
|
||||
}
|
||||
|
||||
/// Parse a named tuple literal: (name: expr, name: expr, ...)
|
||||
/// Called after '(' has been consumed and we've verified identifier + colon pattern.
|
||||
fn parseTupleLiteralNamed(self: *Parser, start: u32) anyerror!*Node {
|
||||
var elements = std.ArrayList(ast.TupleElement).empty;
|
||||
while (self.current.tag != .r_paren and self.current.tag != .eof) {
|
||||
if (self.current.tag != .identifier) {
|
||||
return self.fail("expected field name in named tuple");
|
||||
}
|
||||
const name = self.tokenSlice(self.current);
|
||||
self.advance();
|
||||
try self.expect(.colon);
|
||||
const value = try self.parseExpr();
|
||||
try elements.append(self.allocator, .{ .name = name, .value = value });
|
||||
if (self.current.tag == .comma) {
|
||||
self.advance();
|
||||
} else break;
|
||||
}
|
||||
try self.expect(.r_paren);
|
||||
return try self.createNode(start, .{ .tuple_literal = .{ .elements = try elements.toOwnedSlice(self.allocator) } });
|
||||
}
|
||||
|
||||
/// Finish parsing a tuple after the first positional element and a comma.
|
||||
/// Called with first element already parsed and current token is ','.
|
||||
fn finishTupleAfterFirst(self: *Parser, start: u32, first: *Node) anyerror!*Node {
|
||||
var elements = std.ArrayList(ast.TupleElement).empty;
|
||||
try elements.append(self.allocator, .{ .name = null, .value = first });
|
||||
while (self.current.tag == .comma) {
|
||||
self.advance(); // skip ','
|
||||
if (self.current.tag == .r_paren) break; // trailing comma: (42,)
|
||||
const value = try self.parseExpr();
|
||||
try elements.append(self.allocator, .{ .name = null, .value = value });
|
||||
}
|
||||
try self.expect(.r_paren);
|
||||
return try self.createNode(start, .{ .tuple_literal = .{ .elements = try elements.toOwnedSlice(self.allocator) } });
|
||||
}
|
||||
|
||||
/// Save state, skip past matching parens, return the tag of the next token, then restore.
|
||||
/// Returns null if no matching ')' found before EOF.
|
||||
fn peekPastParens(self: *Parser) ?Tag {
|
||||
@@ -1755,7 +1833,7 @@ pub const Parser = struct {
|
||||
.kw_and => 2,
|
||||
.pipe => 3,
|
||||
.ampersand => 3,
|
||||
.equal_equal, .bang_equal, .less, .less_equal, .greater, .greater_equal => 4,
|
||||
.equal_equal, .bang_equal, .less, .less_equal, .greater, .greater_equal, .kw_in => 4,
|
||||
.plus, .minus => 5,
|
||||
.star, .slash, .percent => 6,
|
||||
else => 0,
|
||||
@@ -1779,6 +1857,7 @@ pub const Parser = struct {
|
||||
.less_equal => .lte,
|
||||
.greater => .gt,
|
||||
.greater_equal => .gte,
|
||||
.kw_in => .in_op,
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
@@ -1797,6 +1876,14 @@ pub const Parser = struct {
|
||||
};
|
||||
}
|
||||
|
||||
/// Peek at the next token's tag without consuming.
|
||||
fn peekNext(self: *Parser) Tag {
|
||||
const saved_lexer = self.lexer;
|
||||
const tok = self.lexer.next();
|
||||
self.lexer = saved_lexer;
|
||||
return tok.tag;
|
||||
}
|
||||
|
||||
fn advance(self: *Parser) void {
|
||||
self.prev_end = self.current.loc.end;
|
||||
self.current = self.lexer.next();
|
||||
|
||||
Reference in New Issue
Block a user