This commit is contained in:
agra
2026-02-15 17:26:15 +02:00
parent 2727b5509e
commit a3be9cce7c
4 changed files with 356 additions and 260 deletions

View File

@@ -117,6 +117,8 @@ pub const CodeGen = struct {
tagged_enum_types: std.StringHashMap(TaggedEnumInfo),
// Union registry: maps name to field info + LLVM type (untagged, C-style)
union_types: std.StringHashMap(UnionInfo),
// Unified type registry: single lookup for all named types
type_registry: std.StringHashMap(TypeRegistryEntry),
// Flags enum registry: tracks which enum names are flags
flags_enum_types: std.StringHashMap(void),
// Enum variant values: maps enum name → resolved i64 values per variant
@@ -271,6 +273,14 @@ pub const CodeGen = struct {
promoted_fields: std.StringHashMap(PromotedField),
};
const TypeRegistryEntry = union(enum) {
struct_info: StructInfo,
tagged_enum: TaggedEnumInfo,
union_info: UnionInfo,
plain_enum: []const []const u8,
alias: []const u8,
};
// Scope stack entry: records what a name mapped to before being shadowed
const ScopeEntry = struct {
name: []const u8,
@@ -326,6 +336,7 @@ pub const CodeGen = struct {
.struct_types = std.StringHashMap(StructInfo).init(allocator),
.tagged_enum_types = std.StringHashMap(TaggedEnumInfo).init(allocator),
.union_types = std.StringHashMap(UnionInfo).init(allocator),
.type_registry = std.StringHashMap(TypeRegistryEntry).init(allocator),
.flags_enum_types = std.StringHashMap(void).init(allocator),
.enum_variant_values = std.StringHashMap([]const i64).init(allocator),
.enum_backing_types = std.StringHashMap(c.LLVMTypeRef).init(allocator),
@@ -361,6 +372,7 @@ pub const CodeGen = struct {
self.struct_types.deinit();
self.tagged_enum_types.deinit();
self.union_types.deinit();
self.type_registry.deinit();
self.comptime_globals.deinit();
self.enum_backing_types.deinit();
self.generic_templates.deinit();
@@ -422,9 +434,21 @@ pub const CodeGen = struct {
return c.LLVMBuildAlloca(tmp_builder, ty, name);
}
/// Convert a Zig slice to a null-terminated C string using a caller-provided stack buffer.
/// Returns the stack-based result when it fits, or falls back to allocator.dupeZ.
fn nameToCStr(self: *CodeGen, name: []const u8, buf: *[256]u8) [*:0]const u8 {
if (name.len < 256) {
@memcpy(buf[0..name.len], name);
buf[name.len] = 0;
return @ptrCast(buf[0..name.len :0]);
}
const duped = self.allocator.dupeZ(u8, name) catch unreachable;
return duped.ptr;
}
fn buildNamedAlloca(self: *CodeGen, ty: c.LLVMTypeRef, name: []const u8) !c.LLVMValueRef {
const name_z = try self.allocator.dupeZ(u8, name);
return self.buildEntryBlockAlloca(ty, name_z.ptr);
var buf: [256]u8 = undefined;
return self.buildEntryBlockAlloca(ty, self.nameToCStr(name, &buf));
}
pub fn typeToLLVM(self: *CodeGen, ty: Type) c.LLVMTypeRef {
@@ -860,8 +884,12 @@ pub const CodeGen = struct {
}
fn pushScope(self: *CodeGen) !void {
try self.scope_saves.append(self.allocator, std.ArrayList(ScopeEntry).empty);
try self.defer_stack.append(self.allocator, std.ArrayList(*Node).empty);
var saves = std.ArrayList(ScopeEntry).empty;
try saves.ensureTotalCapacity(self.allocator, 8);
try self.scope_saves.append(self.allocator, saves);
var defers = std.ArrayList(*Node).empty;
try defers.ensureTotalCapacity(self.allocator, 4);
try self.defer_stack.append(self.allocator, defers);
}
fn popScope(self: *CodeGen) !void {
@@ -955,6 +983,7 @@ pub const CodeGen = struct {
} else {
// Payload-less enum
try self.enum_types.put(ed.name, ed.variant_names);
try self.type_registry.put(ed.name, .{ .plain_enum = ed.variant_names });
_ = try self.getAnyTypeId(ed.name, .{ .enum_type = ed.name });
if (ed.is_flags) {
@@ -997,6 +1026,7 @@ pub const CodeGen = struct {
try self.registerLambdaAsFunction(cd.name, cd.value.data.lambda);
} else if (cd.value.data == .type_expr) {
try self.type_aliases.put(cd.name, cd.value.data.type_expr.name);
try self.type_registry.put(cd.name, .{ .alias = cd.value.data.type_expr.name });
} else if (cd.value.data == .call) {
// Check if this is a generic struct or type function instantiation
const callee_name = if (cd.value.data.call.callee.data == .identifier)
@@ -1009,20 +1039,24 @@ pub const CodeGen = struct {
const result_ty = try self.instantiateGenericStruct(cn, tmpl, cd.value.data.call.args);
if (result_ty.isStruct()) {
try self.type_aliases.put(cd.name, result_ty.struct_type);
try self.type_registry.put(cd.name, .{ .alias = result_ty.struct_type });
}
} else if (self.generic_templates.get(cn)) |tmpl| {
// Type-returning function: Foo :: Complex(u32);
const result_ty = try self.instantiateTypeFunction(cd.name, cn, tmpl, cd.value.data.call.args);
if (result_ty.isStruct()) {
try self.type_aliases.put(cd.name, result_ty.struct_type);
try self.type_registry.put(cd.name, .{ .alias = result_ty.struct_type });
} else if (result_ty.isUnion()) {
try self.type_aliases.put(cd.name, result_ty.union_type);
try self.type_registry.put(cd.name, .{ .alias = result_ty.union_type });
}
} else if (self.builtin_functions.contains(cn)) {
// Builtin type function (e.g., Vector(4, f32), Array(5, s32))
if (self.resolveBuiltinType(cn, cd.value.data.call.args)) |result_ty| {
const display = try result_ty.displayName(self.allocator);
try self.type_aliases.put(cd.name, display);
try self.type_registry.put(cd.name, .{ .alias = display });
} else {
try self.registerTopLevelConstant(cd);
}
@@ -1054,26 +1088,6 @@ pub const CodeGen = struct {
}
}
// Pre-register all known types for Any type ID assignment
{
var it = self.struct_types.iterator();
while (it.next()) |entry| {
_ = try self.getAnyTypeId(entry.key_ptr.*, .{ .struct_type = entry.key_ptr.* });
}
}
{
var it = self.enum_types.iterator();
while (it.next()) |entry| {
_ = try self.getAnyTypeId(entry.key_ptr.*, .{ .enum_type = entry.key_ptr.* });
}
}
{
var it = self.tagged_enum_types.iterator();
while (it.next()) |entry| {
_ = try self.getAnyTypeId(entry.key_ptr.*, .{ .union_type = entry.key_ptr.* });
}
}
// Pass 2: Generate all function bodies
// Functions with Any parameters (like any_to_string) are deferred to Pass 3
// so that all types are registered before their type-match expressions are compiled.
@@ -1311,30 +1325,42 @@ pub const CodeGen = struct {
if (self.type_param_bindings) |bindings| {
if (bindings.get(name)) |t| return t;
}
// Check type aliases
if (self.type_aliases.get(name)) |target| {
if (Type.fromName(target)) |t| return t;
if (self.struct_types.contains(target)) return .{ .struct_type = target };
if (self.tagged_enum_types.contains(target)) return .{ .union_type = target };
if (self.union_types.contains(target)) return .{ .union_type = target };
// Unified type registry lookup
if (self.type_registry.get(name)) |entry| {
switch (entry) {
.struct_info => return .{ .struct_type = name },
.tagged_enum => return .{ .union_type = name },
.union_info => return .{ .union_type = name },
.plain_enum => return .{ .enum_type = name },
.alias => |target| {
if (Type.fromName(target)) |t| return t;
if (self.type_registry.get(target)) |inner| {
switch (inner) {
.struct_info => return .{ .struct_type = target },
.tagged_enum => return .{ .union_type = target },
.union_info => return .{ .union_type = target },
.plain_enum => return .{ .enum_type = target },
.alias => {},
}
}
},
}
}
// Check enum types
if (self.enum_types.contains(name)) return .{ .enum_type = name };
// Check struct types
if (self.struct_types.contains(name)) return .{ .struct_type = name };
// Check union types (tagged enums and C-style unions)
if (self.tagged_enum_types.contains(name)) return .{ .union_type = name };
if (self.union_types.contains(name)) return .{ .union_type = name };
}
// Safety net: inline declarations that should have been hoisted
if (tn.data == .struct_decl) {
const sn = tn.data.struct_decl.name;
if (self.struct_types.contains(sn)) return .{ .struct_type = sn };
if (self.type_registry.get(sn)) |e| {
if (e == .struct_info) return .{ .struct_type = sn };
}
}
if (tn.data == .enum_decl) {
const en = tn.data.enum_decl.name;
if (self.tagged_enum_types.contains(en)) return .{ .union_type = en };
if (self.enum_types.contains(en)) return .{ .enum_type = en };
if (self.type_registry.get(en)) |e| switch (e) {
.tagged_enum => return .{ .union_type = en },
.plain_enum => return .{ .enum_type = en },
else => {},
};
}
return .void_type;
}
@@ -1432,7 +1458,7 @@ pub const CodeGen = struct {
}
}
try self.struct_types.put(mangled_name, .{
const si = StructInfo{
.field_names = sd.field_names,
.field_types = build.field_sx_types,
.field_defaults = resolved_defaults,
@@ -1441,7 +1467,9 @@ pub const CodeGen = struct {
.type_param_names = try tp_names.toOwnedSlice(self.allocator),
.type_param_types = try tp_types.toOwnedSlice(self.allocator),
.template_name = template_name,
});
};
try self.struct_types.put(mangled_name, si);
try self.type_registry.put(mangled_name, .{ .struct_info = si });
_ = try self.getAnyTypeId(mangled_name, .{ .struct_type = mangled_name });
return .{ .struct_type = mangled_name };
@@ -1492,13 +1520,15 @@ pub const CodeGen = struct {
const resolved_defaults = try self.allocator.dupe(?*Node, struct_decl.field_defaults);
const display_name = try self.allocator.dupe(u8, alias_name);
try self.struct_types.put(mangled_name, .{
const si2 = StructInfo{
.field_names = struct_decl.field_names,
.field_types = build.field_sx_types,
.field_defaults = resolved_defaults,
.llvm_type = build.llvm_type,
.display_name = display_name,
});
};
try self.struct_types.put(mangled_name, si2);
try self.type_registry.put(mangled_name, .{ .struct_info = si2 });
_ = try self.getAnyTypeId(mangled_name, .{ .struct_type = mangled_name });
return .{ .struct_type = mangled_name };
@@ -1507,13 +1537,15 @@ pub const CodeGen = struct {
fn registerInstantiatedTaggedEnum(self: *CodeGen, mangled_name: []const u8, union_decl: ast.EnumDecl) !Type {
const build = try self.buildUnionFields(mangled_name, union_decl.variant_types);
try self.tagged_enum_types.put(mangled_name, .{
const tei = TaggedEnumInfo{
.variant_names = union_decl.variant_names,
.variant_types = build.variant_sx_types,
.llvm_type = build.llvm_type,
.max_payload_size = build.max_payload_size,
.payload_field_index = build.payload_field_index,
});
};
try self.tagged_enum_types.put(mangled_name, tei);
try self.type_registry.put(mangled_name, .{ .tagged_enum = tei });
_ = try self.getAnyTypeId(mangled_name, .{ .union_type = mangled_name });
return .{ .union_type = mangled_name };
@@ -1868,9 +1900,12 @@ pub const CodeGen = struct {
try self.registerTaggedEnum(ed);
const qualified_u = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, ed.name });
try self.type_aliases.put(qualified_u, ed.name);
try self.type_registry.put(qualified_u, .{ .alias = ed.name });
} else {
const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, ed.name });
try self.enum_types.put(qualified, ed.variant_names);
try self.type_registry.put(qualified, .{ .plain_enum = ed.variant_names });
_ = try self.getAnyTypeId(qualified, .{ .enum_type = qualified });
if (ed.backing_type) |bt_node| {
const bt = self.resolveType(bt_node);
try self.enum_backing_types.put(qualified, self.typeToLLVM(bt));
@@ -1882,11 +1917,13 @@ pub const CodeGen = struct {
// Register qualified alias so rl.Color resolves to Color
const qualified_s = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, sd.name });
try self.type_aliases.put(qualified_s, sd.name);
try self.type_registry.put(qualified_s, .{ .alias = sd.name });
},
.union_decl => |ud| {
try self.registerUnionType(ud);
const qualified_u = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, ud.name });
try self.type_aliases.put(qualified_u, ud.name);
try self.type_registry.put(qualified_u, .{ .alias = ud.name });
},
.const_decl => |cd| {
if (cd.value.data == .builtin_expr) {
@@ -1897,6 +1934,7 @@ pub const CodeGen = struct {
} else if (cd.value.data == .type_expr) {
const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, cd.name });
try self.type_aliases.put(qualified, cd.value.data.type_expr.name);
try self.type_registry.put(qualified, .{ .alias = cd.value.data.type_expr.name });
}
},
.library_decl => |ld| {
@@ -1948,8 +1986,8 @@ pub const CodeGen = struct {
// For function calls, look up the registered function's return type
if (expr.data == .call) {
if (self.resolveCalleeName(expr.data.call)) |callee_name| {
const callee_name_z = self.allocator.dupeZ(u8, callee_name) catch return Type.s(64);
const callee_fn = c.LLVMGetNamedFunction(self.module, callee_name_z.ptr) orelse return Type.s(64);
var cnbuf: [256]u8 = undefined;
const callee_fn = c.LLVMGetNamedFunction(self.module, self.nameToCStr(callee_name, &cnbuf)) orelse return Type.s(64);
const fn_type = c.LLVMGlobalGetValueType(callee_fn);
const ret_llvm = c.LLVMGetReturnType(fn_type);
return self.llvmTypeToSxType(ret_llvm);
@@ -2947,14 +2985,14 @@ pub const CodeGen = struct {
}
// Fall back to function name → function pointer value
{
const name_z = try self.allocator.dupeZ(u8, ident.name);
var fn_val = c.LLVMGetNamedFunction(self.module, name_z.ptr);
var nbuf: [256]u8 = undefined;
var fn_val = c.LLVMGetNamedFunction(self.module, self.nameToCStr(ident.name, &nbuf));
if (fn_val == null) {
// Try qualified name with current namespace
if (self.current_namespace) |ns| {
const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns, ident.name });
const q_z = try self.allocator.dupeZ(u8, qualified);
fn_val = c.LLVMGetNamedFunction(self.module, q_z.ptr);
var qbuf: [256]u8 = undefined;
fn_val = c.LLVMGetNamedFunction(self.module, self.nameToCStr(qualified, &qbuf));
}
}
if (fn_val != null) return fn_val.?;
@@ -3301,6 +3339,7 @@ pub const CodeGen = struct {
try self.registerTaggedEnum(hoisted);
} else {
try self.enum_types.put(synthetic_name, inline_ed.variant_names);
try self.type_registry.put(synthetic_name, .{ .plain_enum = inline_ed.variant_names });
_ = try self.getAnyTypeId(synthetic_name, .{ .enum_type = synthetic_name });
if (inline_ed.backing_type) |bt_node| {
const bt = self.resolveType(bt_node);
@@ -3342,12 +3381,14 @@ pub const CodeGen = struct {
}
}
try self.struct_types.put(sd.name, .{
const sinfo = StructInfo{
.field_names = sd.field_names,
.field_types = build.field_sx_types,
.field_defaults = resolved_defaults,
.llvm_type = build.llvm_type,
});
};
try self.struct_types.put(sd.name, sinfo);
try self.type_registry.put(sd.name, .{ .struct_info = sinfo });
_ = try self.getAnyTypeId(sd.name, .{ .struct_type = sd.name });
}
@@ -3376,13 +3417,15 @@ pub const CodeGen = struct {
}
}
try self.tagged_enum_types.put(ud.name, .{
const tei_layout = TaggedEnumInfo{
.variant_names = ud.variant_names,
.variant_types = try variant_sx_types.toOwnedSlice(self.allocator),
.llvm_type = layout.llvm_type,
.max_payload_size = layout.payload_size,
.payload_field_index = layout.payload_field_index,
});
};
try self.tagged_enum_types.put(ud.name, tei_layout);
try self.type_registry.put(ud.name, .{ .tagged_enum = tei_layout });
} else {
// Primitive backing type (e.g. enum u32 { ... })
if (ud.backing_type) |bt_node| {
@@ -3392,13 +3435,15 @@ pub const CodeGen = struct {
const build = try self.buildUnionFields(ud.name, ud.variant_types);
try self.tagged_enum_types.put(ud.name, .{
const tei_build = TaggedEnumInfo{
.variant_names = ud.variant_names,
.variant_types = build.variant_sx_types,
.llvm_type = build.llvm_type,
.max_payload_size = build.max_payload_size,
.payload_field_index = build.payload_field_index,
});
};
try self.tagged_enum_types.put(ud.name, tei_build);
try self.type_registry.put(ud.name, .{ .tagged_enum = tei_build });
}
_ = try self.getAnyTypeId(ud.name, .{ .union_type = ud.name });
@@ -3575,13 +3620,15 @@ pub const CodeGen = struct {
}
}
try self.union_types.put(ud.name, .{
const uinfo = UnionInfo{
.field_names = ud.field_names,
.field_types = resolved_field_types,
.llvm_type = llvm_type,
.total_size = max_size,
.promoted_fields = promoted,
});
};
try self.union_types.put(ud.name, uinfo);
try self.type_registry.put(ud.name, .{ .union_info = uinfo });
// Note: C-style unions are not registered with the Any type system.
// They can't be meaningfully printed as a whole — access individual fields instead.
}
@@ -5112,22 +5159,22 @@ pub const CodeGen = struct {
return self.genMemcpy(call_node.args);
}
const name_z = try self.allocator.dupeZ(u8, callee_name);
var callee_fn = c.LLVMGetNamedFunction(self.module, name_z.ptr);
var nbuf: [256]u8 = undefined;
var callee_fn = c.LLVMGetNamedFunction(self.module, self.nameToCStr(callee_name, &nbuf));
// Foreign function fallback: qualified name "ns.Func" → try unqualified "Func" (the C symbol)
if (callee_fn == null) {
if (std.mem.lastIndexOfScalar(u8, callee_name, '.')) |dot_idx| {
const base_name = callee_name[dot_idx + 1 ..];
const base_z = try self.allocator.dupeZ(u8, base_name);
callee_fn = c.LLVMGetNamedFunction(self.module, base_z.ptr);
var bbuf: [256]u8 = undefined;
callee_fn = c.LLVMGetNamedFunction(self.module, self.nameToCStr(base_name, &bbuf));
}
}
// Intra-namespace fallback: try qualified name
if (callee_fn == null) {
if (self.current_namespace) |ns| {
const qualified2 = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns, callee_name });
const qualified_z = try self.allocator.dupeZ(u8, qualified2);
callee_fn = c.LLVMGetNamedFunction(self.module, qualified_z.ptr);
var qbuf: [256]u8 = undefined;
callee_fn = c.LLVMGetNamedFunction(self.module, self.nameToCStr(qualified2, &qbuf));
}
}
// Function pointer indirect call: callee is a variable with function_type
@@ -6677,13 +6724,23 @@ pub const CodeGen = struct {
fn resolveTypeFromName(self: *CodeGen, name: []const u8) ?Type {
// Primitives
if (Type.fromName(name)) |t| return t;
// Structs
if (self.struct_types.contains(name)) return .{ .struct_type = name };
// Unions (tagged enums and C-style)
if (self.tagged_enum_types.contains(name)) return .{ .union_type = name };
if (self.union_types.contains(name)) return .{ .union_type = name };
// Enums
if (self.enum_types.contains(name)) return .{ .enum_type = name };
// Unified type registry lookup
if (self.type_registry.get(name)) |entry| switch (entry) {
.struct_info => return .{ .struct_type = name },
.tagged_enum => return .{ .union_type = name },
.union_info => return .{ .union_type = name },
.plain_enum => return .{ .enum_type = name },
.alias => |target| {
if (Type.fromName(target)) |t| return t;
if (self.type_registry.get(target)) |inner| switch (inner) {
.struct_info => return .{ .struct_type = target },
.tagged_enum => return .{ .union_type = target },
.union_info => return .{ .union_type = target },
.plain_enum => return .{ .enum_type = target },
.alias => {},
};
},
};
// Vector display name: "Vector(N,T)"
if (name.len > 8 and std.mem.startsWith(u8, name, "Vector(") and name[name.len - 1] == ')') {
const inner = name[7 .. name.len - 1]; // "N,T"
@@ -6847,14 +6904,14 @@ pub const CodeGen = struct {
if (self.function_return_types.get(qualified)) |ret_ty| return ret_ty;
}
// Fallback: check non-generic LLVM functions
const callee_name_z = self.allocator.dupeZ(u8, callee_name) catch return Type.s(64);
var callee_fn_opt = c.LLVMGetNamedFunction(self.module, callee_name_z.ptr);
var cnbuf2: [256]u8 = undefined;
var callee_fn_opt = c.LLVMGetNamedFunction(self.module, self.nameToCStr(callee_name, &cnbuf2));
// Intra-namespace fallback
if (callee_fn_opt == null) {
if (self.current_namespace) |ns2| {
const q = std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns2, callee_name }) catch return Type.s(64);
const qz = self.allocator.dupeZ(u8, q) catch return Type.s(64);
callee_fn_opt = c.LLVMGetNamedFunction(self.module, qz.ptr);
var qbuf2: [256]u8 = undefined;
callee_fn_opt = c.LLVMGetNamedFunction(self.module, self.nameToCStr(q, &qbuf2));
}
}
if (callee_fn_opt) |callee_fn| {

View File

@@ -711,16 +711,11 @@ pub const Parser = struct {
/// 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;
var seen = std.StringHashMap(void).init(self.allocator);
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) {
if (!seen.contains(param.name)) {
try seen.put(param.name, {});
try type_params.append(self.allocator, .{ .name = param.name, .constraint = param.type_expr });
}
} else {
@@ -728,14 +723,8 @@ pub const Parser = struct {
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) {
if (!seen.contains(gen_name)) {
try seen.put(gen_name, {});
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 {};
}
@@ -1506,8 +1495,30 @@ pub const Parser = struct {
return try self.createNode(start_pos, .{ .match_expr = .{ .subject = subject, .arms = try arms.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 {
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) return null;
self.advance(); // skip ')'
return self.current.tag;
}
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;
@@ -1517,32 +1528,23 @@ pub const Parser = struct {
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;
}
// Use shared paren-scanning, then check for lambda patterns
const tag = self.peekPastParens() orelse return false;
if (tag == .fat_arrow) return true;
// (params) -> ReturnType => expr
if (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;
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;
@@ -1573,29 +1575,8 @@ pub const Parser = struct {
// ---- 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;
const tag = self.peekPastParens() orelse return false;
return tag == .l_brace or tag == .arrow or tag == .hash_builtin or tag == .hash_foreign or tag == .fat_arrow;
}
fn isAssignOp(self: *const Parser) bool {

View File

@@ -68,6 +68,8 @@ pub const Analyzer = struct {
scope_depth: u32,
/// Stack of symbol counts at each scope entry, for popScope cleanup.
scope_starts: std.ArrayList(u32),
/// Hash index: name → list of indices into symbols array for O(1) lookup
symbol_index: std.StringHashMap(std.ArrayList(u32)),
// Type registries
fn_signatures: std.StringHashMap(FnSignature),
struct_types: std.StringHashMap(StructTypeInfo),
@@ -83,6 +85,7 @@ pub const Analyzer = struct {
.diagnostics = std.ArrayList(Diagnostic).empty,
.scope_depth = 0,
.scope_starts = std.ArrayList(u32).empty,
.symbol_index = std.StringHashMap(std.ArrayList(u32)).init(allocator),
.fn_signatures = std.StringHashMap(FnSignature).init(allocator),
.struct_types = std.StringHashMap(StructTypeInfo).init(allocator),
.enum_types = std.StringHashMap([]const []const u8).init(allocator),
@@ -315,13 +318,15 @@ pub const Analyzer = struct {
},
.chained_comparison => .boolean,
.identifier => |ident| {
// Search symbols backwards for matching name at or above current scope
var i = self.symbols.items.len;
while (i > 0) {
i -= 1;
const sym = self.symbols.items[i];
if (sym.scope_depth <= self.scope_depth and std.mem.eql(u8, sym.name, ident.name)) {
return sym.ty orelse Type.s(64);
// Use symbol index for O(1) name lookup
if (self.symbol_index.get(ident.name)) |indices| {
var j = indices.items.len;
while (j > 0) {
j -= 1;
const sym = self.symbols.items[indices.items[j]];
if (sym.scope_depth <= self.scope_depth) {
return sym.ty orelse Type.s(64);
}
}
}
return Type.s(64);
@@ -508,19 +513,24 @@ pub const Analyzer = struct {
}
fn addSymbol(self: *Analyzer, name: []const u8, kind: SymbolKind, ty: ?Type, span: Span) !void {
// Check for duplicate only within the current scope window.
const scope_start: usize = if (self.scope_starts.items.len > 0)
self.scope_starts.items[self.scope_starts.items.len - 1]
else
0;
for (self.symbols.items[scope_start..]) |sym| {
if (sym.scope_depth == self.scope_depth and std.mem.eql(u8, sym.name, name)) {
try self.diagnostics.append(self.allocator, .{
.level = .warn,
.span = span,
.message = "duplicate declaration",
});
break;
// Check for duplicate using the symbol index
if (self.symbol_index.get(name)) |indices| {
const scope_start: u32 = if (self.scope_starts.items.len > 0)
self.scope_starts.items[self.scope_starts.items.len - 1]
else
0;
for (indices.items) |idx| {
if (idx >= scope_start) {
const sym = self.symbols.items[idx];
if (sym.scope_depth == self.scope_depth) {
try self.diagnostics.append(self.allocator, .{
.level = .warn,
.span = span,
.message = "duplicate declaration",
});
break;
}
}
}
}
@@ -531,26 +541,42 @@ pub const Analyzer = struct {
.def_span = span,
.scope_depth = self.scope_depth,
});
// Update symbol index
const idx: u32 = @intCast(self.symbols.items.len - 1);
const gop = try self.symbol_index.getOrPut(name);
if (!gop.found_existing) {
gop.value_ptr.* = std.ArrayList(u32).empty;
}
try gop.value_ptr.append(self.allocator, idx);
}
/// Pre-register an imported symbol so references in this file can resolve to it.
pub fn preRegisterSymbol(self: *Analyzer, sym: Symbol) !void {
try self.symbols.append(self.allocator, sym);
// Update symbol index
const idx: u32 = @intCast(self.symbols.items.len - 1);
const gop = try self.symbol_index.getOrPut(sym.name);
if (!gop.found_existing) {
gop.value_ptr.* = std.ArrayList(u32).empty;
}
try gop.value_ptr.append(self.allocator, idx);
}
fn resolveIdentifier(self: *Analyzer, name: []const u8, span: Span) !void {
// Search backwards to find the most recent declaration with this name
// that is at or above the current scope depth.
var i = self.symbols.items.len;
while (i > 0) {
i -= 1;
const sym = self.symbols.items[i];
if (sym.scope_depth <= self.scope_depth and std.mem.eql(u8, sym.name, name)) {
try self.references.append(self.allocator, .{
.span = span,
.symbol_index = @intCast(i),
});
return;
// Use symbol index for O(1) name lookup, then walk backwards through indices
if (self.symbol_index.get(name)) |indices| {
var j = indices.items.len;
while (j > 0) {
j -= 1;
const idx = indices.items[j];
const sym = self.symbols.items[idx];
if (sym.scope_depth <= self.scope_depth) {
try self.references.append(self.allocator, .{
.span = span,
.symbol_index = idx,
});
return;
}
}
}
@@ -787,9 +813,10 @@ pub const Analyzer = struct {
const name = tn.data.type_expr.name;
// Check type aliases first
const resolved = self.type_aliases.get(name) orelse name;
for (self.symbols.items) |sym| {
if (!std.mem.eql(u8, sym.name, resolved)) continue;
if (sym.ty) |ty| return ty;
if (self.symbol_index.get(resolved)) |indices| {
for (indices.items) |idx| {
if (self.symbols.items[idx].ty) |ty| return ty;
}
}
}
}

View File

@@ -98,49 +98,65 @@ pub const Type = union(enum) {
}
pub fn fromName(name: []const u8) ?Type {
// Named types (check before variable-width integers since "string" starts with 's')
if (std.mem.eql(u8, name, "string")) return .string_type;
if (std.mem.eql(u8, name, "bool")) return .boolean;
if (std.mem.eql(u8, name, "f32")) return .f32;
if (std.mem.eql(u8, name, "f64")) return .f64;
if (std.mem.eql(u8, name, "Any")) return .any_type;
// Sentinel-terminated slice: [:0]T → string_type when T is u8
if (name.len >= 5 and name[0] == '[' and name[1] == ':') {
// Find closing ']'
if (std.mem.indexOfScalar(u8, name, ']')) |close| {
const sentinel = name[2..close];
const elem = name[close + 1 ..];
if (std.mem.eql(u8, sentinel, "0") and std.mem.eql(u8, elem, "u8")) {
return .string_type;
if (name.len == 0) return null;
return switch (name[0]) {
's' => {
if (std.mem.eql(u8, name, "string")) return .string_type;
if (name.len >= 2) {
const width = std.fmt.parseInt(u8, name[1..], 10) catch return null;
if (width >= 1 and width <= 64) return Type.s(width);
}
}
}
// Many-pointer: [*]T
if (name.len >= 4 and name[0] == '[' and name[1] == '*' and name[2] == ']') {
return .{ .many_pointer_type = .{ .element_name = name[3..] } };
}
// Pointer: *T
if (name.len >= 2 and name[0] == '*') {
return .{ .pointer_type = .{ .pointee_name = name[1..] } };
}
// Vector: Vector(N,T)
if (name.len >= 10 and std.mem.startsWith(u8, name, "Vector(") and name[name.len - 1] == ')') {
const inner = name[7 .. name.len - 1]; // contents between ( and )
if (std.mem.indexOfScalar(u8, inner, ',')) |comma| {
const length = std.fmt.parseInt(u32, inner[0..comma], 10) catch return null;
const elem_name = inner[comma + 1 ..];
if (elem_name.len > 0) {
return .{ .vector_type = .{ .element_name = elem_name, .length = length } };
return null;
},
'u' => {
if (name.len >= 2) {
const width = std.fmt.parseInt(u8, name[1..], 10) catch return null;
if (width >= 1 and width <= 64) return Type.u(width);
}
}
}
// Variable-width integers: s1..s64, u1..u64
if (name.len >= 2 and (name[0] == 's' or name[0] == 'u')) {
const width = std.fmt.parseInt(u8, name[1..], 10) catch return null;
if (width < 1 or width > 64) return null;
return if (name[0] == 's') Type.s(width) else Type.u(width);
}
return null;
return null;
},
'b' => if (std.mem.eql(u8, name, "bool")) .boolean else null,
'f' => {
if (std.mem.eql(u8, name, "f32")) return .f32;
if (std.mem.eql(u8, name, "f64")) return .f64;
return null;
},
'A' => if (std.mem.eql(u8, name, "Any")) .any_type else null,
'v' => if (std.mem.eql(u8, name, "void")) .void_type else null,
'[' => {
// Sentinel-terminated slice: [:0]u8 → string_type
if (name.len >= 5 and name[1] == ':') {
if (std.mem.indexOfScalar(u8, name, ']')) |close| {
const sentinel = name[2..close];
const elem = name[close + 1 ..];
if (std.mem.eql(u8, sentinel, "0") and std.mem.eql(u8, elem, "u8")) {
return .string_type;
}
}
}
// Many-pointer: [*]T
if (name.len >= 4 and name[1] == '*' and name[2] == ']') {
return .{ .many_pointer_type = .{ .element_name = name[3..] } };
}
return null;
},
'*' => if (name.len >= 2) .{ .pointer_type = .{ .pointee_name = name[1..] } } else null,
'V' => {
// Vector(N,T)
if (name.len >= 10 and std.mem.startsWith(u8, name, "Vector(") and name[name.len - 1] == ')') {
const inner = name[7 .. name.len - 1];
if (std.mem.indexOfScalar(u8, inner, ',')) |comma| {
const length = std.fmt.parseInt(u32, inner[0..comma], 10) catch return null;
const elem_name = inner[comma + 1 ..];
if (elem_name.len > 0) {
return .{ .vector_type = .{ .element_name = elem_name, .length = length } };
}
}
}
return null;
},
else => null,
};
}
pub fn fromTypeExpr(node: *Node) ?Type {
@@ -363,20 +379,14 @@ pub const Type = union(enum) {
pub fn displayName(self: Type, allocator: std.mem.Allocator) ![]const u8 {
return switch (self) {
.signed => |w| {
var buf = std.ArrayList(u8).empty;
try buf.append(allocator, 's');
var tmp: [4]u8 = undefined;
const width_str = std.fmt.bufPrint(&tmp, "{d}", .{w}) catch unreachable;
try buf.appendSlice(allocator, width_str);
return try buf.toOwnedSlice(allocator);
var buf: [4]u8 = undefined;
const result = std.fmt.bufPrint(&buf, "s{d}", .{w}) catch unreachable;
return try allocator.dupe(u8, result);
},
.unsigned => |w| {
var buf = std.ArrayList(u8).empty;
try buf.append(allocator, 'u');
var tmp: [4]u8 = undefined;
const width_str = std.fmt.bufPrint(&tmp, "{d}", .{w}) catch unreachable;
try buf.appendSlice(allocator, width_str);
return try buf.toOwnedSlice(allocator);
var buf: [4]u8 = undefined;
const result = std.fmt.bufPrint(&buf, "u{d}", .{w}) catch unreachable;
return try allocator.dupe(u8, result);
},
.f32 => "f32",
.f64 => "f64",
@@ -388,43 +398,64 @@ pub const Type = union(enum) {
.struct_type => |name| name,
.union_type => |name| name,
.slice_type => |info| {
var buf = std.ArrayList(u8).empty;
try buf.appendSlice(allocator, "[]");
try buf.appendSlice(allocator, info.element_name);
return try buf.toOwnedSlice(allocator);
var buf: [128]u8 = undefined;
const result = std.fmt.bufPrint(&buf, "[]{s}", .{info.element_name}) catch {
// Fall back to dynamic allocation for very long element names
var dyn = std.ArrayList(u8).empty;
try dyn.appendSlice(allocator, "[]");
try dyn.appendSlice(allocator, info.element_name);
return try dyn.toOwnedSlice(allocator);
};
return try allocator.dupe(u8, result);
},
.pointer_type => |info| {
var buf = std.ArrayList(u8).empty;
try buf.append(allocator, '*');
try buf.appendSlice(allocator, info.pointee_name);
return try buf.toOwnedSlice(allocator);
var buf: [128]u8 = undefined;
const result = std.fmt.bufPrint(&buf, "*{s}", .{info.pointee_name}) catch {
var dyn = std.ArrayList(u8).empty;
try dyn.appendSlice(allocator, "*");
try dyn.appendSlice(allocator, info.pointee_name);
return try dyn.toOwnedSlice(allocator);
};
return try allocator.dupe(u8, result);
},
.many_pointer_type => |info| {
var buf = std.ArrayList(u8).empty;
try buf.appendSlice(allocator, "[*]");
try buf.appendSlice(allocator, info.element_name);
return try buf.toOwnedSlice(allocator);
var buf: [128]u8 = undefined;
const result = std.fmt.bufPrint(&buf, "[*]{s}", .{info.element_name}) catch {
var dyn = std.ArrayList(u8).empty;
try dyn.appendSlice(allocator, "[*]");
try dyn.appendSlice(allocator, info.element_name);
return try dyn.toOwnedSlice(allocator);
};
return try allocator.dupe(u8, result);
},
.array_type => |info| {
var buf = std.ArrayList(u8).empty;
try buf.append(allocator, '[');
var tmp: [10]u8 = undefined;
const len_str = std.fmt.bufPrint(&tmp, "{d}", .{info.length}) catch unreachable;
try buf.appendSlice(allocator, len_str);
try buf.append(allocator, ']');
try buf.appendSlice(allocator, info.element_name);
return try buf.toOwnedSlice(allocator);
var buf: [128]u8 = undefined;
const result = std.fmt.bufPrint(&buf, "[{d}]{s}", .{ info.length, info.element_name }) catch {
var dyn = std.ArrayList(u8).empty;
try dyn.appendSlice(allocator, "[");
var tmp: [10]u8 = undefined;
const len_str = std.fmt.bufPrint(&tmp, "{d}", .{info.length}) catch unreachable;
try dyn.appendSlice(allocator, len_str);
try dyn.appendSlice(allocator, "]");
try dyn.appendSlice(allocator, info.element_name);
return try dyn.toOwnedSlice(allocator);
};
return try allocator.dupe(u8, result);
},
.vector_type => |info| {
var buf = std.ArrayList(u8).empty;
try buf.appendSlice(allocator, "Vector(");
var tmp: [10]u8 = undefined;
const len_str = std.fmt.bufPrint(&tmp, "{d}", .{info.length}) catch unreachable;
try buf.appendSlice(allocator, len_str);
try buf.appendSlice(allocator, ",");
try buf.appendSlice(allocator, info.element_name);
try buf.append(allocator, ')');
return try buf.toOwnedSlice(allocator);
var buf: [128]u8 = undefined;
const result = std.fmt.bufPrint(&buf, "Vector({d},{s})", .{ info.length, info.element_name }) catch {
var dyn = std.ArrayList(u8).empty;
try dyn.appendSlice(allocator, "Vector(");
var tmp: [10]u8 = undefined;
const len_str = std.fmt.bufPrint(&tmp, "{d}", .{info.length}) catch unreachable;
try dyn.appendSlice(allocator, len_str);
try dyn.appendSlice(allocator, ",");
try dyn.appendSlice(allocator, info.element_name);
try dyn.appendSlice(allocator, ")");
return try dyn.toOwnedSlice(allocator);
};
return try allocator.dupe(u8, result);
},
.function_type => |info| {
var buf = std.ArrayList(u8).empty;