Files
sx/src/parser.zig
2026-02-14 19:33:33 +02:00

1920 lines
82 KiB
Zig

const std = @import("std");
const Token = @import("token.zig").Token;
const Tag = @import("token.zig").Tag;
const Lexer = @import("lexer.zig").Lexer;
const ast = @import("ast.zig");
const Node = ast.Node;
const Type = @import("types.zig").Type;
const errors = @import("errors.zig");
pub const Parser = struct {
lexer: Lexer,
current: Token,
source: [:0]const u8,
allocator: std.mem.Allocator,
err_msg: ?[]const u8,
err_offset: ?u32 = null,
prev_end: u32 = 0,
diagnostics: ?*errors.DiagnosticList = null,
pub fn init(allocator: std.mem.Allocator, source: [:0]const u8) Parser {
var lexer = Lexer.init(source);
const first = lexer.next();
return .{
.lexer = lexer,
.current = first,
.source = source,
.allocator = allocator,
.err_msg = null,
.err_offset = null,
};
}
fn createNode(self: *Parser, start: u32, data: Node.Data) !*Node {
const node = try self.allocator.create(Node);
node.* = .{ .span = .{ .start = start, .end = self.prev_end }, .data = data };
return node;
}
pub fn parse(self: *Parser) anyerror!*Node {
var decls = std.ArrayList(*Node).empty;
while (self.current.tag != .eof) {
const decl = try self.parseTopLevel();
try decls.append(self.allocator, decl);
}
const node = try self.createNode(0, .{ .root = .{ .decls = try decls.toOwnedSlice(self.allocator) } });
return node;
}
fn parseTopLevel(self: *Parser) anyerror!*Node {
const start = self.current.loc.start;
// Top-level flat import: #import "path";
if (self.current.tag == .hash_import) {
self.advance();
if (self.current.tag != .string_literal) {
return self.fail("expected string path after '#import'");
}
const raw = self.tokenSlice(self.current);
const path = raw[1 .. raw.len - 1];
self.advance();
try self.expect(.semicolon);
return try self.createNode(start, .{ .import_decl = .{ .path = path, .name = null } });
}
// Top-level #library directive: #library "libname";
if (self.current.tag == .hash_library) {
self.advance();
if (self.current.tag != .string_literal) {
return self.fail("expected string after '#library'");
}
const raw = self.tokenSlice(self.current);
const lib_name = raw[1 .. raw.len - 1];
self.advance();
try self.expect(.semicolon);
return try self.createNode(start, .{ .library_decl = .{ .lib_name = lib_name } });
}
// Top-level #run directive
if (self.current.tag == .hash_run) {
self.advance();
const expr = try self.parseExpr();
try self.expect(.semicolon);
return try self.createNode(start, .{ .comptime_expr = .{ .expr = expr } });
}
// All top-level declarations start with an identifier
if (self.current.tag != .identifier) {
return self.fail("expected identifier at top level");
}
const name = self.tokenSlice(self.current);
self.advance();
// IDENT :: ...
if (self.current.tag == .colon_colon) {
self.advance();
return self.parseConstBinding(name, start);
}
// IDENT : type : value; (typed constant)
// IDENT : type = value; (typed variable)
if (self.current.tag == .colon) {
self.advance();
return self.parseTypedBinding(name, start);
}
// IDENT := value; (variable)
if (self.current.tag == .colon_equal) {
self.advance();
const value = try self.parseExpr();
try self.expectSemicolonAfter(value);
return try self.createNode(start, .{ .var_decl = .{ .name = name, .type_annotation = null, .value = value } });
}
return self.fail("expected '::', ':=', or ':' after identifier");
}
fn parseConstBinding(self: *Parser, name: []const u8, start_pos: u32) anyerror!*Node {
// After `::`
// Could be: #run expr, enum { ... }, (params) -> type { body }, or expr;
// Namespaced import: name :: #import "path";
if (self.current.tag == .hash_import) {
self.advance();
if (self.current.tag != .string_literal) {
return self.fail("expected string path after '#import'");
}
const raw = self.tokenSlice(self.current);
const path = raw[1 .. raw.len - 1];
self.advance();
try self.expect(.semicolon);
return try self.createNode(start_pos, .{ .import_decl = .{ .path = path, .name = name } });
}
// Compile-time evaluation: name :: #run expr;
if (self.current.tag == .hash_run) {
const run_start = self.current.loc.start;
self.advance();
const inner = try self.parseExpr();
try self.expect(.semicolon);
const ct = try self.createNode(run_start, .{ .comptime_expr = .{ .expr = inner } });
return try self.createNode(start_pos, .{ .const_decl = .{ .name = name, .type_annotation = null, .value = ct } });
}
// Built-in declaration: name :: #builtin;
if (self.current.tag == .hash_builtin) {
const bi_start = self.current.loc.start;
self.advance();
try self.expect(.semicolon);
const bi = try self.createNode(bi_start, .{ .builtin_expr = {} });
return try self.createNode(start_pos, .{ .const_decl = .{ .name = name, .type_annotation = null, .value = bi } });
}
// Enum declaration
if (self.current.tag == .kw_enum) {
return self.parseEnumDecl(name, start_pos);
}
// Struct declaration
if (self.current.tag == .kw_struct) {
return self.parseStructDecl(name, start_pos);
}
// C-style union declaration
if (self.current.tag == .kw_union) {
return self.parseUnionDecl(name, start_pos);
}
// Function declaration: (params) -> type { body } or () { body }
if (self.current.tag == .l_paren) {
// Look ahead: is this a function or an expression starting with `(`?
// Heuristic: if after matching parens we see `{` or `->`, it's a function.
if (self.isFunctionDef()) {
return self.parseFnDecl(name, start_pos);
}
}
// Bare block shorthand: name :: { body } is equivalent to name :: () { body }
if (self.current.tag == .l_brace) {
const body = try self.parseBlock();
return try self.createNode(start_pos, .{ .fn_decl = .{ .name = name, .params = &.{}, .return_type = null, .body = body } });
}
// Otherwise it's a constant expression
const value = try self.parseExpr();
// name :: type_expr #builtin; — builtin with type annotation
if (self.current.tag == .hash_builtin) {
const bi_start = self.current.loc.start;
self.advance();
try self.expect(.semicolon);
const bi = try self.createNode(bi_start, .{ .builtin_expr = {} });
return try self.createNode(start_pos, .{ .const_decl = .{ .name = name, .type_annotation = value, .value = bi } });
}
// name :: type_expr #foreign; — foreign with type annotation
if (self.current.tag == .hash_foreign) {
const fi_start = self.current.loc.start;
self.advance();
try self.expect(.semicolon);
const fi = try self.createNode(fi_start, .{ .foreign_expr = {} });
return try self.createNode(start_pos, .{ .const_decl = .{ .name = name, .type_annotation = value, .value = fi } });
}
try self.expect(.semicolon);
return try self.createNode(start_pos, .{ .const_decl = .{ .name = name, .type_annotation = null, .value = value } });
}
fn parseTypedBinding(self: *Parser, name: []const u8, start_pos: u32) anyerror!*Node {
// After `name :`
// Parse type
const type_node = try self.parseTypeExpr();
if (self.current.tag == .colon) {
// name : type : value; (typed constant)
self.advance();
const value = try self.parseExpr();
try self.expectSemicolonAfter(value);
return try self.createNode(start_pos, .{ .const_decl = .{ .name = name, .type_annotation = type_node, .value = value } });
}
if (self.current.tag == .equal) {
// name : type = value; (typed variable)
self.advance();
const value = try self.parseExpr();
try self.expectSemicolonAfter(value);
return try self.createNode(start_pos, .{ .var_decl = .{ .name = name, .type_annotation = type_node, .value = value } });
}
if (self.current.tag == .semicolon) {
// name : type; (default-initialized variable)
self.advance();
return try self.createNode(start_pos, .{ .var_decl = .{ .name = name, .type_annotation = type_node, .value = null } });
}
return self.fail("expected ':', '=' or ';' after type annotation");
}
fn parseTypeExpr(self: *Parser) anyerror!*Node {
const start = self.current.loc.start;
// Pointer type: *T
if (self.current.tag == .star) {
self.advance(); // skip '*'
const pointee_type = try self.parseTypeExpr();
return try self.createNode(start, .{ .pointer_type_expr = .{ .pointee_type = pointee_type } });
}
// Array type: [N]T, Slice type: []T, Many-pointer type: [*]T, Sentinel slice: [:0]T
if (self.current.tag == .l_bracket) {
self.advance(); // skip '['
if (self.current.tag == .colon) {
// Sentinel-terminated slice: [:0]T
self.advance(); // skip ':'
if (self.current.tag != .int_literal) {
return self.fail("expected sentinel value after ':'");
}
const sentinel_str = self.tokenSlice(self.current);
self.advance(); // skip sentinel value
try self.expect(.r_bracket); // expect ']'
const elem_type = try self.parseTypeExpr();
// Build name like "[:0]u8" for type resolution
const elem_name = if (elem_type.data == .type_expr) elem_type.data.type_expr.name else "?";
const name = try std.fmt.allocPrint(self.allocator, "[:{s}]{s}", .{ sentinel_str, elem_name });
return try self.createNode(start, .{ .type_expr = .{ .name = name } });
}
if (self.current.tag == .r_bracket) {
// Slice type: []T
self.advance(); // skip ']'
const elem_type = try self.parseTypeExpr();
return try self.createNode(start, .{ .slice_type_expr = .{ .element_type = elem_type } });
}
if (self.current.tag == .star) {
// Many-pointer type: [*]T
self.advance(); // skip '*'
try self.expect(.r_bracket); // expect ']'
const elem_type = try self.parseTypeExpr();
return try self.createNode(start, .{ .many_pointer_type_expr = .{ .element_type = elem_type } });
}
const len_node = try self.parseExpr();
try self.expect(.r_bracket);
const elem_type = try self.parseTypeExpr();
return try self.createNode(start, .{ .array_type_expr = .{ .length = len_node, .element_type = elem_type } });
}
// Generic type parameter introduction: $T
if (self.current.tag == .dollar) {
self.advance();
if (self.current.tag != .identifier) {
return self.fail("expected type parameter name after '$'");
}
const name = self.tokenSlice(self.current);
self.advance();
return try self.createNode(start, .{ .type_expr = .{ .name = name, .is_generic = true } });
}
// Function pointer type: (ParamTypes) -> ReturnType
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);
}
try param_types.append(self.allocator, try self.parseTypeExpr());
}
try self.expect(.r_paren);
var return_type: ?*Node = null;
if (self.current.tag == .arrow) {
self.advance(); // skip '->'
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,
} });
}
if (self.current.tag.isTypeKeyword() or self.current.tag == .identifier) {
var name = self.tokenSlice(self.current);
self.advance();
// Qualified name: ns.Type or ns.Type(args)
while (self.current.tag == .dot) {
const dot_lexer = self.lexer;
const dot_current = self.current;
const dot_prev_end = self.prev_end;
self.advance();
if (self.current.tag == .identifier or self.current.tag.isTypeKeyword()) {
name = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ name, self.tokenSlice(self.current) });
self.advance();
} else {
// Not a qualified name continuation — restore the dot
self.lexer = dot_lexer;
self.current = dot_current;
self.prev_end = dot_prev_end;
break;
}
}
// Parameterized type: Vector(N, T) or later generic struct instantiation
if (self.current.tag == .l_paren) {
self.advance(); // skip '('
var args = std.ArrayList(*Node).empty;
while (self.current.tag != .r_paren and self.current.tag != .eof) {
if (args.items.len > 0) {
try self.expect(.comma);
}
// Args can be int literals (for lengths) or type expressions
if (self.current.tag == .int_literal) {
const arg_start = self.current.loc.start;
const text = self.tokenSlice(self.current);
const value = std.fmt.parseInt(i64, text, 10) catch 0;
self.advance();
try args.append(self.allocator, try self.createNode(arg_start, .{ .int_literal = .{ .value = value } }));
} else {
try args.append(self.allocator, try self.parseTypeExpr());
}
}
try self.expect(.r_paren);
return try self.createNode(start, .{ .parameterized_type_expr = .{
.name = name,
.args = try args.toOwnedSlice(self.allocator),
} });
}
return try self.createNode(start, .{ .type_expr = .{ .name = name } });
}
// Inline struct type in type position: struct { ... }
if (self.current.tag == .kw_struct) {
return try self.parseStructDecl("__anon", start);
}
// Inline C-style union in type position: union { ... }
if (self.current.tag == .kw_union) {
return try self.parseUnionDecl("__anon", start);
}
// Inline enum type in type position: enum { ... }
if (self.current.tag == .kw_enum) {
return try self.parseEnumDecl("__anon", start);
}
return self.fail("expected type name");
}
fn parseEnumDecl(self: *Parser, name: []const u8, start_pos: u32) anyerror!*Node {
self.advance(); // skip 'enum'
// Check for 'flags' modifier: enum flags { ... }
var is_flags = false;
if (self.current.tag == .identifier and std.mem.eql(u8, self.tokenSlice(self.current), "flags")) {
is_flags = true;
self.advance();
}
// Check for optional backing type: enum u8 { ... } or enum flags u32 { ... }
var backing_type: ?*Node = null;
if (self.current.tag != .l_brace) {
backing_type = try self.parseTypeExpr();
}
try self.expect(.l_brace);
var variant_names = std.ArrayList([]const u8).empty;
var variant_types = std.ArrayList(?*Node).empty;
var variant_values = std.ArrayList(?*Node).empty;
var has_any_type = false;
var has_any_value = false;
while (self.current.tag != .r_brace and self.current.tag != .eof) {
if (self.current.tag != .identifier) {
return self.fail("expected variant name");
}
try variant_names.append(self.allocator, self.tokenSlice(self.current));
self.advance();
if (self.current.tag == .colon_colon) {
// Explicit value: name :: expr; or name :: expr: type;
self.advance();
const val_expr = try self.parseExpr();
try variant_values.append(self.allocator, val_expr);
has_any_value = true;
// Check for payload type after value: name :: 0x300: KeyData
if (self.current.tag == .colon) {
if (is_flags) {
return self.fail("flags enum variants cannot have payloads");
}
self.advance();
const vtype = try self.parseTypeExpr();
try variant_types.append(self.allocator, vtype);
has_any_type = true;
} else {
try variant_types.append(self.allocator, null);
}
} else if (self.current.tag == .colon) {
// Typed variant: name: type;
if (is_flags) {
return self.fail("flags enum variants cannot have payloads");
}
self.advance();
const vtype = try self.parseTypeExpr();
try variant_types.append(self.allocator, vtype);
try variant_values.append(self.allocator, null);
has_any_type = true;
} else {
// Void variant: name;
try variant_types.append(self.allocator, null);
try variant_values.append(self.allocator, null);
}
if (self.current.tag == .semicolon) {
self.advance();
}
}
try self.expect(.r_brace);
// Always produce enum_decl; variant_types distinguishes payload-less from tagged
return try self.createNode(start_pos, .{ .enum_decl = .{
.name = name,
.variant_names = try variant_names.toOwnedSlice(self.allocator),
.variant_types = if (has_any_type) try variant_types.toOwnedSlice(self.allocator) else &.{},
.is_flags = is_flags,
.variant_values = if (has_any_value) try variant_values.toOwnedSlice(self.allocator) else &.{},
.backing_type = backing_type,
} });
}
fn parseUnionDecl(self: *Parser, name: []const u8, start_pos: u32) anyerror!*Node {
self.advance(); // skip 'union'
try self.expect(.l_brace);
var field_names = std.ArrayList([]const u8).empty;
var field_types = std.ArrayList(*Node).empty;
var anon_idx: u32 = 0;
while (self.current.tag != .r_brace and self.current.tag != .eof) {
// Anonymous struct field: struct { x, y: f32; };
if (self.current.tag == .kw_struct) {
const anon_field = try std.fmt.allocPrint(self.allocator, "__anon_{d}", .{anon_idx});
anon_idx += 1;
const anon_struct_name = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ name, anon_field });
const struct_node = try self.parseStructDecl(anon_struct_name, self.current.loc.start);
try field_names.append(self.allocator, anon_field);
try field_types.append(self.allocator, struct_node);
if (self.current.tag == .semicolon) {
self.advance();
}
continue;
}
if (self.current.tag != .identifier) {
return self.fail("expected field name or 'struct'");
}
try field_names.append(self.allocator, self.tokenSlice(self.current));
self.advance();
if (self.current.tag != .colon) {
return self.fail("union fields must have a type");
}
self.advance();
const ftype = try self.parseTypeExpr();
try field_types.append(self.allocator, ftype);
if (self.current.tag == .semicolon) {
self.advance();
}
}
try self.expect(.r_brace);
return try self.createNode(start_pos, .{ .union_decl = .{
.name = name,
.field_names = try field_names.toOwnedSlice(self.allocator),
.field_types = try field_types.toOwnedSlice(self.allocator),
} });
}
fn parseStructDecl(self: *Parser, name: []const u8, start_pos: u32) anyerror!*Node {
self.advance(); // skip 'struct'
// Optional type params: struct($N: u32, $T: Type) { ... }
var type_params = std.ArrayList(ast.StructTypeParam).empty;
if (self.current.tag == .l_paren) {
self.advance(); // skip '('
while (self.current.tag != .r_paren and self.current.tag != .eof) {
if (type_params.items.len > 0) {
try self.expect(.comma);
}
// Expect $name : constraint
try self.expect(.dollar);
if (self.current.tag != .identifier) {
return self.fail("expected type parameter name after '$'");
}
const param_name = self.tokenSlice(self.current);
self.advance();
try self.expect(.colon);
const constraint = try self.parseTypeExpr();
try type_params.append(self.allocator, .{ .name = param_name, .constraint = constraint });
}
try self.expect(.r_paren);
}
try self.expect(.l_brace);
var field_names = std.ArrayList([]const u8).empty;
var field_types = std.ArrayList(*Node).empty;
var field_defaults = std.ArrayList(?*Node).empty;
while (self.current.tag != .r_brace and self.current.tag != .eof) {
// Parse field group: name1, name2, ...: type (= default)?;
var group_names = std.ArrayList([]const u8).empty;
if (self.current.tag != .identifier) {
return self.fail("expected field name in struct");
}
try group_names.append(self.allocator, self.tokenSlice(self.current));
self.advance();
while (self.current.tag == .comma) {
self.advance(); // skip ','
if (self.current.tag != .identifier) {
return self.fail("expected field name after ','");
}
try group_names.append(self.allocator, self.tokenSlice(self.current));
self.advance();
}
try self.expect(.colon);
const field_type = try self.parseTypeExpr();
// Check for default value: = expr
var default_val: ?*Node = null;
if (self.current.tag == .equal) {
self.advance();
default_val = try self.parseExpr();
}
// All names in the group share the same type and default
for (group_names.items) |fname| {
// `_` is an ignore identifier — auto-rename to unique internal name
const actual_name = if (std.mem.eql(u8, fname, "_"))
try std.fmt.allocPrint(self.allocator, "_{d}", .{field_names.items.len})
else
fname;
try field_names.append(self.allocator, actual_name);
try field_types.append(self.allocator, field_type);
try field_defaults.append(self.allocator, default_val);
}
if (self.current.tag == .semicolon) {
self.advance();
}
}
try self.expect(.r_brace);
return try self.createNode(start_pos, .{ .struct_decl = .{
.name = name,
.field_names = try field_names.toOwnedSlice(self.allocator),
.field_types = try field_types.toOwnedSlice(self.allocator),
.field_defaults = try field_defaults.toOwnedSlice(self.allocator),
.type_params = try type_params.toOwnedSlice(self.allocator),
} });
}
fn parseStructLiteral(self: *Parser, struct_name: ?[]const u8, type_expr: ?*Node, start_pos: u32) anyerror!*Node {
try self.expect(.l_brace);
var field_inits = std.ArrayList(ast.StructFieldInit).empty;
while (self.current.tag != .r_brace and self.current.tag != .eof) {
if (field_inits.items.len > 0) {
try self.expect(.comma);
}
// Check if this is a named field: identifier followed by '='
if (self.current.tag == .identifier) {
const saved_lexer = self.lexer;
const saved_current = self.current;
const saved_prev_end = self.prev_end;
const fname = self.tokenSlice(self.current);
const ident_start = self.current.loc.start;
self.advance();
if (self.current.tag == .equal) {
// Named field: name = expr
self.advance(); // skip '='
const value = try self.parseExpr();
try field_inits.append(self.allocator, .{ .name = fname, .value = value });
continue;
} else if (self.current.tag == .comma or self.current.tag == .r_brace) {
// Shorthand: just an identifier (name = identifier with same name)
const ident_node = try self.createNode(ident_start, .{ .identifier = .{ .name = fname } });
try field_inits.append(self.allocator, .{ .name = fname, .value = ident_node });
continue;
}
// Not named — backtrack and parse as positional expression
self.lexer = saved_lexer;
self.current = saved_current;
self.prev_end = saved_prev_end;
}
// Positional field: just an expression
const value = try self.parseExpr();
try field_inits.append(self.allocator, .{ .name = null, .value = value });
}
try self.expect(.r_brace);
return try self.createNode(start_pos, .{ .struct_literal = .{
.struct_name = struct_name,
.type_expr = type_expr,
.field_inits = try field_inits.toOwnedSlice(self.allocator),
} });
}
fn reconstructQualifiedName(self: *Parser, node: *Node) ![]const u8 {
if (node.data == .identifier) return node.data.identifier.name;
if (node.data == .field_access) {
const obj_name = try self.reconstructQualifiedName(node.data.field_access.object);
return std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ obj_name, node.data.field_access.field });
}
return error.ParseError;
}
/// Parse a parenthesized parameter list: `(name: type, $T: Type, args: ..Any)`
/// Handles `$` generic params, `..` variadic marker, and comptime detection.
/// Expects opening `(` already NOT consumed — this function consumes `(` through `)`.
fn parseParams(self: *Parser) anyerror![]const ast.Param {
try self.expect(.l_paren);
var params = std.ArrayList(ast.Param).empty;
while (self.current.tag != .r_paren and self.current.tag != .eof) {
if (params.items.len > 0) {
try self.expect(.comma);
}
var is_ct_param = false;
if (self.current.tag == .dollar) {
is_ct_param = true;
self.advance();
}
if (self.current.tag != .identifier) {
return self.fail("expected parameter name");
}
const param_name = self.tokenSlice(self.current);
const param_name_span = ast.Span{ .start = self.current.loc.start, .end = self.current.loc.end };
self.advance();
try self.expect(.colon);
const is_variadic = self.current.tag == .dot_dot;
if (is_variadic) self.advance();
const param_type = try self.parseTypeExpr();
var is_comptime_param = false;
if (is_ct_param and param_type.data == .type_expr) {
const constraint_name = param_type.data.type_expr.name;
if (std.mem.eql(u8, constraint_name, "Type")) {
param_type.data = .{ .type_expr = .{ .name = param_name, .is_generic = true } };
} else {
is_comptime_param = true;
}
}
try params.append(self.allocator, .{ .name = param_name, .name_span = param_name_span, .type_expr = param_type, .is_variadic = is_variadic, .is_comptime = is_comptime_param });
}
for (params.items, 0..) |param, i| {
if (param.is_variadic and i != params.items.len - 1) {
return self.fail("variadic parameter must be the last parameter");
}
}
try self.expect(.r_paren);
return try params.toOwnedSlice(self.allocator);
}
/// Recursively find all generic type names ($T) in a type expression tree.
fn collectGenericNames(node: *Node, list: *std.ArrayList([]const u8), allocator: std.mem.Allocator) void {
switch (node.data) {
.type_expr => |te| {
if (te.is_generic) list.append(allocator, te.name) catch {};
},
.pointer_type_expr => |pte| collectGenericNames(pte.pointee_type, list, allocator),
.many_pointer_type_expr => |mpte| collectGenericNames(mpte.element_type, list, allocator),
.slice_type_expr => |ste| collectGenericNames(ste.element_type, list, allocator),
.array_type_expr => |ate| collectGenericNames(ate.element_type, list, allocator),
.parameterized_type_expr => |pte| {
for (pte.args) |arg| collectGenericNames(arg, list, allocator);
},
else => {},
}
}
/// Collect generic type params and comptime value params from parameter annotations.
fn collectTypeParams(self: *Parser, params: []const ast.Param) ![]const ast.StructTypeParam {
var type_params = std.ArrayList(ast.StructTypeParam).empty;
for (params) |param| {
if (param.is_comptime) {
var found = false;
for (type_params.items) |existing| {
if (std.mem.eql(u8, existing.name, param.name)) {
found = true;
break;
}
}
if (!found) {
try type_params.append(self.allocator, .{ .name = param.name, .constraint = param.type_expr });
}
} else {
// Collect all generic type params found anywhere in the type expression
var generic_names = std.ArrayList([]const u8).empty;
collectGenericNames(param.type_expr, &generic_names, self.allocator);
for (generic_names.items) |gen_name| {
var found = false;
for (type_params.items) |existing| {
if (std.mem.eql(u8, existing.name, gen_name)) {
found = true;
break;
}
}
if (!found) {
const type_constraint = self.createNode(param.type_expr.span.start, .{ .type_expr = .{ .name = "Type" } }) catch continue;
type_params.append(self.allocator, .{ .name = gen_name, .constraint = type_constraint }) catch {};
}
}
}
}
return try type_params.toOwnedSlice(self.allocator);
}
fn parseFnDecl(self: *Parser, name: []const u8, start_pos: u32) anyerror!*Node {
const params = try self.parseParams();
// Optional return type
var return_type: ?*Node = null;
if (self.current.tag == .arrow) {
self.advance();
return_type = try self.parseTypeExpr();
}
// Body: block `{ ... }`, arrow `=> expr;`, #builtin, or #foreign marker
const body = if (self.current.tag == .hash_builtin) blk: {
const bi_start = self.current.loc.start;
self.advance();
try self.expect(.semicolon);
break :blk try self.createNode(bi_start, .{ .builtin_expr = {} });
} else if (self.current.tag == .hash_foreign) blk: {
const fi_start = self.current.loc.start;
self.advance();
try self.expect(.semicolon);
break :blk try self.createNode(fi_start, .{ .foreign_expr = {} });
} else if (self.current.tag == .fat_arrow) blk: {
self.advance();
const expr = try self.parseExpr();
try self.expect(.semicolon);
const stmts = try self.allocator.alloc(*Node, 1);
stmts[0] = expr;
const block_start = expr.span.start;
const block = try self.createNode(block_start, .{ .block = .{ .stmts = stmts } });
break :blk block;
} else try self.parseBlock();
const type_params = try self.collectTypeParams(params);
return try self.createNode(start_pos, .{ .fn_decl = .{
.name = name,
.params = params,
.return_type = return_type,
.body = body,
.type_params = type_params,
} });
}
fn parseBlock(self: *Parser) anyerror!*Node {
const start = self.current.loc.start;
try self.expect(.l_brace);
var stmts = std.ArrayList(*Node).empty;
while (self.current.tag != .r_brace and self.current.tag != .eof) {
const stmt = try self.parseStmt();
try stmts.append(self.allocator, stmt);
}
try self.expect(.r_brace);
return try self.createNode(start, .{ .block = .{ .stmts = try stmts.toOwnedSlice(self.allocator) } });
}
/// Block-form if/match/while/bare blocks don't require trailing semicolon.
fn expectSemicolonAfter(self: *Parser, expr: *Node) anyerror!void {
const needs_semi = switch (expr.data) {
.if_expr => |ie| ie.is_inline,
.match_expr => false,
.while_expr => false,
.for_expr => false,
.block => false,
else => true,
};
if (needs_semi) {
try self.expect(.semicolon);
} else if (self.current.tag == .semicolon) {
self.advance(); // consume optional ;
}
}
pub fn parseStmt(self: *Parser) anyerror!*Node {
// Check if this is a declaration (IDENT followed by ::, :=, or : type)
if (self.current.tag == .identifier) {
const saved_lexer = self.lexer;
const saved_current = self.current;
const saved_prev_end = self.prev_end;
const start = self.current.loc.start;
const name = self.tokenSlice(self.current);
self.advance();
if (self.current.tag == .colon_colon) {
self.advance();
return self.parseConstBinding(name, start);
}
if (self.current.tag == .colon_equal) {
self.advance();
const value = try self.parseExpr();
try self.expectSemicolonAfter(value);
return try self.createNode(start, .{ .var_decl = .{ .name = name, .type_annotation = null, .value = value } });
}
if (self.current.tag == .colon) {
self.advance();
return self.parseTypedBinding(name, start);
}
// Check for assignment operators
if (self.isAssignOp()) {
const op = self.assignOp();
self.advance();
const value = try self.parseExpr();
try self.expect(.semicolon);
const target = try self.createNode(start, .{ .identifier = .{ .name = name } });
return try self.createNode(start, .{ .assignment = .{ .target = target, .op = op, .value = value } });
}
// Not a declaration or assignment — backtrack and parse as expression
self.lexer = saved_lexer;
self.current = saved_current;
self.prev_end = saved_prev_end;
}
// Return statement: return expr; or return;
if (self.current.tag == .kw_return) {
const start = self.current.loc.start;
self.advance();
if (self.current.tag == .semicolon) {
self.advance();
return try self.createNode(start, .{ .return_stmt = .{ .value = null } });
}
const value = try self.parseExpr();
try self.expect(.semicolon);
return try self.createNode(start, .{ .return_stmt = .{ .value = value } });
}
// Defer statement: defer <expr>;
if (self.current.tag == .kw_defer) {
const start = self.current.loc.start;
self.advance();
const deferred = try self.parseExpr();
try self.expect(.semicolon);
return try self.createNode(start, .{ .defer_stmt = .{ .expr = deferred } });
}
// Break statement: break;
if (self.current.tag == .kw_break) {
const start = self.current.loc.start;
self.advance();
try self.expect(.semicolon);
return try self.createNode(start, .{ .break_expr = {} });
}
// Continue statement: continue;
if (self.current.tag == .kw_continue) {
const start = self.current.loc.start;
self.advance();
try self.expect(.semicolon);
return try self.createNode(start, .{ .continue_expr = {} });
}
// Insert directive: #insert <expr>;
if (self.current.tag == .hash_insert) {
const start = self.current.loc.start;
self.advance();
const inner = try self.parseExpr();
try self.expect(.semicolon);
return try self.createNode(start, .{ .insert_expr = .{ .expr = inner } });
}
// Block-form if/while/for as statements — parse directly to prevent
// postfix chaining (e.g. `if cond { ... }.field` being misparsed)
if (self.current.tag == .kw_if) {
const expr = try self.parseIfExpr();
try self.expectSemicolonAfter(expr);
return expr;
}
if (self.current.tag == .kw_while) {
const expr = try self.parsePrimary();
try self.expectSemicolonAfter(expr);
return expr;
}
if (self.current.tag == .kw_for) {
const expr = try self.parsePrimary();
try self.expectSemicolonAfter(expr);
return expr;
}
// Expression statement
const expr = try self.parseExpr();
// Check for field assignment: expr = value; (e.g. a.b = 1;)
if (self.isAssignOp()) {
const op = self.assignOp();
self.advance();
const value = try self.parseExpr();
try self.expect(.semicolon);
return try self.createNode(expr.span.start, .{ .assignment = .{ .target = expr, .op = op, .value = value } });
}
// Block-form if/match/while/bare blocks don't require trailing semicolon
try self.expectSemicolonAfter(expr);
return expr;
}
// ---- Expression parsing (Pratt / precedence climbing) ----
pub fn parseExpr(self: *Parser) anyerror!*Node {
return self.parseBinary(0);
}
fn parseBinary(self: *Parser, min_prec: u8) anyerror!*Node {
const lhs = try self.parseUnary();
return self.parseBinaryRhs(lhs, min_prec);
}
fn parseBinaryRhs(self: *Parser, initial_lhs: *Node, min_prec: u8) anyerror!*Node {
var lhs = initial_lhs;
while (true) {
const prec = self.binaryPrec();
if (prec == 0 or prec < min_prec) break;
const op = self.binaryOp() orelse break;
self.advance();
const rhs = try self.parseBinary(prec + 1);
// Chained comparison detection: if op is a comparison and the next
// token is also a comparison at the same precedence, accumulate
// into a ChainedComparison node.
if (isComparisonOp(op) and self.binaryPrec() == prec and self.isComparisonToken()) {
var operands = std.ArrayList(*Node).empty;
var ops = std.ArrayList(ast.BinaryOp.Op).empty;
try operands.append(self.allocator, lhs);
try operands.append(self.allocator, rhs);
try ops.append(self.allocator, op);
while (self.binaryPrec() == prec and self.isComparisonToken()) {
const chain_op = self.binaryOp() orelse break;
self.advance();
const chain_rhs = try self.parseBinary(prec + 1);
try operands.append(self.allocator, chain_rhs);
try ops.append(self.allocator, chain_op);
}
lhs = try self.createNode(lhs.span.start, .{ .chained_comparison = .{
.operands = try operands.toOwnedSlice(self.allocator),
.ops = try ops.toOwnedSlice(self.allocator),
} });
} else {
lhs = try self.createNode(lhs.span.start, .{ .binary_op = .{ .op = op, .lhs = lhs, .rhs = rhs } });
}
}
return lhs;
}
fn parseUnary(self: *Parser) anyerror!*Node {
if (self.current.tag == .minus) {
const start = self.current.loc.start;
self.advance();
const operand = try self.parseUnary();
return try self.createNode(start, .{ .unary_op = .{ .op = .negate, .operand = operand } });
}
if (self.current.tag == .bang) {
const start = self.current.loc.start;
self.advance();
const operand = try self.parseUnary();
return try self.createNode(start, .{ .unary_op = .{ .op = .not, .operand = operand } });
}
if (self.current.tag == .kw_xx) {
const start = self.current.loc.start;
self.advance();
const operand = try self.parseUnary();
return try self.createNode(start, .{ .unary_op = .{ .op = .xx, .operand = operand } });
}
if (self.current.tag == .at) {
const start = self.current.loc.start;
self.advance();
const operand = try self.parseUnary();
return try self.createNode(start, .{ .unary_op = .{ .op = .address_of, .operand = operand } });
}
// cast(Type) expr — prefix operator with type parameter
if (self.current.tag == .identifier and std.mem.eql(u8, self.tokenSlice(self.current), "cast")) {
const saved_lexer = self.lexer;
const next_tok = self.lexer.next();
self.lexer = saved_lexer;
if (next_tok.tag == .l_paren) {
const start = self.current.loc.start;
self.advance(); // consume 'cast'
self.advance(); // consume '('
const type_arg = try self.parseExpr();
try self.expect(.r_paren);
const operand = try self.parseUnary();
const callee = try self.createNode(start, .{ .identifier = .{ .name = "cast" } });
const args = try self.allocator.alloc(*Node, 2);
args[0] = type_arg;
args[1] = operand;
return try self.createNode(start, .{ .call = .{ .callee = callee, .args = args } });
}
}
return self.parsePostfix();
}
fn parsePostfix(self: *Parser) anyerror!*Node {
var expr = try self.parsePrimary();
while (true) {
if (self.current.tag == .l_paren) {
// Call
self.advance();
var args = std.ArrayList(*Node).empty;
while (self.current.tag != .r_paren and self.current.tag != .eof) {
if (args.items.len > 0) {
try self.expect(.comma);
}
// Spread operator: ..expr
if (self.current.tag == .dot_dot) {
const spread_start = self.current.loc.start;
self.advance();
const operand = try self.parseExpr();
try args.append(self.allocator, try self.createNode(spread_start, .{ .spread_expr = .{ .operand = operand } }));
} else {
try args.append(self.allocator, try self.parseExpr());
}
}
try self.expect(.r_paren);
expr = try self.createNode(expr.span.start, .{ .call = .{ .callee = expr, .args = try args.toOwnedSlice(self.allocator) } });
} else if (self.current.tag == .dot) {
self.advance();
if (self.current.tag == .l_brace) {
// Struct literal: Type.{ ... }
if (expr.data == .identifier) {
// Simple name: Vec4.{ ... }
expr = try self.parseStructLiteral(expr.data.identifier.name, null, expr.span.start);
} else if (expr.data == .field_access) {
// Qualified name: std.Vec4.{ ... }
const qname = try self.reconstructQualifiedName(expr);
expr = try self.parseStructLiteral(qname, null, expr.span.start);
} else {
// Expression type: Vec(3, f32).{ ... }
expr = try self.parseStructLiteral(null, expr, expr.span.start);
}
} else if (self.current.tag == .l_bracket) {
// Typed array/vector literal: Type.[elem, ...]
self.advance(); // skip '['
var elements = std.ArrayList(*Node).empty;
while (self.current.tag != .r_bracket and self.current.tag != .eof) {
if (elements.items.len > 0) {
try self.expect(.comma);
}
const elem = try self.parseExpr();
try elements.append(self.allocator, elem);
}
try self.expect(.r_bracket);
expr = try self.createNode(expr.span.start, .{ .array_literal = .{
.elements = try elements.toOwnedSlice(self.allocator),
.type_expr = expr,
} });
} else if (self.current.tag == .star) {
// 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 '.'");
}
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 == .l_bracket) {
// Index or slice access: expr[expr] or expr[start..end]
self.advance();
if (self.current.tag == .dot_dot) {
// [..end]
self.advance();
const end_expr = try self.parseExpr();
try self.expect(.r_bracket);
expr = try self.createNode(expr.span.start, .{ .slice_expr = .{
.object = expr, .start = null, .end = end_expr,
} });
} else {
const first = try self.parseExpr();
if (self.current.tag == .dot_dot) {
// [start..end] or [start..]
self.advance();
const end_expr: ?*ast.Node = if (self.current.tag != .r_bracket)
try self.parseExpr()
else
null;
try self.expect(.r_bracket);
expr = try self.createNode(expr.span.start, .{ .slice_expr = .{
.object = expr, .start = first, .end = end_expr,
} });
} else {
// [index] — normal index access
try self.expect(.r_bracket);
expr = try self.createNode(expr.span.start, .{ .index_expr = .{
.object = expr, .index = first,
} });
}
}
} else {
break;
}
}
return expr;
}
fn parsePrimary(self: *Parser) anyerror!*Node {
const start = self.current.loc.start;
switch (self.current.tag) {
.int_literal => {
const text = self.tokenSlice(self.current);
const base: u8 = if (text.len > 2 and text[0] == '0' and (text[1] == 'x' or text[1] == 'X'))
16
else if (text.len > 2 and text[0] == '0' and (text[1] == 'b' or text[1] == 'B'))
2
else
10;
const digits = if (base != 10) text[2..] else text;
const value = std.fmt.parseInt(i64, digits, base) catch {
return self.fail("integer literal overflow");
};
self.advance();
return try self.createNode(start, .{ .int_literal = .{ .value = value } });
},
.float_literal => {
const text = self.tokenSlice(self.current);
const value = std.fmt.parseFloat(f64, text) catch {
return self.fail("float literal overflow");
};
self.advance();
return try self.createNode(start, .{ .float_literal = .{ .value = value } });
},
.string_literal => {
// raw includes quotes
const raw = self.tokenSlice(self.current);
self.advance();
return try self.createNode(start, .{ .string_literal = .{ .raw = raw[1 .. raw.len - 1] } });
},
.raw_string_literal => {
// #string heredoc — token span is content only, no stripping needed
const raw = self.tokenSlice(self.current);
self.advance();
return try self.createNode(start, .{ .string_literal = .{ .raw = raw, .is_raw = true } });
},
.kw_true => {
self.advance();
return try self.createNode(start, .{ .bool_literal = .{ .value = true } });
},
.kw_false => {
self.advance();
return try self.createNode(start, .{ .bool_literal = .{ .value = false } });
},
.kw_null => {
self.advance();
return try self.createNode(start, .{ .null_literal = {} });
},
.identifier => {
const name = self.tokenSlice(self.current);
// Check if this identifier is a type name (e.g. s32, u8, s128)
if (Type.fromName(name) != null) {
self.advance();
return try self.createNode(start, .{ .type_expr = .{ .name = name } });
}
self.advance();
return try self.createNode(start, .{ .identifier = .{ .name = name } });
},
.dot => {
self.advance();
// Anonymous struct literal: .{ ... }
if (self.current.tag == .l_brace) {
return self.parseStructLiteral(null, null, start);
}
// Array literal: .[expr, expr, ...]
if (self.current.tag == .l_bracket) {
self.advance(); // skip '['
var elements = std.ArrayList(*Node).empty;
while (self.current.tag != .r_bracket and self.current.tag != .eof) {
if (elements.items.len > 0) {
try self.expect(.comma);
}
const elem = try self.parseExpr();
try elements.append(self.allocator, elem);
}
try self.expect(.r_bracket);
return try self.createNode(start, .{ .array_literal = .{ .elements = try elements.toOwnedSlice(self.allocator) } });
}
// Enum literal: .variant_name
if (self.current.tag != .identifier) {
return self.fail("expected variant name, '{', or '[' after '.'");
}
const name = self.tokenSlice(self.current);
self.advance();
// Enum literal with payload: .variant(payload) — tagged enum (formerly union literal)
if (self.current.tag == .l_paren) {
self.advance(); // skip '('
const payload = try self.parseExpr();
try self.expect(.r_paren);
return try self.createNode(start, .{ .enum_literal = .{
.name = name,
.payload = payload,
} });
}
return try self.createNode(start, .{ .enum_literal = .{ .name = name } });
},
.l_paren => {
// Lambda: (params) => expr
if (self.isLambda()) {
return self.parseLambda();
}
// Grouped expression
self.advance();
const expr = try self.parseExpr();
try self.expect(.r_paren);
return expr;
},
.kw_f32, .kw_f64, .kw_Type => {
// Type keyword used as expression (for type aliases: SOME_TYPE :: f64;)
const name = self.tokenSlice(self.current);
self.advance();
return try self.createNode(start, .{ .type_expr = .{ .name = name } });
},
.kw_struct => {
// Anonymous struct expression: struct { value: T; count: u32; }
return try self.parseStructDecl("__anon", start);
},
.kw_enum => {
// Anonymous enum expression: enum { variant: T; other: u32; }
return try self.parseEnumDecl("__anon", start);
},
.kw_union => {
// Anonymous C-style union expression: union { f: f32; i: s32; }
return try self.parseUnionDecl("__anon", start);
},
.kw_if => {
return self.parseIfExpr();
},
.kw_while => {
return self.parseWhileExpr();
},
.kw_for => {
return self.parseForExpr();
},
.kw_break => {
self.advance();
return try self.createNode(start, .{ .break_expr = {} });
},
.kw_continue => {
self.advance();
return try self.createNode(start, .{ .continue_expr = {} });
},
.kw_return => {
self.advance();
// return with optional value
const value = if (self.current.tag != .semicolon and self.current.tag != .eof)
try self.parseExpr()
else
null;
return try self.createNode(start, .{ .return_stmt = .{ .value = value } });
},
.l_bracket => {
// Type expression in expression position: []T.[...] or [N]T.[...]
return try self.parseTypeExpr();
},
.l_brace => {
return self.parseBlock();
},
.triple_minus => {
self.advance();
return try self.createNode(start, .{ .undef_literal = {} });
},
.hash_run => {
self.advance(); // skip '#run'
const inner = try self.parseExpr();
return try self.createNode(start, .{ .comptime_expr = .{ .expr = inner } });
},
else => {
return self.fail("unexpected token in expression");
},
}
}
fn parseIfExpr(self: *Parser) anyerror!*Node {
const start = self.current.loc.start;
self.advance(); // skip 'if'
// Parse condition at prec 5 (arithmetic+), leaving all comparisons
// unconsumed for manual handling with match disambiguation.
var condition = try self.parseBinary(5);
// Handle comparisons with chain detection and match disambiguation.
// All comparisons (< <= > >= == !=) are at the same precedence.
if (self.isComparisonToken()) {
var operands = std.ArrayList(*Node).empty;
var ops = std.ArrayList(ast.BinaryOp.Op).empty;
try operands.append(self.allocator, condition);
while (self.isComparisonToken()) {
// Match disambiguation: == followed by { is a match expression
if (self.current.tag == .equal_equal) {
self.advance();
if (self.current.tag == .l_brace) {
// Match expression: if expr == { case ... }
// Only valid as the first comparison (no chain before it)
if (ops.items.len == 0) {
return self.parseMatchBody(condition, start);
}
// Chain followed by == { is an error — fall through to
// regular comparison (will likely fail at parse time)
}
const rhs = try self.parseBinary(5);
try operands.append(self.allocator, rhs);
try ops.append(self.allocator, .eq);
} else {
const cmp_op = self.binaryOp() orelse break;
self.advance();
const rhs = try self.parseBinary(5);
try operands.append(self.allocator, rhs);
try ops.append(self.allocator, cmp_op);
}
}
if (ops.items.len == 1) {
// Single comparison — regular binary_op
condition = try self.createNode(condition.span.start, .{ .binary_op = .{
.op = ops.items[0],
.lhs = operands.items[0],
.rhs = operands.items[1],
} });
} else {
// Chained comparison
condition = try self.createNode(condition.span.start, .{ .chained_comparison = .{
.operands = try operands.toOwnedSlice(self.allocator),
.ops = try ops.toOwnedSlice(self.allocator),
} });
}
}
// Handle and/or with proper Pratt precedence
condition = try self.parseBinaryRhs(condition, 1);
// Inline form: if cond then expr [else expr]
if (self.current.tag == .kw_then) {
self.advance();
const then_branch = try self.parseExpr();
var else_branch: ?*Node = null;
if (self.current.tag == .kw_else) {
self.advance();
else_branch = try self.parseExpr();
}
return try self.createNode(start, .{ .if_expr = .{
.condition = condition,
.then_branch = then_branch,
.else_branch = else_branch,
.is_inline = true,
} });
}
// Block form: if cond { ... } else { ... }
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 = condition,
.then_branch = then_branch,
.else_branch = else_branch,
.is_inline = false,
} });
}
fn parseWhileExpr(self: *Parser) anyerror!*Node {
const start = self.current.loc.start;
self.advance(); // skip 'while'
const condition = try self.parseExpr();
const body = try self.parseBlock();
return try self.createNode(start, .{ .while_expr = .{
.condition = condition,
.body = body,
} });
}
fn parseForExpr(self: *Parser) anyerror!*Node {
const start = self.current.loc.start;
self.advance(); // skip 'for'
const iterable = try self.parseExpr();
const body = try self.parseBlock();
return try self.createNode(start, .{ .for_expr = .{
.iterable = iterable,
.body = body,
} });
}
fn parseMatchBody(self: *Parser, subject: *Node, start_pos: u32) anyerror!*Node {
try self.expect(.l_brace);
var arms = std.ArrayList(ast.MatchArm).empty;
while (self.current.tag == .kw_case) {
const arm_start = self.current.loc.start;
self.advance(); // skip 'case'
// Allow keyword tokens (struct, enum, union) as type category names in match arms
const pattern: *Node = if (self.current.tag == .kw_struct or self.current.tag == .kw_enum or self.current.tag == .kw_union) blk: {
const name = self.tokenSlice(self.current);
self.advance();
break :blk try self.createNode(arm_start, .{ .identifier = .{ .name = name } });
} else try self.parsePrimary(); // .variant
try self.expect(.colon);
// Optional payload capture: (ident)
var capture: ?[]const u8 = null;
if (self.current.tag == .l_paren) {
self.advance();
if (self.current.tag != .identifier) return self.fail("expected capture name");
capture = self.tokenSlice(self.current);
self.advance();
try self.expect(.r_paren);
}
if (self.current.tag == .kw_break) {
self.advance();
try self.expect(.semicolon);
const body = try self.createNode(arm_start, .{ .block = .{ .stmts = &.{} } });
try arms.append(self.allocator, .{ .pattern = pattern, .body = body, .is_break = true, .capture = capture });
} else if (self.current.tag == .fat_arrow) {
// Short form: (ident) => expr;
self.advance();
const expr = try self.parseExpr();
try self.expect(.semicolon);
const body = try self.createNode(arm_start, .{ .block = .{ .stmts = try self.allocator.dupe(*Node, &.{expr}) } });
try arms.append(self.allocator, .{ .pattern = pattern, .body = body, .is_break = false, .capture = capture });
} else {
const stmts_start = self.current.loc.start;
var stmts = std.ArrayList(*Node).empty;
while (self.current.tag != .kw_case and self.current.tag != .kw_else and self.current.tag != .r_brace and self.current.tag != .eof) {
try stmts.append(self.allocator, try self.parseStmt());
}
const body = try self.createNode(stmts_start, .{ .block = .{ .stmts = try stmts.toOwnedSlice(self.allocator) } });
try arms.append(self.allocator, .{ .pattern = pattern, .body = body, .is_break = false, .capture = capture });
}
}
// Optional else arm (default)
if (self.current.tag == .kw_else) {
const else_start = self.current.loc.start;
self.advance(); // skip 'else'
try self.expect(.colon);
var stmts = std.ArrayList(*Node).empty;
while (self.current.tag != .r_brace and self.current.tag != .eof) {
try stmts.append(self.allocator, try self.parseStmt());
}
const body = try self.createNode(else_start, .{ .block = .{ .stmts = try stmts.toOwnedSlice(self.allocator) } });
try arms.append(self.allocator, .{ .pattern = null, .body = body, .is_break = false });
}
try self.expect(.r_brace);
return try self.createNode(start_pos, .{ .match_expr = .{ .subject = subject, .arms = try arms.toOwnedSlice(self.allocator) } });
}
fn isLambda(self: *Parser) bool {
// Peek ahead: save state, scan to matching ), check if => or -> ... => follows
const saved_lexer = self.lexer;
const saved_current = self.current;
const saved_prev_end = self.prev_end;
defer {
self.lexer = saved_lexer;
self.current = saved_current;
self.prev_end = saved_prev_end;
}
self.advance(); // skip '('
var depth: u32 = 1;
while (depth > 0 and self.current.tag != .eof) {
if (self.current.tag == .l_paren) depth += 1;
if (self.current.tag == .r_paren) depth -= 1;
if (depth > 0) self.advance();
}
if (self.current.tag == .r_paren) {
self.advance(); // skip ')'
if (self.current.tag == .fat_arrow) return true;
// (params) -> ReturnType => expr
if (self.current.tag == .arrow) {
self.advance(); // skip '->'
// Skip past the return type tokens until we see '=>' or something unexpected
while (self.current.tag != .eof) {
if (self.current.tag == .fat_arrow) return true;
// Return type tokens: identifiers, dots, parens, type keywords, dollar, brackets
if (self.current.tag == .identifier or self.current.tag.isTypeKeyword() or
self.current.tag == .dot or self.current.tag == .dollar or
self.current.tag == .l_bracket or self.current.tag == .r_bracket or
self.current.tag == .l_paren or self.current.tag == .r_paren or
self.current.tag == .comma or self.current.tag == .int_literal)
{
self.advance();
} else break;
}
}
}
return false;
}
fn parseLambda(self: *Parser) anyerror!*Node {
const start = self.current.loc.start;
const params = try self.parseParams();
// Optional return type: (params) -> Type => expr
var return_type: ?*Node = null;
if (self.current.tag == .arrow) {
self.advance();
return_type = try self.parseTypeExpr();
}
try self.expect(.fat_arrow);
const body = try self.parseExpr();
const type_params = try self.collectTypeParams(params);
return try self.createNode(start, .{ .lambda = .{
.params = params,
.return_type = return_type,
.body = body,
.type_params = type_params,
} });
}
// ---- Helpers ----
fn isFunctionDef(self: *Parser) bool {
// Peek ahead: save state, scan to matching ), check what follows
const saved_lexer = self.lexer;
const saved_current = self.current;
const saved_prev_end = self.prev_end;
defer {
self.lexer = saved_lexer;
self.current = saved_current;
self.prev_end = saved_prev_end;
}
self.advance(); // skip '('
var depth: u32 = 1;
while (depth > 0 and self.current.tag != .eof) {
if (self.current.tag == .l_paren) depth += 1;
if (self.current.tag == .r_paren) depth -= 1;
if (depth > 0) self.advance();
}
if (self.current.tag == .r_paren) {
self.advance(); // skip ')'
// Function if followed by '{', '->', '#builtin', '#foreign', or '=>'
return self.current.tag == .l_brace or self.current.tag == .arrow or self.current.tag == .hash_builtin or self.current.tag == .hash_foreign or self.current.tag == .fat_arrow;
}
return false;
}
fn isAssignOp(self: *const Parser) bool {
return switch (self.current.tag) {
.equal, .plus_equal, .minus_equal, .star_equal, .slash_equal, .percent_equal => true,
else => false,
};
}
fn assignOp(self: *const Parser) ast.Assignment.Op {
return switch (self.current.tag) {
.equal => .assign,
.plus_equal => .add_assign,
.minus_equal => .sub_assign,
.star_equal => .mul_assign,
.slash_equal => .div_assign,
.percent_equal => .mod_assign,
else => unreachable,
};
}
fn binaryPrec(self: *const Parser) u8 {
return switch (self.current.tag) {
.kw_or => 1,
.kw_and => 2,
.pipe => 3,
.ampersand => 3,
.equal_equal, .bang_equal, .less, .less_equal, .greater, .greater_equal => 4,
.plus, .minus => 5,
.star, .slash, .percent => 6,
else => 0,
};
}
fn binaryOp(self: *const Parser) ?ast.BinaryOp.Op {
return switch (self.current.tag) {
.kw_and => .and_op,
.kw_or => .or_op,
.pipe => .bit_or,
.ampersand => .bit_and,
.plus => .add,
.minus => .sub,
.star => .mul,
.slash => .div,
.percent => .mod,
.equal_equal => .eq,
.bang_equal => .neq,
.less => .lt,
.less_equal => .lte,
.greater => .gt,
.greater_equal => .gte,
else => null,
};
}
fn isComparisonOp(op: ast.BinaryOp.Op) bool {
return switch (op) {
.lt, .lte, .gt, .gte, .eq, .neq => true,
else => false,
};
}
fn isComparisonToken(self: *const Parser) bool {
return switch (self.current.tag) {
.less, .less_equal, .greater, .greater_equal, .equal_equal, .bang_equal => true,
else => false,
};
}
fn advance(self: *Parser) void {
self.prev_end = self.current.loc.end;
self.current = self.lexer.next();
}
fn expect(self: *Parser, tag: Tag) !void {
if (self.current.tag != tag) {
const expected = tag.lexeme() orelse @tagName(tag);
return self.failFmt("expected '{s}'", .{expected});
}
self.advance();
}
fn failFmt(self: *Parser, comptime fmt: []const u8, args: anytype) error{ParseError} {
const msg = std.fmt.allocPrint(self.allocator, fmt, args) catch return error.ParseError;
return self.fail(msg);
}
fn tokenSlice(self: *const Parser, token: Token) []const u8 {
return self.source[token.loc.start..token.loc.end];
}
fn fail(self: *Parser, msg: []const u8) error{ParseError} {
self.err_msg = msg;
self.err_offset = self.current.loc.start;
if (self.diagnostics) |diags| {
diags.add(.err, msg, .{ .start = self.current.loc.start, .end = self.current.loc.end });
}
return error.ParseError;
}
};
test "parse minimal main" {
const source = "main :: () { 42; }";
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
var parser = Parser.init(arena.allocator(), source);
const root = try parser.parse();
try std.testing.expect(root.data == .root);
try std.testing.expectEqual(@as(usize, 1), root.data.root.decls.len);
const decl = root.data.root.decls[0];
try std.testing.expect(decl.data == .fn_decl);
try std.testing.expectEqualStrings("main", decl.data.fn_decl.name);
const body = decl.data.fn_decl.body;
try std.testing.expect(body.data == .block);
try std.testing.expectEqual(@as(usize, 1), body.data.block.stmts.len);
try std.testing.expect(body.data.block.stmts[0].data == .int_literal);
try std.testing.expectEqual(@as(i64, 42), body.data.block.stmts[0].data.int_literal.value);
}
test "parse #run const binding" {
const source = "x :: #run compute(5);";
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
var parser = Parser.init(arena.allocator(), source);
const root = try parser.parse();
try std.testing.expectEqual(@as(usize, 1), root.data.root.decls.len);
const decl = root.data.root.decls[0];
try std.testing.expect(decl.data == .const_decl);
try std.testing.expectEqualStrings("x", decl.data.const_decl.name);
try std.testing.expect(decl.data.const_decl.value.data == .comptime_expr);
// inner expr is a call
try std.testing.expect(decl.data.const_decl.value.data.comptime_expr.expr.data == .call);
}
test "parse top-level #run" {
const source = "#run main();";
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
var parser = Parser.init(arena.allocator(), source);
const root = try parser.parse();
try std.testing.expectEqual(@as(usize, 1), root.data.root.decls.len);
const decl = root.data.root.decls[0];
try std.testing.expect(decl.data == .comptime_expr);
// inner expr is a call
try std.testing.expect(decl.data.comptime_expr.expr.data == .call);
}
test "parse flat import" {
const source = "#import \"modules/std/math.sx\";";
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
var parser = Parser.init(arena.allocator(), source);
const root = try parser.parse();
try std.testing.expectEqual(@as(usize, 1), root.data.root.decls.len);
const decl = root.data.root.decls[0];
try std.testing.expect(decl.data == .import_decl);
try std.testing.expectEqualStrings("modules/std/math.sx", decl.data.import_decl.path);
try std.testing.expect(decl.data.import_decl.name == null);
}
test "parse namespaced import" {
const source = "std :: #import \"modules/std/std.sx\";";
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
var parser = Parser.init(arena.allocator(), source);
const root = try parser.parse();
try std.testing.expectEqual(@as(usize, 1), root.data.root.decls.len);
const decl = root.data.root.decls[0];
try std.testing.expect(decl.data == .import_decl);
try std.testing.expectEqualStrings("modules/std/std.sx", decl.data.import_decl.path);
try std.testing.expectEqualStrings("std", decl.data.import_decl.name.?);
}
test "parse library declaration" {
const source = "#library \"raylib\";";
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
var parser = Parser.init(arena.allocator(), source);
const root = try parser.parse();
try std.testing.expectEqual(@as(usize, 1), root.data.root.decls.len);
const decl = root.data.root.decls[0];
try std.testing.expect(decl.data == .library_decl);
try std.testing.expectEqualStrings("raylib", decl.data.library_decl.lib_name);
}
test "parse void function with builtin body" {
const source = "foo :: () #builtin;";
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
var parser = Parser.init(arena.allocator(), source);
const root = try parser.parse();
try std.testing.expectEqual(@as(usize, 1), root.data.root.decls.len);
const decl = root.data.root.decls[0];
try std.testing.expect(decl.data == .fn_decl);
try std.testing.expectEqualStrings("foo", decl.data.fn_decl.name);
try std.testing.expect(decl.data.fn_decl.body.data == .builtin_expr);
}
test "parse void function with foreign body" {
const source = "InitWindow :: (width: s32, height: s32, title: *u8) -> void #foreign;";
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
var parser = Parser.init(arena.allocator(), source);
const root = try parser.parse();
try std.testing.expectEqual(@as(usize, 1), root.data.root.decls.len);
const decl = root.data.root.decls[0];
try std.testing.expect(decl.data == .fn_decl);
try std.testing.expectEqualStrings("InitWindow", decl.data.fn_decl.name);
try std.testing.expect(decl.data.fn_decl.body.data == .foreign_expr);
try std.testing.expectEqual(@as(usize, 3), decl.data.fn_decl.params.len);
}
test "parse void function with arrow body" {
const source = "foo :: () => 42;";
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
var parser = Parser.init(arena.allocator(), source);
const root = try parser.parse();
try std.testing.expectEqual(@as(usize, 1), root.data.root.decls.len);
const decl = root.data.root.decls[0];
try std.testing.expect(decl.data == .fn_decl);
try std.testing.expectEqualStrings("foo", decl.data.fn_decl.name);
try std.testing.expect(decl.data.fn_decl.body.data == .int_literal);
try std.testing.expectEqual(@as(i64, 42), decl.data.fn_decl.body.data.int_literal.value);
}
test "parse hex and binary literals" {
const source = "main :: () { 0xFF; 0b1010; }";
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
var parser = Parser.init(arena.allocator(), source);
const root = try parser.parse();
const body = root.data.root.decls[0].data.fn_decl.body;
try std.testing.expectEqual(@as(usize, 2), body.data.block.stmts.len);
try std.testing.expectEqual(@as(i64, 255), body.data.block.stmts[0].data.int_literal.value);
try std.testing.expectEqual(@as(i64, 10), body.data.block.stmts[1].data.int_literal.value);
}
test "parse array type with identifier length" {
const source = "foo :: (arr: [N]f32) => arr;";
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
var parser = Parser.init(arena.allocator(), source);
const root = try parser.parse();
const decl = root.data.root.decls[0];
try std.testing.expect(decl.data == .fn_decl);
const param_type = decl.data.fn_decl.params[0].type_expr;
try std.testing.expect(param_type.data == .array_type_expr);
// length is an identifier "N", not an int literal
try std.testing.expect(param_type.data.array_type_expr.length.data == .identifier);
try std.testing.expectEqualStrings("N", param_type.data.array_type_expr.length.data.identifier.name);
try std.testing.expect(param_type.data.array_type_expr.element_type.data == .type_expr);
}
test "parse lambda with generic params" {
const source = "f :: (x: $T) => x;";
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
var parser = Parser.init(arena.allocator(), source);
const root = try parser.parse();
const decl = root.data.root.decls[0];
try std.testing.expect(decl.data == .const_decl);
const lambda = decl.data.const_decl.value;
try std.testing.expect(lambda.data == .lambda);
try std.testing.expectEqual(@as(usize, 1), lambda.data.lambda.params.len);
try std.testing.expectEqualStrings("x", lambda.data.lambda.params[0].name);
// has generic type param
try std.testing.expectEqual(@as(usize, 1), lambda.data.lambda.type_params.len);
try std.testing.expectEqualStrings("T", lambda.data.lambda.type_params[0].name);
}
test "parse lambda with return type" {
const source = "f :: (x: s32) -> s32 => x;";
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
var parser = Parser.init(arena.allocator(), source);
const root = try parser.parse();
const decl = root.data.root.decls[0];
try std.testing.expect(decl.data == .const_decl);
const lambda = decl.data.const_decl.value;
try std.testing.expect(lambda.data == .lambda);
try std.testing.expect(lambda.data.lambda.return_type != null);
try std.testing.expect(lambda.data.lambda.return_type.?.data == .type_expr);
try std.testing.expectEqualStrings("s32", lambda.data.lambda.return_type.?.data.type_expr.name);
}
test "parse match with else arm" {
const source =
\\main :: () {
\\ x := 5;
\\ if x == {
\\ case 1: 10;
\\ else: 99;
\\ };
\\}
;
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
var parser = Parser.init(arena.allocator(), source);
const root = try parser.parse();
const body = root.data.root.decls[0].data.fn_decl.body;
// second stmt is the match expr (after var decl)
const match_node = body.data.block.stmts[1];
try std.testing.expect(match_node.data == .match_expr);
const arms = match_node.data.match_expr.arms;
try std.testing.expectEqual(@as(usize, 2), arms.len);
// first arm has a pattern
try std.testing.expect(arms[0].pattern != null);
// second arm is the else arm (null pattern)
try std.testing.expect(arms[1].pattern == null);
}
test "integer literal overflow error" {
const source = "main :: () { 99999999999999999999; }";
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
var parser = Parser.init(arena.allocator(), source);
const result = parser.parse();
try std.testing.expectError(error.ParseError, result);
try std.testing.expectEqualStrings("integer literal overflow", parser.err_msg.?);
}