optionals

This commit is contained in:
agra
2026-02-22 22:16:30 +02:00
parent d3e574eae5
commit 1cc67f9b5a
17 changed files with 1952 additions and 32 deletions

View File

@@ -327,6 +327,13 @@ pub const Parser = struct {
fn parseTypeExpr(self: *Parser) anyerror!*Node {
const start = self.current.loc.start;
// Optional type: ?T
if (self.current.tag == .question) {
self.advance(); // skip '?'
const inner_type = try self.parseTypeExpr();
return try self.createNode(start, .{ .optional_type_expr = .{ .inner_type = inner_type } });
}
// Pointer type: *T
if (self.current.tag == .star) {
self.advance(); // skip '*'
@@ -1128,6 +1135,14 @@ pub const Parser = struct {
continue;
}
// Null coalescing: expr ?? default
if (self.current.tag == .question_question and Prec.null_coalesce >= min_prec) {
self.advance();
const rhs = try self.parseBinary(Prec.null_coalesce + 1);
lhs = try self.createNode(lhs.span.start, .{ .null_coalesce = .{ .lhs = lhs, .rhs = rhs } });
continue;
}
const prec = self.binaryPrec();
if (prec == 0 or prec < min_prec) break;
@@ -1291,6 +1306,20 @@ pub const Parser = struct {
} else {
return self.fail("expected field name or index after '.'");
}
} else if (self.current.tag == .question_dot) {
// Optional chaining: expr?.field
self.advance();
if (self.current.tag == .identifier) {
const field = self.tokenSlice(self.current);
self.advance();
expr = try self.createNode(expr.span.start, .{ .field_access = .{ .object = expr, .field = field, .is_optional = true } });
} else if (self.current.tag == .int_literal) {
const field = self.tokenSlice(self.current);
self.advance();
expr = try self.createNode(expr.span.start, .{ .field_access = .{ .object = expr, .field = field, .is_optional = true } });
} else {
return self.fail("expected field name after '?.'");
}
} else if (self.current.tag == .l_bracket) {
// Index or slice access: expr[expr] or expr[start..end]
self.advance();
@@ -1323,6 +1352,11 @@ pub const Parser = struct {
} });
}
}
} else if (self.current.tag == .bang) {
// Force unwrap: expr!
// Only if it's not != (bang_equal would have been lexed as a single token)
self.advance();
expr = try self.createNode(expr.span.start, .{ .force_unwrap = .{ .operand = expr } });
} else {
break;
}
@@ -1532,6 +1566,32 @@ pub const Parser = struct {
const start = self.current.loc.start;
self.advance(); // skip 'if'
// Optional binding: if val := expr { ... }
// Detect: identifier followed by :=
if (self.current.tag == .identifier and self.peekNext() == .colon_equal) {
const binding_name = self.tokenSlice(self.current);
self.advance(); // skip identifier
self.advance(); // skip :=
const source_expr = try self.parseExpr();
const then_branch = try self.parseBlock();
var else_branch: ?*Node = null;
if (self.current.tag == .kw_else) {
self.advance();
if (self.current.tag == .kw_if) {
else_branch = try self.parseIfExpr();
} else {
else_branch = try self.parseBlock();
}
}
return try self.createNode(start, .{ .if_expr = .{
.condition = source_expr,
.then_branch = then_branch,
.else_branch = else_branch,
.is_inline = false,
.binding_name = binding_name,
} });
}
// Parse condition above comparison level, leaving comparisons
// unconsumed for manual handling with match disambiguation.
var condition = try self.parseBinary(Prec.shift);
@@ -1627,6 +1687,20 @@ pub const Parser = struct {
const start = self.current.loc.start;
self.advance(); // skip 'while'
// Optional binding: while val := expr { ... }
if (self.current.tag == .identifier and self.peekNext() == .colon_equal) {
const binding_name = self.tokenSlice(self.current);
self.advance(); // skip identifier
self.advance(); // skip :=
const source_expr = try self.parseExpr();
const body = try self.parseBlock();
return try self.createNode(start, .{ .while_expr = .{
.condition = source_expr,
.body = body,
.binding_name = binding_name,
} });
}
const condition = try self.parseExpr();
const body = try self.parseBlock();
@@ -1934,15 +2008,16 @@ pub const Parser = struct {
const Prec = struct {
const none: u8 = 0;
const pipe: u8 = 1; // |>
const logical_or: u8 = 2; // or
const logical_and: u8 = 3; // and
const bit_or: u8 = 4; // |
const bit_xor: u8 = 5; // ^
const bit_and: u8 = 6; // &
const comparison: u8 = 7; // == != < <= > >= in
const shift: u8 = 8; // << >>
const additive: u8 = 9; // + -
const multiplicative: u8 = 10; // * / %
const null_coalesce: u8 = 2; // ??
const logical_or: u8 = 3; // or
const logical_and: u8 = 4; // and
const bit_or: u8 = 5; // |
const bit_xor: u8 = 6; // ^
const bit_and: u8 = 7; // &
const comparison: u8 = 8; // == != < <= > >= in
const shift: u8 = 9; // << >>
const additive: u8 = 10; // + -
const multiplicative: u8 = 11; // * / %
};
fn binaryPrec(self: *const Parser) u8 {