vtables, protocol
This commit is contained in:
270
src/parser.zig
270
src/parser.zig
@@ -16,6 +16,8 @@ pub const Parser = struct {
|
||||
err_offset: ?u32 = null,
|
||||
prev_end: u32 = 0,
|
||||
diagnostics: ?*errors.DiagnosticList = null,
|
||||
/// Type param names from enclosing generic struct (set while parsing methods)
|
||||
struct_type_params: []const []const u8 = &.{},
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, source: [:0]const u8) Parser {
|
||||
var lexer = Lexer.init(source);
|
||||
@@ -75,8 +77,13 @@ pub const Parser = struct {
|
||||
return try self.createNode(start, .{ .comptime_expr = .{ .expr = expr } });
|
||||
}
|
||||
|
||||
// impl Protocol for Type { methods }
|
||||
if (self.current.tag == .kw_impl) {
|
||||
return self.parseImplBlock(start);
|
||||
}
|
||||
|
||||
// All top-level declarations start with an identifier
|
||||
if (self.current.tag != .identifier) {
|
||||
if (self.current.tag != .identifier and self.current.tag != .kw_Self) {
|
||||
return self.fail("expected identifier at top level");
|
||||
}
|
||||
const name = self.tokenSlice(self.current);
|
||||
@@ -170,6 +177,11 @@ pub const Parser = struct {
|
||||
return self.parseStructDecl(name, start_pos);
|
||||
}
|
||||
|
||||
// Protocol declaration
|
||||
if (self.current.tag == .kw_protocol) {
|
||||
return self.parseProtocolDecl(name, start_pos);
|
||||
}
|
||||
|
||||
// C-style union declaration
|
||||
if (self.current.tag == .kw_union) {
|
||||
return self.parseUnionDecl(name, start_pos);
|
||||
@@ -378,7 +390,7 @@ pub const Parser = struct {
|
||||
return try self.createNode(start, .{ .array_type_expr = .{ .length = len_node, .element_type = elem_type } });
|
||||
}
|
||||
|
||||
// Generic type parameter introduction: $T
|
||||
// Generic type parameter introduction: $T or $T/Protocol1/Protocol2
|
||||
if (self.current.tag == .dollar) {
|
||||
self.advance();
|
||||
if (self.current.tag != .identifier) {
|
||||
@@ -386,7 +398,18 @@ pub const Parser = struct {
|
||||
}
|
||||
const name = self.tokenSlice(self.current);
|
||||
self.advance();
|
||||
return try self.createNode(start, .{ .type_expr = .{ .name = name, .is_generic = true } });
|
||||
// Parse optional protocol constraints: $T/Eq/Hashable
|
||||
var constraints = std.ArrayList([]const u8).empty;
|
||||
while (self.current.tag == .slash) {
|
||||
self.advance(); // skip '/'
|
||||
if (self.current.tag != .identifier) {
|
||||
return self.fail("expected protocol name after '/'");
|
||||
}
|
||||
try constraints.append(self.allocator, self.tokenSlice(self.current));
|
||||
self.advance();
|
||||
}
|
||||
const pc = try constraints.toOwnedSlice(self.allocator);
|
||||
return try self.createNode(start, .{ .type_expr = .{ .name = name, .is_generic = true, .protocol_constraints = pc } });
|
||||
}
|
||||
// Function type: (ParamTypes) -> ReturnType
|
||||
// Tuple type: (T1, T2) or (T1) — no '->' after ')'
|
||||
@@ -525,7 +548,12 @@ pub const Parser = struct {
|
||||
} });
|
||||
}
|
||||
|
||||
return try self.createNode(start, .{ .type_expr = .{ .name = name } });
|
||||
// Mark as generic if name matches an enclosing struct's type param
|
||||
var is_struct_generic = false;
|
||||
for (self.struct_type_params) |tp| {
|
||||
if (std.mem.eql(u8, tp, name)) { is_struct_generic = true; break; }
|
||||
}
|
||||
return try self.createNode(start, .{ .type_expr = .{ .name = name, .is_generic = is_struct_generic } });
|
||||
}
|
||||
// Inline struct type in type position: struct { ... }
|
||||
if (self.current.tag == .kw_struct) {
|
||||
@@ -682,17 +710,38 @@ pub const Parser = struct {
|
||||
self.advance();
|
||||
try self.expect(.colon);
|
||||
const constraint = try self.parseTypeExpr();
|
||||
try type_params.append(self.allocator, .{ .name = param_name, .constraint = constraint });
|
||||
// Parse optional protocol constraints: $T: Type/Eq/Hashable
|
||||
var pc_list = std.ArrayList([]const u8).empty;
|
||||
if (constraint.data == .type_expr and std.mem.eql(u8, constraint.data.type_expr.name, "Type")) {
|
||||
while (self.current.tag == .slash) {
|
||||
self.advance(); // skip '/'
|
||||
if (self.current.tag != .identifier) {
|
||||
return self.fail("expected protocol name after '/'");
|
||||
}
|
||||
try pc_list.append(self.allocator, self.tokenSlice(self.current));
|
||||
self.advance();
|
||||
}
|
||||
}
|
||||
const pc = try pc_list.toOwnedSlice(self.allocator);
|
||||
try type_params.append(self.allocator, .{ .name = param_name, .constraint = constraint, .protocol_constraints = pc });
|
||||
}
|
||||
try self.expect(.r_paren);
|
||||
}
|
||||
|
||||
try self.expect(.l_brace);
|
||||
|
||||
// Set struct type params context so method params can reference T without $
|
||||
var tp_names = std.ArrayList([]const u8).empty;
|
||||
for (type_params.items) |tp| try tp_names.append(self.allocator, tp.name);
|
||||
const saved_struct_type_params = self.struct_type_params;
|
||||
self.struct_type_params = tp_names.items;
|
||||
defer self.struct_type_params = saved_struct_type_params;
|
||||
|
||||
var field_names = std.ArrayList([]const u8).empty;
|
||||
var field_types = std.ArrayList(*Node).empty;
|
||||
var field_defaults = std.ArrayList(?*Node).empty;
|
||||
var using_entries = std.ArrayList(ast.UsingEntry).empty;
|
||||
var methods = std.ArrayList(*Node).empty;
|
||||
|
||||
while (self.current.tag != .r_brace and self.current.tag != .eof) {
|
||||
// Check for #using directive
|
||||
@@ -711,6 +760,20 @@ pub const Parser = struct {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Method declaration: name :: (params) -> type { body }
|
||||
if (self.current.tag == .identifier and self.peekNext() == .colon_colon) {
|
||||
const method_start = self.current.loc.start;
|
||||
const method_name = self.tokenSlice(self.current);
|
||||
self.advance(); // skip name
|
||||
self.advance(); // skip ::
|
||||
if (self.current.tag == .l_paren and self.isFunctionDef()) {
|
||||
try methods.append(self.allocator, try self.parseFnDecl(method_name, method_start));
|
||||
} else {
|
||||
return self.fail("only function declarations are allowed inside struct bodies");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse field group: name1, name2, ...: type (= default)?;
|
||||
var group_names = std.ArrayList([]const u8).empty;
|
||||
|
||||
@@ -764,6 +827,166 @@ pub const Parser = struct {
|
||||
.field_defaults = try field_defaults.toOwnedSlice(self.allocator),
|
||||
.type_params = try type_params.toOwnedSlice(self.allocator),
|
||||
.using_entries = try using_entries.toOwnedSlice(self.allocator),
|
||||
.methods = try methods.toOwnedSlice(self.allocator),
|
||||
} });
|
||||
}
|
||||
|
||||
fn parseProtocolDecl(self: *Parser, name: []const u8, start_pos: u32) anyerror!*Node {
|
||||
self.advance(); // skip 'protocol'
|
||||
|
||||
// Check for #inline
|
||||
var is_inline = false;
|
||||
if (self.current.tag == .hash_inline) {
|
||||
is_inline = true;
|
||||
self.advance();
|
||||
}
|
||||
|
||||
try self.expect(.l_brace);
|
||||
|
||||
var methods = std.ArrayList(ast.ProtocolMethodDecl).empty;
|
||||
|
||||
while (self.current.tag != .r_brace and self.current.tag != .eof) {
|
||||
// Method: name :: (params) -> type; or name :: (params) -> type { body }
|
||||
if (self.current.tag != .identifier) {
|
||||
return self.fail("expected method name in protocol body");
|
||||
}
|
||||
const method_name = self.tokenSlice(self.current);
|
||||
self.advance();
|
||||
try self.expect(.colon_colon);
|
||||
try self.expect(.l_paren);
|
||||
|
||||
var param_types = std.ArrayList(*Node).empty;
|
||||
var param_names = std.ArrayList([]const u8).empty;
|
||||
|
||||
while (self.current.tag != .r_paren and self.current.tag != .eof) {
|
||||
if (param_types.items.len > 0) {
|
||||
try self.expect(.comma);
|
||||
}
|
||||
// Parse: name: type
|
||||
if (self.current.tag != .identifier and self.current.tag != .kw_Self) {
|
||||
return self.fail("expected parameter name in protocol method");
|
||||
}
|
||||
const pname = self.tokenSlice(self.current);
|
||||
self.advance();
|
||||
try self.expect(.colon);
|
||||
const ptype = try self.parseTypeExpr();
|
||||
try param_names.append(self.allocator, pname);
|
||||
try param_types.append(self.allocator, ptype);
|
||||
}
|
||||
try self.expect(.r_paren);
|
||||
|
||||
// Optional return type
|
||||
var return_type: ?*Node = null;
|
||||
if (self.current.tag == .arrow) {
|
||||
self.advance();
|
||||
return_type = try self.parseTypeExpr();
|
||||
}
|
||||
|
||||
// Optional body (default method) or semicolon
|
||||
var default_body: ?*Node = null;
|
||||
if (self.current.tag == .l_brace) {
|
||||
default_body = try self.parseBlock();
|
||||
} else {
|
||||
if (self.current.tag == .semicolon) self.advance();
|
||||
}
|
||||
|
||||
try methods.append(self.allocator, .{
|
||||
.name = method_name,
|
||||
.params = try param_types.toOwnedSlice(self.allocator),
|
||||
.param_names = try param_names.toOwnedSlice(self.allocator),
|
||||
.return_type = return_type,
|
||||
.default_body = default_body,
|
||||
});
|
||||
}
|
||||
|
||||
try self.expect(.r_brace);
|
||||
|
||||
return try self.createNode(start_pos, .{ .protocol_decl = .{
|
||||
.name = name,
|
||||
.methods = try methods.toOwnedSlice(self.allocator),
|
||||
.is_inline = is_inline,
|
||||
} });
|
||||
}
|
||||
|
||||
fn parseImplBlock(self: *Parser, start_pos: u32) anyerror!*Node {
|
||||
self.advance(); // skip 'impl'
|
||||
|
||||
// Protocol name
|
||||
if (self.current.tag != .identifier) {
|
||||
return self.fail("expected protocol name after 'impl'");
|
||||
}
|
||||
const protocol_name = self.tokenSlice(self.current);
|
||||
self.advance();
|
||||
|
||||
// 'for' — note: 'for' is a keyword (kw_for), not an identifier
|
||||
if (self.current.tag != .kw_for) {
|
||||
return self.fail("expected 'for' after protocol name in impl block");
|
||||
}
|
||||
self.advance();
|
||||
|
||||
// Target type name
|
||||
if (self.current.tag != .identifier) {
|
||||
return self.fail("expected type name after 'for'");
|
||||
}
|
||||
const target_type = self.tokenSlice(self.current);
|
||||
self.advance();
|
||||
|
||||
// Optional type params: impl Protocol for List($T)
|
||||
var target_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 (target_type_params.items.len > 0) {
|
||||
try self.expect(.comma);
|
||||
}
|
||||
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();
|
||||
// Optional constraint — for now just use Type
|
||||
const constraint = try self.createNode(self.current.loc.start, .{ .type_expr = .{ .name = "Type" } });
|
||||
try target_type_params.append(self.allocator, .{ .name = param_name, .constraint = constraint });
|
||||
}
|
||||
try self.expect(.r_paren);
|
||||
}
|
||||
|
||||
try self.expect(.l_brace);
|
||||
|
||||
// Set struct type params context so method params can reference T without $
|
||||
var tp_names = std.ArrayList([]const u8).empty;
|
||||
for (target_type_params.items) |tp| try tp_names.append(self.allocator, tp.name);
|
||||
const saved_struct_type_params = self.struct_type_params;
|
||||
self.struct_type_params = tp_names.items;
|
||||
defer self.struct_type_params = saved_struct_type_params;
|
||||
|
||||
var methods = std.ArrayList(*Node).empty;
|
||||
|
||||
while (self.current.tag != .r_brace and self.current.tag != .eof) {
|
||||
// Method: name :: (params) -> type { body }
|
||||
if (self.current.tag != .identifier) {
|
||||
return self.fail("expected method name in impl block");
|
||||
}
|
||||
const method_start = self.current.loc.start;
|
||||
const method_name = self.tokenSlice(self.current);
|
||||
self.advance();
|
||||
try self.expect(.colon_colon);
|
||||
|
||||
if (self.current.tag == .l_paren and self.isFunctionDef()) {
|
||||
try methods.append(self.allocator, try self.parseFnDecl(method_name, method_start));
|
||||
} else {
|
||||
return self.fail("expected function declaration in impl block");
|
||||
}
|
||||
}
|
||||
|
||||
try self.expect(.r_brace);
|
||||
|
||||
return try self.createNode(start_pos, .{ .impl_block = .{
|
||||
.protocol_name = protocol_name,
|
||||
.target_type = target_type,
|
||||
.target_type_params = try target_type_params.toOwnedSlice(self.allocator),
|
||||
.methods = try methods.toOwnedSlice(self.allocator),
|
||||
} });
|
||||
}
|
||||
|
||||
@@ -848,7 +1071,13 @@ pub const Parser = struct {
|
||||
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);
|
||||
// Optional type annotation: if no ':', infer type from context
|
||||
if (self.current.tag != .colon) {
|
||||
const inferred_node = try self.createNode(param_name_span.start, .{ .inferred_type = {} });
|
||||
try params.append(self.allocator, .{ .name = param_name, .name_span = param_name_span, .type_expr = inferred_node });
|
||||
continue;
|
||||
}
|
||||
self.advance(); // consume ':'
|
||||
const is_variadic = self.current.tag == .dot_dot;
|
||||
if (is_variadic) self.advance();
|
||||
const param_type = try self.parseTypeExpr();
|
||||
@@ -856,7 +1085,18 @@ pub const Parser = struct {
|
||||
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 } };
|
||||
// Parse optional protocol constraints: $T: Type/Eq/Hashable
|
||||
var constraints = std.ArrayList([]const u8).empty;
|
||||
while (self.current.tag == .slash) {
|
||||
self.advance(); // skip '/'
|
||||
if (self.current.tag != .identifier) {
|
||||
return self.fail("expected protocol name after '/'");
|
||||
}
|
||||
try constraints.append(self.allocator, self.tokenSlice(self.current));
|
||||
self.advance();
|
||||
}
|
||||
const pc = try constraints.toOwnedSlice(self.allocator);
|
||||
param_type.data = .{ .type_expr = .{ .name = param_name, .is_generic = true, .protocol_constraints = pc } };
|
||||
} else {
|
||||
is_comptime_param = true;
|
||||
}
|
||||
@@ -906,8 +1146,13 @@ pub const Parser = struct {
|
||||
for (generic_names.items) |gen_name| {
|
||||
if (!seen.contains(gen_name)) {
|
||||
try seen.put(gen_name, {});
|
||||
// Propagate protocol constraints from the TypeExpr if present
|
||||
const pc = if (param.type_expr.data == .type_expr)
|
||||
param.type_expr.data.type_expr.protocol_constraints
|
||||
else
|
||||
&[_][]const u8{};
|
||||
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 {};
|
||||
type_params.append(self.allocator, .{ .name = gen_name, .constraint = type_constraint, .protocol_constraints = pc }) catch {};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1476,6 +1721,11 @@ pub const Parser = struct {
|
||||
self.advance();
|
||||
return try self.createNode(start, .{ .identifier = .{ .name = name } });
|
||||
},
|
||||
.kw_closure => {
|
||||
// `closure` keyword used as identifier in expressions (closure intrinsic call)
|
||||
self.advance();
|
||||
return try self.createNode(start, .{ .identifier = .{ .name = "closure" } });
|
||||
},
|
||||
.dot => {
|
||||
self.advance();
|
||||
// Anonymous struct literal: .{ ... }
|
||||
@@ -1946,7 +2196,9 @@ pub const Parser = struct {
|
||||
if (self.current.tag == .r_paren) break :blk true; // empty parens
|
||||
if (self.current.tag != .identifier) break :blk false;
|
||||
self.advance();
|
||||
break :blk self.current.tag == .colon;
|
||||
break :blk self.current.tag == .colon or
|
||||
self.current.tag == .comma or
|
||||
self.current.tag == .r_paren;
|
||||
};
|
||||
|
||||
// Restore to '(' and scan past parens inline (not via peekPastParens which restores state)
|
||||
|
||||
Reference in New Issue
Block a user