This commit is contained in:
agra
2026-02-11 20:41:43 +02:00
parent 94b0296fd5
commit 9d96f05d3b
13 changed files with 460 additions and 70 deletions

View File

@@ -62,6 +62,19 @@ pub const Parser = struct {
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();
@@ -179,6 +192,15 @@ pub const Parser = struct {
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 } });
}
@@ -223,9 +245,24 @@ pub const Parser = struct {
return try self.createNode(start, .{ .pointer_type_expr = .{ .pointee_type = pointee_type } });
}
// Array type: [N]T, Slice type: []T, Many-pointer type: [*]T
// 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 ']'
@@ -621,12 +658,17 @@ pub const Parser = struct {
return_type = try self.parseTypeExpr();
}
// Body: block `{ ... }`, arrow `=> expr;`, or #builtin marker
// 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();
@@ -1409,8 +1451,8 @@ pub const Parser = struct {
}
if (self.current.tag == .r_paren) {
self.advance(); // skip ')'
// Function if followed by '{', '->', '#builtin', or '=>'
return self.current.tag == .l_brace or self.current.tag == .arrow or self.current.tag == .hash_builtin or self.current.tag == .fat_arrow;
// 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;
}
@@ -1582,6 +1624,18 @@ test "parse namespaced import" {
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);
@@ -1595,6 +1649,20 @@ test "parse void function with builtin body" {
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);