graphics
This commit is contained in:
@@ -61,6 +61,8 @@ pub const Node = struct {
|
||||
continue_expr: void,
|
||||
undef_literal: void,
|
||||
builtin_expr: void,
|
||||
foreign_expr: void,
|
||||
library_decl: LibraryDecl,
|
||||
|
||||
pub fn declName(self: Data) ?[]const u8 {
|
||||
return switch (self) {
|
||||
@@ -353,3 +355,7 @@ pub const NamespaceDecl = struct {
|
||||
name: []const u8,
|
||||
decls: []const *Node,
|
||||
};
|
||||
|
||||
pub const LibraryDecl = struct {
|
||||
lib_name: []const u8,
|
||||
};
|
||||
|
||||
302
src/codegen.zig
302
src/codegen.zig
@@ -91,6 +91,10 @@ pub const CodeGen = struct {
|
||||
current_match_tags: ?[]const u64 = null,
|
||||
// Functions deferred to compile after all types are registered (e.g. any_to_string)
|
||||
deferred_fn_bodies: std.ArrayList(DeferredFn),
|
||||
// Libraries to link against (from #library directives)
|
||||
foreign_libraries: std.ArrayList([]const u8),
|
||||
// Set of foreign function names (for ABI lowering at call sites)
|
||||
foreign_fns: std.StringHashMap(void),
|
||||
|
||||
const DeferredFn = struct {
|
||||
fd: ast.FnDecl,
|
||||
@@ -194,6 +198,8 @@ pub const CodeGen = struct {
|
||||
.any_type_id_map = std.StringHashMap(u64).init(allocator),
|
||||
.any_type_entries = std.StringHashMap(AnyTypeEntry).init(allocator),
|
||||
.deferred_fn_bodies = std.ArrayList(DeferredFn).empty,
|
||||
.foreign_libraries = std.ArrayList([]const u8).empty,
|
||||
.foreign_fns = std.StringHashMap(void).init(allocator),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -214,6 +220,8 @@ pub const CodeGen = struct {
|
||||
self.any_type_id_map.deinit();
|
||||
self.any_type_entries.deinit();
|
||||
self.deferred_fn_bodies.deinit(self.allocator);
|
||||
self.foreign_libraries.deinit(self.allocator);
|
||||
self.foreign_fns.deinit();
|
||||
c.LLVMDisposeBuilder(self.builder);
|
||||
c.LLVMDisposeModule(self.module);
|
||||
c.LLVMContextDispose(self.context);
|
||||
@@ -229,6 +237,21 @@ pub const CodeGen = struct {
|
||||
return error.CodeGenError;
|
||||
}
|
||||
|
||||
/// Build an alloca in the entry block of the current function so that
|
||||
/// stack space is reserved once, not on every loop iteration.
|
||||
fn buildEntryBlockAlloca(self: *CodeGen, ty: c.LLVMTypeRef, name: [*:0]const u8) c.LLVMValueRef {
|
||||
const entry_bb = c.LLVMGetEntryBasicBlock(self.current_function);
|
||||
const first_instr = c.LLVMGetFirstInstruction(entry_bb);
|
||||
const tmp_builder = c.LLVMCreateBuilderInContext(self.context);
|
||||
defer c.LLVMDisposeBuilder(tmp_builder);
|
||||
if (first_instr != null) {
|
||||
c.LLVMPositionBuilderBefore(tmp_builder, first_instr);
|
||||
} else {
|
||||
c.LLVMPositionBuilderAtEnd(tmp_builder, entry_bb);
|
||||
}
|
||||
return c.LLVMBuildAlloca(tmp_builder, ty, name);
|
||||
}
|
||||
|
||||
pub fn typeToLLVM(self: *CodeGen, ty: Type) c.LLVMTypeRef {
|
||||
return switch (ty) {
|
||||
.signed => |w| c.LLVMIntTypeInContext(self.context, w),
|
||||
@@ -347,11 +370,17 @@ pub const CodeGen = struct {
|
||||
/// Build an Any value { tag: i32, value: i64 } from a typed LLVM value.
|
||||
/// Small values (ints, floats, bools, enums) are stored inline in the i64.
|
||||
/// Complex values (strings, structs, unions) are stored via pointer (alloca + ptr-to-int).
|
||||
fn buildAnyValue(self: *CodeGen, val: c.LLVMValueRef, ty: Type) !c.LLVMValueRef {
|
||||
fn buildAnyValue(self: *CodeGen, val: c.LLVMValueRef, in_ty: Type) !c.LLVMValueRef {
|
||||
const any_ty = self.getAnyStructType();
|
||||
const i64_ty = c.LLVMInt64TypeInContext(self.context);
|
||||
const undef = c.LLVMGetUndef(any_ty);
|
||||
|
||||
// []u8 boxes as string (same repr, same Any tag)
|
||||
const ty: Type = if (in_ty.isSlice() and std.mem.eql(u8, in_ty.slice_type.element_name, "u8"))
|
||||
.string_type
|
||||
else
|
||||
in_ty;
|
||||
|
||||
// Determine tag
|
||||
const tag: u64 = switch (ty) {
|
||||
.void_type => ANY_TAG_VOID,
|
||||
@@ -394,7 +423,7 @@ pub const CodeGen = struct {
|
||||
.f64 => c.LLVMBuildBitCast(self.builder, val, i64_ty, "any_f64"),
|
||||
.string_type => blk: {
|
||||
// String is {ptr, i32} — store to alloca, pass alloca as i64
|
||||
const str_alloca = c.LLVMBuildAlloca(self.builder, self.getStringStructType(), "any_str_tmp");
|
||||
const str_alloca = self.buildEntryBlockAlloca(self.getStringStructType(), "any_str_tmp");
|
||||
_ = c.LLVMBuildStore(self.builder, val, str_alloca);
|
||||
break :blk c.LLVMBuildPtrToInt(self.builder, str_alloca, i64_ty, "any_str");
|
||||
},
|
||||
@@ -402,7 +431,7 @@ pub const CodeGen = struct {
|
||||
// Struct — store to alloca, pass pointer as i64
|
||||
const info = self.struct_types.get(sname) orelse
|
||||
return c.LLVMGetUndef(any_ty);
|
||||
const alloca = c.LLVMBuildAlloca(self.builder, info.llvm_type, "any_struct_tmp");
|
||||
const alloca = self.buildEntryBlockAlloca(info.llvm_type, "any_struct_tmp");
|
||||
_ = c.LLVMBuildStore(self.builder, val, alloca);
|
||||
break :blk c.LLVMBuildPtrToInt(self.builder, alloca, i64_ty, "any_struct");
|
||||
},
|
||||
@@ -414,20 +443,20 @@ pub const CodeGen = struct {
|
||||
// Union — store to alloca, pass pointer as i64
|
||||
const info = self.union_types.get(uname) orelse
|
||||
return c.LLVMGetUndef(any_ty);
|
||||
const alloca = c.LLVMBuildAlloca(self.builder, info.llvm_type, "any_union_tmp");
|
||||
const alloca = self.buildEntryBlockAlloca(info.llvm_type, "any_union_tmp");
|
||||
_ = c.LLVMBuildStore(self.builder, val, alloca);
|
||||
break :blk c.LLVMBuildPtrToInt(self.builder, alloca, i64_ty, "any_union");
|
||||
},
|
||||
.vector_type, .array_type => blk: {
|
||||
// Vector/Array — store to alloca, pass pointer as i64
|
||||
const llvm_ty = self.typeToLLVM(ty);
|
||||
const alloca = c.LLVMBuildAlloca(self.builder, llvm_ty, "any_vec_tmp");
|
||||
const alloca = self.buildEntryBlockAlloca(llvm_ty, "any_vec_tmp");
|
||||
_ = c.LLVMBuildStore(self.builder, val, alloca);
|
||||
break :blk c.LLVMBuildPtrToInt(self.builder, alloca, i64_ty, "any_vec");
|
||||
},
|
||||
.slice_type => blk: {
|
||||
// Slice {ptr, i32} — store to alloca, pass pointer as i64
|
||||
const alloca = c.LLVMBuildAlloca(self.builder, self.getStringStructType(), "any_slice_tmp");
|
||||
const alloca = self.buildEntryBlockAlloca(self.getStringStructType(), "any_slice_tmp");
|
||||
_ = c.LLVMBuildStore(self.builder, val, alloca);
|
||||
break :blk c.LLVMBuildPtrToInt(self.builder, alloca, i64_ty, "any_slice");
|
||||
},
|
||||
@@ -435,7 +464,7 @@ pub const CodeGen = struct {
|
||||
.meta_type => |mt| blk: {
|
||||
// Meta type: wrap raw char ptr in string slice {ptr, len} for extraction
|
||||
const str_slice = self.buildStringSlice(val, mt.name.len);
|
||||
const alloca = c.LLVMBuildAlloca(self.builder, self.getStringStructType(), "any_type_tmp");
|
||||
const alloca = self.buildEntryBlockAlloca(self.getStringStructType(), "any_type_tmp");
|
||||
_ = c.LLVMBuildStore(self.builder, str_slice, alloca);
|
||||
break :blk c.LLVMBuildPtrToInt(self.builder, alloca, i64_ty, "any_type");
|
||||
},
|
||||
@@ -542,6 +571,9 @@ pub const CodeGen = struct {
|
||||
.fn_decl => |fd| {
|
||||
if (fd.body.data == .builtin_expr) {
|
||||
try self.builtin_functions.put(fd.name, {});
|
||||
} else if (fd.body.data == .foreign_expr) {
|
||||
// External C function — register LLVM declaration (no body)
|
||||
try self.registerFnDecl(fd);
|
||||
} else if (fd.type_params.len > 0) {
|
||||
try self.generic_templates.put(fd.name, .{ .fd = fd });
|
||||
} else {
|
||||
@@ -549,6 +581,9 @@ pub const CodeGen = struct {
|
||||
}
|
||||
try self.fn_signatures.put(fd.name, self.buildFnSignature(fd));
|
||||
},
|
||||
.library_decl => |ld| {
|
||||
try self.foreign_libraries.append(self.allocator, ld.lib_name);
|
||||
},
|
||||
.enum_decl => |ed| {
|
||||
try self.enum_types.put(ed.name, ed.variants);
|
||||
_ = try self.getAnyTypeId(ed.name, .{ .enum_type = ed.name });
|
||||
@@ -642,8 +677,8 @@ pub const CodeGen = struct {
|
||||
for (root.data.root.decls) |decl| {
|
||||
switch (decl.data) {
|
||||
.fn_decl => |fd| {
|
||||
if (fd.body.data == .builtin_expr) {
|
||||
// skip
|
||||
if (fd.body.data == .builtin_expr or fd.body.data == .foreign_expr) {
|
||||
// skip — no body to generate
|
||||
} else if (fd.type_params.len == 0) {
|
||||
if (shouldDeferFnBody(fd)) {
|
||||
try self.deferred_fn_bodies.append(self.allocator, .{ .fd = fd, .name = fd.name });
|
||||
@@ -1100,6 +1135,10 @@ pub const CodeGen = struct {
|
||||
}
|
||||
|
||||
fn buildFnType(self: *CodeGen, params: []const ast.Param, return_type: ?*Node, name: []const u8) !c.LLVMTypeRef {
|
||||
return self.buildFnTypeEx(params, return_type, name, false);
|
||||
}
|
||||
|
||||
fn buildFnTypeEx(self: *CodeGen, params: []const ast.Param, return_type: ?*Node, name: []const u8, is_foreign: bool) !c.LLVMTypeRef {
|
||||
const ret_sx_type = self.resolveType(return_type);
|
||||
const is_main = std.mem.eql(u8, name, "main");
|
||||
const ret_llvm_type = if (is_main)
|
||||
@@ -1116,7 +1155,14 @@ pub const CodeGen = struct {
|
||||
} else {
|
||||
const sx_ty = self.resolveType(param.type_expr);
|
||||
if (sx_ty == .void_type) return self.emitErrorFmt("parameter '{s}' in function '{s}' has unresolved type", .{ param.name, name });
|
||||
try param_llvm_types.append(self.allocator, self.typeToLLVM(sx_ty));
|
||||
// Foreign functions: apply C ABI lowering
|
||||
if (is_foreign and sx_ty == .string_type) {
|
||||
try param_llvm_types.append(self.allocator, c.LLVMPointerTypeInContext(self.context, 0));
|
||||
} else if (is_foreign and sx_ty.isStruct()) {
|
||||
try param_llvm_types.append(self.allocator, self.getForeignParamABIType(sx_ty));
|
||||
} else {
|
||||
try param_llvm_types.append(self.allocator, self.typeToLLVM(sx_ty));
|
||||
}
|
||||
}
|
||||
}
|
||||
const params_slice = try param_llvm_types.toOwnedSlice(self.allocator);
|
||||
@@ -1129,14 +1175,82 @@ pub const CodeGen = struct {
|
||||
);
|
||||
}
|
||||
|
||||
/// For foreign (C ABI) functions on ARM64, struct parameters must be lowered
|
||||
/// to their ABI-equivalent types. LLVM does NOT do this automatically.
|
||||
/// - HFA (1-4 same float/double fields): [N x float/double]
|
||||
/// - Non-HFA ≤ 8 bytes: i64
|
||||
/// - Non-HFA 9-16 bytes: [2 x i64]
|
||||
fn getForeignParamABIType(self: *CodeGen, sx_ty: Type) c.LLVMTypeRef {
|
||||
const is_aarch64 = comptime @import("builtin").cpu.arch == .aarch64;
|
||||
if (!is_aarch64) return self.typeToLLVM(sx_ty);
|
||||
if (!sx_ty.isStruct()) return self.typeToLLVM(sx_ty);
|
||||
|
||||
const sname = self.type_aliases.get(sx_ty.struct_type) orelse sx_ty.struct_type;
|
||||
const info = self.struct_types.get(sname) orelse return self.typeToLLVM(sx_ty);
|
||||
|
||||
// Check HFA: 1-4 fields all of the same float type
|
||||
const field_types = info.field_types;
|
||||
if (field_types.len >= 1 and field_types.len <= 4) {
|
||||
const first = field_types[0];
|
||||
if (first == .f32 or first == .f64) {
|
||||
var all_same = true;
|
||||
for (field_types[1..]) |ft| {
|
||||
if (!std.meta.eql(ft, first)) {
|
||||
all_same = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (all_same) {
|
||||
const elem_ty = if (first == .f32)
|
||||
c.LLVMFloatTypeInContext(self.context)
|
||||
else
|
||||
c.LLVMDoubleTypeInContext(self.context);
|
||||
return c.LLVMArrayType2(elem_ty, @intCast(field_types.len));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Non-HFA: pack into integer registers
|
||||
const data_layout = c.LLVMGetModuleDataLayout(self.module);
|
||||
const size = c.LLVMStoreSizeOfType(data_layout, info.llvm_type);
|
||||
if (size <= 8) return c.LLVMInt64TypeInContext(self.context);
|
||||
if (size <= 16) return c.LLVMArrayType2(c.LLVMInt64TypeInContext(self.context), 2);
|
||||
// > 16 bytes: pass by pointer (indirect) — not yet handled, fall back to struct type
|
||||
return info.llvm_type;
|
||||
}
|
||||
|
||||
/// Convert a struct value to its C ABI representation for a foreign call.
|
||||
/// Stores the struct to memory, then loads as the ABI type.
|
||||
fn convertStructToABI(self: *CodeGen, struct_val: c.LLVMValueRef, struct_ty: c.LLVMTypeRef, abi_ty: c.LLVMTypeRef) c.LLVMValueRef {
|
||||
const data_layout = c.LLVMGetModuleDataLayout(self.module);
|
||||
const struct_size = c.LLVMStoreSizeOfType(data_layout, struct_ty);
|
||||
const abi_size = c.LLVMStoreSizeOfType(data_layout, abi_ty);
|
||||
|
||||
if (struct_size == abi_size) {
|
||||
// Same size (e.g. {float, float} → [2 x float]): store and reload
|
||||
const alloca = self.buildEntryBlockAlloca(struct_ty, "abi_tmp");
|
||||
_ = c.LLVMBuildStore(self.builder, struct_val, alloca);
|
||||
return c.LLVMBuildLoad2(self.builder, abi_ty, alloca, "abi_arg");
|
||||
} else {
|
||||
// Struct smaller than ABI type (e.g. {i8,i8,i8,i8} → i64): zero-init, then store struct
|
||||
const alloca = self.buildEntryBlockAlloca(abi_ty, "abi_tmp");
|
||||
_ = c.LLVMBuildStore(self.builder, c.LLVMConstNull(abi_ty), alloca);
|
||||
_ = c.LLVMBuildStore(self.builder, struct_val, alloca);
|
||||
return c.LLVMBuildLoad2(self.builder, abi_ty, alloca, "abi_arg");
|
||||
}
|
||||
}
|
||||
|
||||
fn registerFnDecl(self: *CodeGen, fd: ast.FnDecl) !void {
|
||||
return self.registerFnDeclAs(fd, fd.name);
|
||||
}
|
||||
|
||||
fn registerFnDeclAs(self: *CodeGen, fd: ast.FnDecl, llvm_name: []const u8) !void {
|
||||
const fn_type = try self.buildFnType(fd.params, fd.return_type, fd.name);
|
||||
const is_foreign = fd.body.data == .foreign_expr;
|
||||
const fn_type = try self.buildFnTypeEx(fd.params, fd.return_type, fd.name, is_foreign);
|
||||
const name_z = try self.allocator.dupeZ(u8, llvm_name);
|
||||
_ = c.LLVMAddFunction(self.module, name_z.ptr, fn_type);
|
||||
// Track foreign functions for ABI lowering at call sites
|
||||
if (is_foreign) try self.foreign_fns.put(llvm_name, {});
|
||||
// Track resolved parameter types for accurate call-site conversion
|
||||
var param_types = std.ArrayList(Type).empty;
|
||||
for (fd.params) |param| {
|
||||
@@ -1173,7 +1287,19 @@ pub const CodeGen = struct {
|
||||
continue;
|
||||
}
|
||||
const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, fd.name });
|
||||
if (fd.type_params.len > 0) {
|
||||
if (fd.body.data == .foreign_expr) {
|
||||
// External C function in namespace — register LLVM declaration with C name only
|
||||
try self.registerFnDeclAs(fd, fd.name);
|
||||
// Also track qualified name as foreign for ABI lowering at call sites
|
||||
try self.foreign_fns.put(qualified, {});
|
||||
// Store param types under qualified name so call-site type resolution works
|
||||
var param_types = std.ArrayList(Type).empty;
|
||||
for (fd.params) |param| {
|
||||
if (param.is_comptime) continue;
|
||||
try param_types.append(self.allocator, self.resolveType(param.type_expr));
|
||||
}
|
||||
try self.fn_param_types.put(qualified, try param_types.toOwnedSlice(self.allocator));
|
||||
} else if (fd.type_params.len > 0) {
|
||||
try self.generic_templates.put(qualified, .{ .fd = fd });
|
||||
} else {
|
||||
try self.registerFnDeclAs(fd, qualified);
|
||||
@@ -1183,8 +1309,17 @@ pub const CodeGen = struct {
|
||||
const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, ed.name });
|
||||
try self.enum_types.put(qualified, ed.variants);
|
||||
},
|
||||
.struct_decl => |sd| try self.registerStructType(sd),
|
||||
.union_decl => |ud| try self.registerUnionType(ud),
|
||||
.struct_decl => |sd| {
|
||||
try self.registerStructType(sd);
|
||||
// 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);
|
||||
},
|
||||
.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);
|
||||
},
|
||||
.const_decl => |cd| {
|
||||
if (cd.value.data == .builtin_expr) {
|
||||
// #builtin constant in namespace — skip codegen
|
||||
@@ -1196,6 +1331,9 @@ pub const CodeGen = struct {
|
||||
try self.type_aliases.put(qualified, cd.value.data.type_expr.name);
|
||||
}
|
||||
},
|
||||
.library_decl => |ld| {
|
||||
try self.foreign_libraries.append(self.allocator, ld.lib_name);
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
@@ -1209,8 +1347,8 @@ pub const CodeGen = struct {
|
||||
for (ns.decls) |decl| {
|
||||
switch (decl.data) {
|
||||
.fn_decl => |fd| {
|
||||
if (fd.body.data == .builtin_expr) {
|
||||
// skip
|
||||
if (fd.body.data == .builtin_expr or fd.body.data == .foreign_expr) {
|
||||
// skip — no body to generate
|
||||
} else if (fd.type_params.len == 0) {
|
||||
const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, fd.name });
|
||||
if (shouldDeferFnBody(fd)) {
|
||||
@@ -1636,7 +1774,7 @@ pub const CodeGen = struct {
|
||||
const type_name = try self.allocator.dupeZ(u8, raw_name);
|
||||
const name_z = try self.allocator.dupeZ(u8, vd.name);
|
||||
const ptr_ty = c.LLVMPointerTypeInContext(self.context, 0);
|
||||
const alloca = c.LLVMBuildAlloca(self.builder, ptr_ty, name_z.ptr);
|
||||
const alloca = self.buildEntryBlockAlloca(ptr_ty, name_z.ptr);
|
||||
const str_val = c.LLVMBuildGlobalStringPtr(self.builder, type_name.ptr, "type_name");
|
||||
_ = c.LLVMBuildStore(self.builder, str_val, alloca);
|
||||
try self.saveShadowed(vd.name);
|
||||
@@ -1676,7 +1814,7 @@ pub const CodeGen = struct {
|
||||
sx_ty = .{ .struct_type = sname };
|
||||
const info = self.struct_types.get(sname) orelse return self.emitErrorFmt("unknown struct type '{s}'", .{sname});
|
||||
const name_z = try self.allocator.dupeZ(u8, vd.name);
|
||||
const alloca = c.LLVMBuildAlloca(self.builder, info.llvm_type, name_z.ptr);
|
||||
const alloca = self.buildEntryBlockAlloca(info.llvm_type, name_z.ptr);
|
||||
|
||||
if (vd.value == null) {
|
||||
// Default-init: per-field defaults or zero
|
||||
@@ -1711,7 +1849,7 @@ pub const CodeGen = struct {
|
||||
sx_ty = .{ .union_type = uname };
|
||||
const info = self.union_types.get(uname) orelse return self.emitErrorFmt("unknown union type '{s}'", .{uname});
|
||||
const name_z = try self.allocator.dupeZ(u8, vd.name);
|
||||
const alloca = c.LLVMBuildAlloca(self.builder, info.llvm_type, name_z.ptr);
|
||||
const alloca = self.buildEntryBlockAlloca(info.llvm_type, name_z.ptr);
|
||||
|
||||
if (vd.value == null) {
|
||||
// Zero-init: tag=0, payload zeroed
|
||||
@@ -1756,7 +1894,7 @@ pub const CodeGen = struct {
|
||||
const arr_info = sx_ty.array_type;
|
||||
const llvm_arr_ty = self.typeToLLVM(sx_ty);
|
||||
const arr_name_z = try self.allocator.dupeZ(u8, vd.name);
|
||||
const arr_alloca = c.LLVMBuildAlloca(self.builder, llvm_arr_ty, arr_name_z.ptr);
|
||||
const arr_alloca = self.buildEntryBlockAlloca(llvm_arr_ty, arr_name_z.ptr);
|
||||
|
||||
if (vd.value == null) {
|
||||
_ = c.LLVMBuildStore(self.builder, c.LLVMConstNull(llvm_arr_ty), arr_alloca);
|
||||
@@ -1798,7 +1936,7 @@ pub const CodeGen = struct {
|
||||
if (sx_ty.isVector()) {
|
||||
const llvm_vec_ty = self.typeToLLVM(sx_ty);
|
||||
const vec_name_z = try self.allocator.dupeZ(u8, vd.name);
|
||||
const vec_alloca = c.LLVMBuildAlloca(self.builder, llvm_vec_ty, vec_name_z.ptr);
|
||||
const vec_alloca = self.buildEntryBlockAlloca(llvm_vec_ty, vec_name_z.ptr);
|
||||
|
||||
if (vd.value == null) {
|
||||
_ = c.LLVMBuildStore(self.builder, c.LLVMConstNull(llvm_vec_ty), vec_alloca);
|
||||
@@ -1826,7 +1964,7 @@ pub const CodeGen = struct {
|
||||
// Non-struct types
|
||||
const llvm_ty = self.typeToLLVM(sx_ty);
|
||||
const name_z = try self.allocator.dupeZ(u8, vd.name);
|
||||
const alloca = c.LLVMBuildAlloca(self.builder, llvm_ty, name_z.ptr);
|
||||
const alloca = self.buildEntryBlockAlloca(llvm_ty, name_z.ptr);
|
||||
|
||||
if (vd.value == null) {
|
||||
// Default-init: zero
|
||||
@@ -1912,7 +2050,7 @@ pub const CodeGen = struct {
|
||||
|
||||
const llvm_ty = self.typeToLLVM(sx_ty);
|
||||
const name_z = try self.allocator.dupeZ(u8, cd.name);
|
||||
const alloca = c.LLVMBuildAlloca(self.builder, llvm_ty, name_z.ptr);
|
||||
const alloca = self.buildEntryBlockAlloca(llvm_ty, name_z.ptr);
|
||||
_ = c.LLVMBuildStore(self.builder, init_val, alloca);
|
||||
try self.saveShadowed(cd.name);
|
||||
try self.named_values.put(cd.name, .{ .ptr = alloca, .ty = sx_ty });
|
||||
@@ -2571,7 +2709,7 @@ pub const CodeGen = struct {
|
||||
const idx = variant_idx orelse return self.emitErrorFmt("no variant '{s}' in union '{s}'", .{ ul.variant_name, resolved_name });
|
||||
|
||||
// Alloca union
|
||||
const alloca = c.LLVMBuildAlloca(self.builder, info.llvm_type, "union_tmp");
|
||||
const alloca = self.buildEntryBlockAlloca(info.llvm_type, "union_tmp");
|
||||
const i64_ty = c.LLVMInt64TypeInContext(self.context);
|
||||
|
||||
// Store tag (field 0)
|
||||
@@ -2613,7 +2751,7 @@ pub const CodeGen = struct {
|
||||
|
||||
// Alloca the struct and default-init all fields (zero or declared defaults)
|
||||
const name_z = try self.allocator.dupeZ(u8, sname);
|
||||
const alloca = c.LLVMBuildAlloca(self.builder, info.llvm_type, name_z.ptr);
|
||||
const alloca = self.buildEntryBlockAlloca(info.llvm_type, name_z.ptr);
|
||||
try self.genStructDefaultInit(alloca, info);
|
||||
|
||||
// Determine if this is named or positional mode
|
||||
@@ -2671,7 +2809,7 @@ pub const CodeGen = struct {
|
||||
const arr_info = arr_ty.array_type;
|
||||
const elem_sx_ty = Type.fromName(arr_info.element_name) orelse return self.emitErrorFmt("unknown array element type '{s}'", .{arr_info.element_name});
|
||||
const llvm_arr_ty = self.typeToLLVM(arr_ty);
|
||||
const alloca = c.LLVMBuildAlloca(self.builder, llvm_arr_ty, "arr");
|
||||
const alloca = self.buildEntryBlockAlloca(llvm_arr_ty, "arr");
|
||||
|
||||
const len = @min(al.elements.len, arr_info.length);
|
||||
for (0..len) |i| {
|
||||
@@ -2694,7 +2832,7 @@ pub const CodeGen = struct {
|
||||
// Create backing array [N]elem on the stack
|
||||
const arr_ty: Type = .{ .array_type = .{ .element_name = elem_name, .length = n } };
|
||||
const llvm_arr_ty = self.typeToLLVM(arr_ty);
|
||||
const arr_alloca = c.LLVMBuildAlloca(self.builder, llvm_arr_ty, "slice_backing");
|
||||
const arr_alloca = self.buildEntryBlockAlloca(llvm_arr_ty, "slice_backing");
|
||||
|
||||
// Fill elements
|
||||
for (0..n) |i| {
|
||||
@@ -2758,6 +2896,13 @@ pub const CodeGen = struct {
|
||||
return self.convertValue(val, src_ty, target_ty);
|
||||
}
|
||||
|
||||
// String literal → pointer context: produce raw pointer directly (no {ptr, len} wrapping)
|
||||
if (node.data == .string_literal and target_ty.isPointer()) {
|
||||
const unescaped = try unescapeString(self.allocator, node.data.string_literal.raw);
|
||||
const str_z = try self.allocator.dupeZ(u8, unescaped);
|
||||
return c.LLVMBuildGlobalStringPtr(self.builder, str_z.ptr, "str");
|
||||
}
|
||||
|
||||
// Enum literal assigned to union type: construct tag-only (void variant) union
|
||||
if (node.data == .enum_literal and target_ty.isUnion()) {
|
||||
const ul = ast.UnionLiteral{
|
||||
@@ -2799,7 +2944,7 @@ pub const CodeGen = struct {
|
||||
const variant_ty = info.variant_types[idx];
|
||||
|
||||
// Alloca union, store tag
|
||||
const alloca = c.LLVMBuildAlloca(self.builder, info.llvm_type, "union_lit");
|
||||
const alloca = self.buildEntryBlockAlloca(info.llvm_type, "union_lit");
|
||||
const i32_ty = c.LLVMInt32TypeInContext(self.context);
|
||||
const tag_gep = c.LLVMBuildStructGEP2(self.builder, info.llvm_type, alloca, 0, "tag");
|
||||
_ = c.LLVMBuildStore(self.builder, c.LLVMConstInt(i32_ty, idx, 0), tag_gep);
|
||||
@@ -2826,7 +2971,13 @@ pub const CodeGen = struct {
|
||||
|
||||
// Struct literal targeting struct type: pass struct name context
|
||||
if (node.data == .struct_literal and target_ty.isStruct()) {
|
||||
return self.genStructLiteral(node.data.struct_literal, target_ty.struct_type);
|
||||
const alloca = try self.genStructLiteral(node.data.struct_literal, target_ty.struct_type);
|
||||
// genStructLiteral returns an alloca pointer — load the value for by-value passing
|
||||
const sname = self.type_aliases.get(target_ty.struct_type) orelse target_ty.struct_type;
|
||||
if (self.struct_types.get(sname)) |si| {
|
||||
return c.LLVMBuildLoad2(self.builder, si.llvm_type, alloca, "struct_val");
|
||||
}
|
||||
return alloca;
|
||||
}
|
||||
|
||||
// Array literal with target array type: generate with element conversion
|
||||
@@ -2893,9 +3044,20 @@ pub const CodeGen = struct {
|
||||
}
|
||||
}
|
||||
|
||||
const val = try self.genExpr(node);
|
||||
var val = try self.genExpr(node);
|
||||
const src_ty = self.inferType(node);
|
||||
|
||||
// Struct literals return alloca pointers — load the value for by-value passing
|
||||
if (src_ty.isStruct() and target_ty.isStruct()) {
|
||||
if (c.LLVMGetTypeKind(c.LLVMTypeOf(val)) == c.LLVMPointerTypeKind) {
|
||||
const info = self.struct_types.get(src_ty.struct_type) orelse
|
||||
self.struct_types.get(self.type_aliases.get(src_ty.struct_type) orelse src_ty.struct_type);
|
||||
if (info) |si| {
|
||||
val = c.LLVMBuildLoad2(self.builder, si.llvm_type, val, "struct_load");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Scalar to vector broadcast
|
||||
if (target_ty.isVector() and !src_ty.isVector()) {
|
||||
const elem_ty = target_ty.vectorElementType() orelse return self.emitError("cannot determine vector element type");
|
||||
@@ -2925,6 +3087,13 @@ pub const CodeGen = struct {
|
||||
// Same type → return as-is
|
||||
if (std.meta.eql(src_ty, target_ty)) return val;
|
||||
|
||||
// string <-> []u8: identical LLVM type {ptr, i64}, no conversion needed
|
||||
if ((src_ty == .string_type and target_ty.isSlice() and
|
||||
std.mem.eql(u8, target_ty.slice_type.element_name, "u8")) or
|
||||
(src_ty.isSlice() and std.mem.eql(u8, src_ty.slice_type.element_name, "u8") and
|
||||
target_ty == .string_type))
|
||||
return val;
|
||||
|
||||
const target_llvm = self.typeToLLVM(target_ty);
|
||||
|
||||
// Any → concrete type: extract the i64 value and convert
|
||||
@@ -3014,7 +3183,7 @@ pub const CodeGen = struct {
|
||||
if (src_ty.isUnion() and target_ty.isInt()) {
|
||||
const uname = src_ty.union_type;
|
||||
if (self.union_types.get(uname)) |info| {
|
||||
const tmp = c.LLVMBuildAlloca(self.builder, info.llvm_type, "union_cast");
|
||||
const tmp = self.buildEntryBlockAlloca(info.llvm_type, "union_cast");
|
||||
_ = c.LLVMBuildStore(self.builder, val, tmp);
|
||||
const tag_ptr = c.LLVMBuildStructGEP2(self.builder, info.llvm_type, tmp, 0, "tag_ptr");
|
||||
const tag_val = c.LLVMBuildLoad2(self.builder, c.LLVMInt32TypeInContext(self.context), tag_ptr, "tag_val");
|
||||
@@ -3238,7 +3407,7 @@ pub const CodeGen = struct {
|
||||
const uinfo = self.union_types.get(val_ty.union_type) orelse
|
||||
return self.emitErrorFmt("unknown union type '{s}'", .{val_ty.union_type});
|
||||
|
||||
const union_alloca = c.LLVMBuildAlloca(self.builder, uinfo.llvm_type, "fv_union");
|
||||
const union_alloca = self.buildEntryBlockAlloca(uinfo.llvm_type, "fv_union");
|
||||
_ = c.LLVMBuildStore(self.builder, val, union_alloca);
|
||||
|
||||
// Read tag (field 0)
|
||||
@@ -3313,7 +3482,7 @@ pub const CodeGen = struct {
|
||||
return self.emitErrorFmt("unknown array element type '{s}'", .{ainfo.element_name});
|
||||
const arr_llvm_ty = self.typeToLLVM(val_ty);
|
||||
const elem_llvm_ty = self.typeToLLVM(elem_ty);
|
||||
const arr_alloca = c.LLVMBuildAlloca(self.builder, arr_llvm_ty, "fv_arr");
|
||||
const arr_alloca = self.buildEntryBlockAlloca(arr_llvm_ty, "fv_arr");
|
||||
_ = c.LLVMBuildStore(self.builder, val, arr_alloca);
|
||||
const idx = try self.genExpr(call_node.args[1]);
|
||||
var gep_indices = [_]c.LLVMValueRef{
|
||||
@@ -3337,7 +3506,7 @@ pub const CodeGen = struct {
|
||||
const n = info.field_names.len;
|
||||
|
||||
// Store struct to alloca BEFORE the switch (switch is a terminator)
|
||||
const struct_alloca = c.LLVMBuildAlloca(self.builder, info.llvm_type, "fv_struct");
|
||||
const struct_alloca = self.buildEntryBlockAlloca(info.llvm_type, "fv_struct");
|
||||
_ = c.LLVMBuildStore(self.builder, struct_val, struct_alloca);
|
||||
|
||||
// Generate switch on idx with N cases
|
||||
@@ -3945,6 +4114,14 @@ pub const CodeGen = struct {
|
||||
|
||||
const name_z = try self.allocator.dupeZ(u8, callee_name);
|
||||
var callee_fn = c.LLVMGetNamedFunction(self.module, name_z.ptr);
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
// Intra-namespace fallback: try qualified name
|
||||
if (callee_fn == null) {
|
||||
if (self.current_namespace) |ns| {
|
||||
@@ -4024,7 +4201,7 @@ pub const CodeGen = struct {
|
||||
} else if (var_arg_count > 0) {
|
||||
// Allocate array on stack: [N x elem_type]
|
||||
const arr_ty = c.LLVMArrayType2(elem_llvm_ty, @intCast(var_arg_count));
|
||||
const arr_alloca = c.LLVMBuildAlloca(self.builder, arr_ty, "varargs_arr");
|
||||
const arr_alloca = self.buildEntryBlockAlloca(arr_ty, "varargs_arr");
|
||||
// Store each variadic arg
|
||||
for (0..var_arg_count) |vi_idx| {
|
||||
const arg_val = if (elem_ty.isAny()) blk: {
|
||||
@@ -4068,7 +4245,34 @@ pub const CodeGen = struct {
|
||||
stored_param_types.?[i]
|
||||
else
|
||||
self.llvmTypeToSxType(param_llvm_types[i]);
|
||||
try arg_vals.append(self.allocator, try self.genExprAsType(arg, param_ty));
|
||||
// For #foreign functions, [:0]u8 params have LLVM type ptr
|
||||
const arg_ty = self.inferType(arg);
|
||||
const llvm_param_is_ptr = (i < num_params and
|
||||
c.LLVMGetTypeKind(param_llvm_types[i]) == c.LLVMPointerTypeKind);
|
||||
const ptr_ty = Type{ .pointer_type = .{ .pointee_name = "u8" } };
|
||||
|
||||
var val = if (llvm_param_is_ptr and arg.data == .string_literal) blk: {
|
||||
// String literal → pointer: produce raw ptr directly (context-dependent)
|
||||
break :blk try self.genExprAsType(arg, ptr_ty);
|
||||
} else if (llvm_param_is_ptr and arg_ty == .string_type) blk: {
|
||||
// String variable → pointer: extract .ptr from {ptr, len}
|
||||
const str_val = try self.genExpr(arg);
|
||||
break :blk c.LLVMBuildExtractValue(self.builder, str_val, 0, "str_ptr");
|
||||
} else if ((param_ty.isPointer() or llvm_param_is_ptr) and arg_ty.isSlice() and
|
||||
std.mem.eql(u8, arg_ty.slice_type.element_name, "u8"))
|
||||
{
|
||||
return self.emitError(
|
||||
"cannot pass []u8 to *u8: slice may not be null-terminated; use a string literal or xx cast",
|
||||
);
|
||||
} else try self.genExprAsType(arg, param_ty);
|
||||
// Foreign calls: convert struct values to C ABI representation
|
||||
if (param_ty.isStruct() and self.foreign_fns.contains(callee_name)) {
|
||||
const struct_llvm_ty = self.typeToLLVM(param_ty);
|
||||
if (struct_llvm_ty != param_llvm_types[i]) {
|
||||
val = self.convertStructToABI(val, struct_llvm_ty, param_llvm_types[i]);
|
||||
}
|
||||
}
|
||||
try arg_vals.append(self.allocator, val);
|
||||
} else {
|
||||
try arg_vals.append(self.allocator, try self.genExpr(arg));
|
||||
}
|
||||
@@ -4886,9 +5090,9 @@ pub const CodeGen = struct {
|
||||
const elem_llvm_ty = self.typeToLLVM(elem_ty);
|
||||
|
||||
// Allocate it_index (s64) and it (element type)
|
||||
const idx_alloca = c.LLVMBuildAlloca(self.builder, i64_type, "it_index");
|
||||
const idx_alloca = self.buildEntryBlockAlloca(i64_type, "it_index");
|
||||
_ = c.LLVMBuildStore(self.builder, c.LLVMConstInt(i64_type, 0, 0), idx_alloca);
|
||||
const it_alloca = c.LLVMBuildAlloca(self.builder, elem_llvm_ty, "it");
|
||||
const it_alloca = self.buildEntryBlockAlloca(elem_llvm_ty, "it");
|
||||
|
||||
// Push scope and bind it, it_index
|
||||
try self.pushScope();
|
||||
@@ -5602,7 +5806,7 @@ pub const CodeGen = struct {
|
||||
}
|
||||
if (obj_ty == .string_type) {
|
||||
if (std.mem.eql(u8, fa.field, "len")) return Type.s(64);
|
||||
if (std.mem.eql(u8, fa.field, "ptr")) return .string_type;
|
||||
if (std.mem.eql(u8, fa.field, "ptr")) return .{ .pointer_type = .{ .pointee_name = "u8" } };
|
||||
}
|
||||
if (obj_ty.isSlice()) {
|
||||
if (std.mem.eql(u8, fa.field, "len")) return Type.s(64);
|
||||
@@ -5654,7 +5858,7 @@ pub const CodeGen = struct {
|
||||
},
|
||||
.slice_expr => |se| {
|
||||
const obj_ty = self.inferType(se.object);
|
||||
if (obj_ty == .string_type) return .string_type;
|
||||
if (obj_ty == .string_type) return .{ .slice_type = .{ .element_name = "u8" } };
|
||||
if (obj_ty.isArray()) return .{ .slice_type = .{ .element_name = obj_ty.array_type.element_name } };
|
||||
if (obj_ty.isSlice()) return obj_ty;
|
||||
return .void_type;
|
||||
@@ -5726,9 +5930,23 @@ pub const CodeGen = struct {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn link(io: std.Io, output_obj: []const u8, output_bin: []const u8) !void {
|
||||
pub fn link(allocator: std.mem.Allocator, io: std.Io, output_obj: []const u8, output_bin: []const u8, libraries: []const []const u8) !void {
|
||||
var argv = std.ArrayList([]const u8).empty;
|
||||
try argv.appendSlice(allocator, &.{ "cc", output_obj, "-o", output_bin });
|
||||
|
||||
if (libraries.len > 0) {
|
||||
// Add Homebrew library path on macOS
|
||||
try argv.append(allocator, "-L/opt/homebrew/lib");
|
||||
|
||||
for (libraries) |lib| {
|
||||
const flag = try std.fmt.allocPrint(allocator, "-l{s}", .{lib});
|
||||
try argv.append(allocator, flag);
|
||||
}
|
||||
}
|
||||
|
||||
const argv_slice = try argv.toOwnedSlice(allocator);
|
||||
var child = std.process.spawn(io, .{
|
||||
.argv = &.{ "cc", output_obj, "-o", output_bin },
|
||||
.argv = argv_slice,
|
||||
}) catch return error.LinkError;
|
||||
const result = child.wait(io) catch return error.LinkError;
|
||||
if (result != .exited) return error.LinkError;
|
||||
|
||||
@@ -50,13 +50,15 @@ pub const Lexer = struct {
|
||||
return self.lexString(start);
|
||||
}
|
||||
|
||||
// Directives: #import, #insert, #run, #builtin
|
||||
// Directives: #import, #insert, #run, #builtin, #foreign, #library
|
||||
if (c == '#') {
|
||||
const directives = .{
|
||||
.{ "#import", Tag.hash_import },
|
||||
.{ "#insert", Tag.hash_insert },
|
||||
.{ "#run", Tag.hash_run },
|
||||
.{ "#builtin", Tag.hash_builtin },
|
||||
.{ "#foreign", Tag.hash_foreign },
|
||||
.{ "#library", Tag.hash_library },
|
||||
};
|
||||
inline for (directives) |d| {
|
||||
const keyword = d[0];
|
||||
@@ -370,6 +372,25 @@ test "lex hash_insert" {
|
||||
try std.testing.expectEqual(Tag.invalid, lex2.next().tag);
|
||||
}
|
||||
|
||||
test "lex hash_foreign" {
|
||||
var lex = Lexer.init("#foreign");
|
||||
try std.testing.expectEqual(Tag.hash_foreign, lex.next().tag);
|
||||
try std.testing.expectEqual(Tag.eof, lex.next().tag);
|
||||
|
||||
var lex2 = Lexer.init("#foreignx");
|
||||
try std.testing.expectEqual(Tag.invalid, lex2.next().tag);
|
||||
}
|
||||
|
||||
test "lex hash_library" {
|
||||
var lex = Lexer.init("#library \"raylib\"");
|
||||
try std.testing.expectEqual(Tag.hash_library, lex.next().tag);
|
||||
try std.testing.expectEqual(Tag.string_literal, lex.next().tag);
|
||||
try std.testing.expectEqual(Tag.eof, lex.next().tag);
|
||||
|
||||
var lex2 = Lexer.init("#librarypath");
|
||||
try std.testing.expectEqual(Tag.invalid, lex2.next().tag);
|
||||
}
|
||||
|
||||
test "lex string" {
|
||||
var lex = Lexer.init("\"Hello\"");
|
||||
const tok = lex.next();
|
||||
|
||||
@@ -717,6 +717,8 @@ pub const Server = struct {
|
||||
.hash_import,
|
||||
.hash_insert,
|
||||
.hash_builtin,
|
||||
.hash_foreign,
|
||||
.hash_library,
|
||||
=> ST.keyword,
|
||||
|
||||
// Type keywords
|
||||
|
||||
@@ -148,7 +148,7 @@ fn compile(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, out
|
||||
cg.emitObject(obj_path.ptr) catch { comp.renderErrors(); return error.CompileError; };
|
||||
|
||||
// Link
|
||||
sx.codegen.CodeGen.link(io, obj_path, output_path) catch {
|
||||
sx.codegen.CodeGen.link(allocator, io, obj_path, output_path, cg.foreign_libraries.items) catch {
|
||||
std.debug.print("error: linking failed\n", .{});
|
||||
return error.CompileError;
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -671,6 +671,8 @@ pub const Analyzer = struct {
|
||||
.match_arm,
|
||||
.undef_literal,
|
||||
.builtin_expr,
|
||||
.foreign_expr,
|
||||
.library_decl,
|
||||
.import_decl,
|
||||
.array_type_expr,
|
||||
.slice_type_expr,
|
||||
@@ -925,6 +927,8 @@ pub fn findNodeAtOffset(node: *Node, offset: u32) ?*Node {
|
||||
.match_arm,
|
||||
.undef_literal,
|
||||
.builtin_expr,
|
||||
.foreign_expr,
|
||||
.library_decl,
|
||||
.enum_decl,
|
||||
.struct_decl,
|
||||
.union_decl,
|
||||
|
||||
@@ -77,6 +77,8 @@ pub const Tag = enum {
|
||||
hash_import, // #import
|
||||
hash_insert, // #insert
|
||||
hash_builtin, // #builtin
|
||||
hash_foreign, // #foreign
|
||||
hash_library, // #library
|
||||
triple_minus, // ---
|
||||
|
||||
// Special
|
||||
|
||||
@@ -66,6 +66,17 @@ pub const Type = union(enum) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Many-pointer: [*]T
|
||||
if (name.len >= 4 and name[0] == '[' and name[1] == '*' and name[2] == ']') {
|
||||
return .{ .many_pointer_type = .{ .element_name = name[3..] } };
|
||||
@@ -116,6 +127,19 @@ pub const Type = union(enum) {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn isString(self: Type) bool {
|
||||
return self == .string_type;
|
||||
}
|
||||
|
||||
/// Returns true for both `string` (null-terminated) and `[]u8` (byte slice)
|
||||
pub fn isStringLike(self: Type) bool {
|
||||
if (self == .string_type) return true;
|
||||
if (self.isSlice()) {
|
||||
return std.mem.eql(u8, self.slice_type.element_name, "u8");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn isSlice(self: Type) bool {
|
||||
return switch (self) {
|
||||
.slice_type => true,
|
||||
@@ -226,10 +250,19 @@ pub const Type = union(enum) {
|
||||
/// Everything else requires `xx`.
|
||||
pub fn isImplicitlyConvertibleTo(self: Type, target: Type) bool {
|
||||
if (std.meta.eql(self, target)) return true;
|
||||
// Struct types: compare names by content (not pointer identity)
|
||||
if (self.isStruct() and target.isStruct()) {
|
||||
return std.mem.eql(u8, self.struct_type, target.struct_type);
|
||||
}
|
||||
// Slice types: compare element names by content (not pointer)
|
||||
if (self.isSlice() and target.isSlice()) {
|
||||
return std.mem.eql(u8, self.slice_type.element_name, target.slice_type.element_name);
|
||||
}
|
||||
// string <-> []u8: same layout, bidirectional implicit conversion
|
||||
if (self == .string_type and target.isSlice() and
|
||||
std.mem.eql(u8, target.slice_type.element_name, "u8")) return true;
|
||||
if (self.isSlice() and std.mem.eql(u8, self.slice_type.element_name, "u8") and
|
||||
target == .string_type) return true;
|
||||
// Pointer types: compare pointee names by content, *void is universal (both directions)
|
||||
if (self.isPointer() and target.isPointer()) {
|
||||
if (std.mem.eql(u8, self.pointer_type.pointee_name, "void")) return true;
|
||||
|
||||
Reference in New Issue
Block a user