From 89fc6427c4a426243aaa25dd824f5425dbd62cd4 Mon Sep 17 00:00:00 2001 From: agra Date: Wed, 11 Feb 2026 01:43:30 +0200 Subject: [PATCH] list --- examples/24-list.sx | 26 ++++++- examples/modules/std.sx | 4 +- src/builtins.zig | 27 ++++++- src/codegen.zig | 161 +++++++++++++++++++++++++++++++++++++--- src/parser.zig | 36 ++++++--- src/types.zig | 9 ++- 6 files changed, 236 insertions(+), 27 deletions(-) diff --git a/examples/24-list.sx b/examples/24-list.sx index f14c527..2c146b5 100644 --- a/examples/24-list.sx +++ b/examples/24-list.sx @@ -1,12 +1,32 @@ #import "modules/std.sx"; List :: struct ($T: Type) { - items: [*]T = .[]; - len: s32 = 0; - cap: s32 = 0; + items: [*]T = null; + len: s64 = 0; + cap: s64 = 0; +} + +append :: (list: *List($T), item: T) { + if list.len >= list.cap { + new_cap := if list.cap == 0 then 4 else list.cap * 2; + new_items : [*]T = xx malloc(new_cap * size_of(T)); + if list.len > 0 { + memcpy(new_items, list.items, list.len * size_of(T)); + free(list.items); + } + list.items = new_items; + list.cap = new_cap; + } + list.items[list.len] = item; + list.len += 1; } main :: () { list := List(s32).{}; + append(list, 3); + append(list, 1); + append(list, 4); + append(list, 1); + append(list, 5); print("{}\n", list); } \ No newline at end of file diff --git a/examples/modules/std.sx b/examples/modules/std.sx index 9fefe96..a0c8f55 100644 --- a/examples/modules/std.sx +++ b/examples/modules/std.sx @@ -194,7 +194,9 @@ slice_to_string :: (items: []$T) -> string { pointer_to_string :: (p: $T) -> string { addr : s64 = xx p; - concat(type_name(T), concat("@", int_to_hex_string(addr))); + if addr == 0 { "null"; } else { + concat(type_name(T), concat("@0x", int_to_hex_string(addr))); + } } union_to_string :: (u: $T) -> string { diff --git a/src/builtins.zig b/src/builtins.zig index f6028a8..dfaa760 100644 --- a/src/builtins.zig +++ b/src/builtins.zig @@ -4,11 +4,15 @@ const c = llvm.c; pub const Builtins = struct { printf_fn: c.LLVMValueRef, calloc_fn: c.LLVMValueRef, + malloc_fn: c.LLVMValueRef, + free_fn: c.LLVMValueRef, + memcpy_fn: c.LLVMValueRef, pub fn init(module: c.LLVMModuleRef, ctx: c.LLVMContextRef) Builtins { const ptr_type = c.LLVMPointerTypeInContext(ctx, 0); const i64_type = c.LLVMInt64TypeInContext(ctx); const i32_type = c.LLVMInt32TypeInContext(ctx); + const void_type = c.LLVMVoidTypeInContext(ctx); // Declare: int printf(const char*, ...) var printf_params = [_]c.LLVMTypeRef{ptr_type}; @@ -20,6 +24,27 @@ pub const Builtins = struct { const calloc_type = c.LLVMFunctionType(ptr_type, &calloc_params, 2, 0); const calloc_fn = c.LLVMAddFunction(module, "calloc", calloc_type); - return .{ .printf_fn = printf_fn, .calloc_fn = calloc_fn }; + // Declare: void* malloc(size_t size) + var malloc_params = [_]c.LLVMTypeRef{i64_type}; + const malloc_type = c.LLVMFunctionType(ptr_type, &malloc_params, 1, 0); + const malloc_fn = c.LLVMAddFunction(module, "malloc", malloc_type); + + // Declare: void free(void* ptr) + var free_params = [_]c.LLVMTypeRef{ptr_type}; + const free_type = c.LLVMFunctionType(void_type, &free_params, 1, 0); + const free_fn = c.LLVMAddFunction(module, "free", free_type); + + // Declare: void* memcpy(void* dst, const void* src, size_t n) + var memcpy_params = [_]c.LLVMTypeRef{ ptr_type, ptr_type, i64_type }; + const memcpy_type = c.LLVMFunctionType(ptr_type, &memcpy_params, 3, 0); + const memcpy_fn = c.LLVMAddFunction(module, "memcpy", memcpy_type); + + return .{ + .printf_fn = printf_fn, + .calloc_fn = calloc_fn, + .malloc_fn = malloc_fn, + .free_fn = free_fn, + .memcpy_fn = memcpy_fn, + }; } }; diff --git a/src/codegen.zig b/src/codegen.zig index ddc0893..69d188b 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -139,6 +139,9 @@ pub const CodeGen = struct { field_defaults: []const ?*Node, llvm_type: c.LLVMTypeRef, display_name: ?[]const u8 = null, // pretty name for generic instances + type_param_names: []const []const u8 = &.{}, // original type param names (e.g. ["T"]) + type_param_types: []const Type = &.{}, // resolved types (e.g. [s32]) + template_name: ?[]const u8 = null, // original template name (e.g. "List") }; const UnionInfo = struct { @@ -320,7 +323,15 @@ pub const CodeGen = struct { /// works in any_to_string even before buildAnyValue is called for this type. fn preRegisterAnyType(self: *CodeGen, sx_type: Type) !void { switch (sx_type) { - .struct_type => |name| _ = try self.getAnyTypeId(name, sx_type), + .struct_type => |name| { + _ = try self.getAnyTypeId(name, sx_type); + // Recursively register struct field types + if (self.struct_types.get(name)) |info| { + for (info.field_types) |ft| { + try self.preRegisterAnyType(ft); + } + } + }, .enum_type => |name| _ = try self.getAnyTypeId(name, sx_type), .union_type => |name| _ = try self.getAnyTypeId(name, sx_type), .vector_type => |info| _ = try self.getAnyTypeId(try std.fmt.allocPrint(self.allocator, "vec[{d}]{s}", .{ info.length, info.element_name }), sx_type), @@ -988,12 +999,28 @@ pub const CodeGen = struct { try display_buf.append(self.allocator, ')'); const display_name = try display_buf.toOwnedSlice(self.allocator); + // Collect type param names and resolved types for later extraction + var tp_names = std.ArrayList([]const u8).empty; + var tp_types = std.ArrayList(Type).empty; + for (sd.type_params) |tp| { + const constraint_name = if (tp.constraint.data == .type_expr) tp.constraint.data.type_expr.name else ""; + if (std.mem.eql(u8, constraint_name, "Type")) { + if (type_bindings.get(tp.name)) |ty| { + try tp_names.append(self.allocator, tp.name); + try tp_types.append(self.allocator, ty); + } + } + } + try self.struct_types.put(mangled_name, .{ .field_names = sd.field_names, .field_types = try field_sx_types.toOwnedSlice(self.allocator), .field_defaults = resolved_defaults, .llvm_type = struct_ty, .display_name = display_name, + .type_param_names = try tp_names.toOwnedSlice(self.allocator), + .type_param_types = try tp_types.toOwnedSlice(self.allocator), + .template_name = template_name, }); _ = try self.getAnyTypeId(mangled_name, .{ .struct_type = mangled_name }); @@ -2118,7 +2145,21 @@ pub const CodeGen = struct { c.LLVMPointerTypeInContext(self.context, 0), entry.ptr, "ptr_load"); const gep = c.LLVMBuildStructGEP2(self.builder, info.llvm_type, loaded_ptr, @intCast(fi), "pfield_ptr"); const rhs = try self.genExprAsType(asgn.value, field_ty); - _ = c.LLVMBuildStore(self.builder, rhs, gep); + if (asgn.op == .assign) { + _ = c.LLVMBuildStore(self.builder, rhs, gep); + } else { + const field_llvm_ty = self.typeToLLVM(field_ty); + const cur = c.LLVMBuildLoad2(self.builder, field_llvm_ty, gep, "pcur"); + const store_val = switch (asgn.op) { + .add_assign => if (field_ty.isFloat()) c.LLVMBuildFAdd(self.builder, cur, rhs, "paddtmp") else c.LLVMBuildAdd(self.builder, cur, rhs, "paddtmp"), + .sub_assign => if (field_ty.isFloat()) c.LLVMBuildFSub(self.builder, cur, rhs, "psubtmp") else c.LLVMBuildSub(self.builder, cur, rhs, "psubtmp"), + .mul_assign => if (field_ty.isFloat()) c.LLVMBuildFMul(self.builder, cur, rhs, "pmultmp") else c.LLVMBuildMul(self.builder, cur, rhs, "pmultmp"), + .div_assign => if (field_ty.isFloat()) c.LLVMBuildFDiv(self.builder, cur, rhs, "pdivtmp") else if (field_ty.isUnsigned()) c.LLVMBuildUDiv(self.builder, cur, rhs, "pdivtmp") else c.LLVMBuildSDiv(self.builder, cur, rhs, "pdivtmp"), + .mod_assign => if (field_ty.isFloat()) c.LLVMBuildFRem(self.builder, cur, rhs, "pmodtmp") else if (field_ty.isUnsigned()) c.LLVMBuildURem(self.builder, cur, rhs, "pmodtmp") else c.LLVMBuildSRem(self.builder, cur, rhs, "pmodtmp"), + .assign => unreachable, + }; + _ = c.LLVMBuildStore(self.builder, store_val, gep); + } return null; } return self.emitError("field assignment through pointer requires a struct pointee"); @@ -2985,6 +3026,25 @@ pub const CodeGen = struct { } } + // Implicit address-of: passing T where *T is expected → auto & + if (target_ty.isPointer()) { + const src_ty = self.inferType(node); + const pointee_name = target_ty.pointer_type.pointee_name; + const src_matches = if (src_ty.isStruct()) + std.mem.eql(u8, src_ty.struct_type, pointee_name) or + (if (self.type_aliases.get(src_ty.struct_type)) |alias| std.mem.eql(u8, alias, pointee_name) else false) or + (if (self.type_aliases.get(pointee_name)) |alias| std.mem.eql(u8, alias, src_ty.struct_type) else false) + else + false; + if (src_matches) { + if (node.data == .identifier) { + if (self.named_values.get(node.data.identifier.name)) |entry| { + return entry.ptr; + } + } + } + } + const val = try self.genExpr(node); const src_ty = self.inferType(node); @@ -3496,6 +3556,37 @@ pub const CodeGen = struct { return self.buildStringSliceRT(ptr, size_val); } + fn genMalloc(self: *CodeGen, args: []const *Node) !c.LLVMValueRef { + if (args.len != 1) return self.emitError("malloc expects exactly 1 argument: malloc(size)"); + const builtins = self.builtins orelse return self.emitError("builtins not available"); + const size_val = try self.genExpr(args[0]); + const fn_ty = c.LLVMGlobalGetValueType(builtins.malloc_fn); + var call_args = [_]c.LLVMValueRef{size_val}; + return c.LLVMBuildCall2(self.builder, fn_ty, builtins.malloc_fn, &call_args, 1, "malloc_ptr"); + } + + fn genFree(self: *CodeGen, args: []const *Node) !c.LLVMValueRef { + if (args.len != 1) return self.emitError("free expects exactly 1 argument: free(ptr)"); + const builtins = self.builtins orelse return self.emitError("builtins not available"); + const ptr_val = try self.genExpr(args[0]); + const fn_ty = c.LLVMGlobalGetValueType(builtins.free_fn); + var call_args = [_]c.LLVMValueRef{ptr_val}; + _ = c.LLVMBuildCall2(self.builder, fn_ty, builtins.free_fn, &call_args, 1, ""); + return null; + } + + fn genMemcpy(self: *CodeGen, args: []const *Node) !c.LLVMValueRef { + if (args.len != 3) return self.emitError("memcpy expects 3 arguments: memcpy(dst, src, size)"); + const builtins = self.builtins orelse return self.emitError("builtins not available"); + const dst = try self.genExpr(args[0]); + const src = try self.genExpr(args[1]); + const size_val = try self.genExpr(args[2]); + const fn_ty = c.LLVMGlobalGetValueType(builtins.memcpy_fn); + var call_args = [_]c.LLVMValueRef{ dst, src, size_val }; + _ = c.LLVMBuildCall2(self.builder, fn_ty, builtins.memcpy_fn, &call_args, 3, ""); + return null; + } + fn genVectorExtract(self: *CodeGen, vec_val: c.LLVMValueRef, field: []const u8) !c.LLVMValueRef { if (field.len == 1) { const idx_val = componentToIndex(field[0]) orelse return self.emitErrorFmt("invalid vector component '{c}'", .{field[0]}); @@ -4034,6 +4125,15 @@ pub const CodeGen = struct { if (std.mem.eql(u8, callee_name, "cast")) { return self.genCast(call_node); } + if (std.mem.eql(u8, callee_name, "malloc")) { + return self.genMalloc(call_node.args); + } + if (std.mem.eql(u8, callee_name, "free")) { + return self.genFree(call_node.args); + } + if (std.mem.eql(u8, callee_name, "memcpy")) { + 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); @@ -4238,25 +4338,62 @@ pub const CodeGen = struct { var bindings = std.StringHashMap(Type).init(self.allocator); for (fd.params, 0..) |param, i| { if (param.is_comptime) continue; - // Direct type param: (a: $T) or (a: T) + // Direct type param: (a: $T) introduces/widens, (a: T) only binds if not yet bound if (param.type_expr.data == .type_expr) { const type_name = param.type_expr.data.type_expr.name; + const is_introducing = param.type_expr.data.type_expr.is_generic; // Check if this type name is a type parameter for (fd.type_params) |tp| { if (std.mem.eql(u8, tp.name, type_name)) { if (i < call_node.args.len) { - const arg_ty = self.inferType(call_node.args[i]); - if (bindings.get(type_name)) |existing| { - // Widen to the broader type to avoid data loss - try bindings.put(type_name, Type.widen(existing, arg_ty)); - } else { - try bindings.put(type_name, arg_ty); + if (is_introducing) { + const arg_ty = self.inferType(call_node.args[i]); + if (bindings.get(type_name)) |existing| { + try bindings.put(type_name, Type.widen(existing, arg_ty)); + } else { + try bindings.put(type_name, arg_ty); + } + } else if (!bindings.contains(type_name)) { + // Plain T reference with no prior binding — infer from arg + try bindings.put(type_name, self.inferType(call_node.args[i])); } } break; } } } + // Pointer to parameterized type: (p: *Foo($T)) — extract T from concrete struct + if (param.type_expr.data == .pointer_type_expr) { + const pointee = param.type_expr.data.pointer_type_expr.pointee_type; + if (pointee.data == .parameterized_type_expr) { + const pte = pointee.data.parameterized_type_expr; + if (i < call_node.args.len) { + const arg_ty = self.inferType(call_node.args[i]); + // arg should be *StructName — get the struct's stored type param bindings + const struct_name = if (arg_ty.isPointer()) + arg_ty.pointer_type.pointee_name + else if (arg_ty.isStruct()) + arg_ty.struct_type + else + ""; + if (self.struct_types.get(struct_name)) |info| { + if (info.template_name) |tmpl_name| { + if (std.mem.eql(u8, tmpl_name, pte.name)) { + // Match generic args against stored type param bindings + for (pte.args, 0..) |arg, ai| { + if (arg.data == .type_expr and arg.data.type_expr.is_generic) { + const gen_name = arg.data.type_expr.name; + if (ai < info.type_param_types.len) { + try bindings.put(gen_name, info.type_param_types[ai]); + } + } + } + } + } + } + } + } + } // Slice type param: (items: []$T) — infer T from array or slice element type if (param.type_expr.data == .slice_type_expr) { const elem_node = param.type_expr.data.slice_type_expr.element_type; @@ -5330,6 +5467,9 @@ pub const CodeGen = struct { if (std.mem.eql(u8, base, "size_of")) return self.genSizeOf(call_node); if (std.mem.eql(u8, base, "cast")) return self.genCast(call_node); if (std.mem.eql(u8, base, "alloc")) return self.genAlloc(call_node.args); + if (std.mem.eql(u8, base, "malloc")) return self.genMalloc(call_node.args); + if (std.mem.eql(u8, base, "free")) return self.genFree(call_node.args); + if (std.mem.eql(u8, base, "memcpy")) return self.genMemcpy(call_node.args); if (std.mem.eql(u8, base, "type_of")) return self.genTypeOf(call_node); if (std.mem.eql(u8, base, "type_name")) return self.genTypeName(call_node); if (std.mem.eql(u8, base, "field_count")) return self.genFieldCount(call_node); @@ -5583,6 +5723,9 @@ pub const CodeGen = struct { } // Built-in: alloc returns string if (std.mem.eql(u8, base_name, "alloc")) return .string_type; + if (std.mem.eql(u8, base_name, "malloc")) return .{ .pointer_type = .{ .pointee_name = "void" } }; + if (std.mem.eql(u8, base_name, "free")) return .void_type; + if (std.mem.eql(u8, base_name, "memcpy")) return .void_type; // Check generic templates — infer return type from widened bindings const template = self.generic_templates.get(callee_name) orelse blk: { // Intra-namespace fallback diff --git a/src/parser.zig b/src/parser.zig index a93f8eb..3bac7b8 100644 --- a/src/parser.zig +++ b/src/parser.zig @@ -547,6 +547,23 @@ pub const Parser = struct { return try params.toOwnedSlice(self.allocator); } + /// Recursively find all generic type names ($T) in a type expression tree. + fn collectGenericNames(node: *Node, list: *std.ArrayList([]const u8), allocator: std.mem.Allocator) void { + switch (node.data) { + .type_expr => |te| { + if (te.is_generic) list.append(allocator, te.name) catch {}; + }, + .pointer_type_expr => |pte| collectGenericNames(pte.pointee_type, list, allocator), + .many_pointer_type_expr => |mpte| collectGenericNames(mpte.element_type, list, allocator), + .slice_type_expr => |ste| collectGenericNames(ste.element_type, list, allocator), + .array_type_expr => |ate| collectGenericNames(ate.element_type, list, allocator), + .parameterized_type_expr => |pte| { + for (pte.args) |arg| collectGenericNames(arg, list, allocator); + }, + else => {}, + } + } + /// Collect generic type params and comptime value params from parameter annotations. fn collectTypeParams(self: *Parser, params: []const ast.Param) ![]const ast.StructTypeParam { var type_params = std.ArrayList(ast.StructTypeParam).empty; @@ -563,25 +580,20 @@ pub const Parser = struct { try type_params.append(self.allocator, .{ .name = param.name, .constraint = param.type_expr }); } } else { - // Check for generic type param: direct $T or nested inside []$T - const generic_type_expr: ?*Node = if (param.type_expr.data == .type_expr and param.type_expr.data.type_expr.is_generic) - param.type_expr - else if (param.type_expr.data == .slice_type_expr) blk: { - const elem = param.type_expr.data.slice_type_expr.element_type; - break :blk if (elem.data == .type_expr and elem.data.type_expr.is_generic) elem else null; - } else null; - - if (generic_type_expr) |gte| { + // Collect all generic type params found anywhere in the type expression + var generic_names = std.ArrayList([]const u8).empty; + collectGenericNames(param.type_expr, &generic_names, self.allocator); + for (generic_names.items) |gen_name| { var found = false; for (type_params.items) |existing| { - if (std.mem.eql(u8, existing.name, gte.data.type_expr.name)) { + if (std.mem.eql(u8, existing.name, gen_name)) { found = true; break; } } if (!found) { - const type_constraint = try self.createNode(param.type_expr.span.start, .{ .type_expr = .{ .name = "Type" } }); - try type_params.append(self.allocator, .{ .name = gte.data.type_expr.name, .constraint = type_constraint }); + 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 {}; } } } diff --git a/src/types.zig b/src/types.zig index 82ec214..551e429 100644 --- a/src/types.zig +++ b/src/types.zig @@ -230,9 +230,10 @@ pub const Type = union(enum) { if (self.isSlice() and target.isSlice()) { return std.mem.eql(u8, self.slice_type.element_name, target.slice_type.element_name); } - // Pointer types: compare pointee names by content, null (*void) → any pointer + // 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; + if (std.mem.eql(u8, target.pointer_type.pointee_name, "void")) return true; return std.mem.eql(u8, self.pointer_type.pointee_name, target.pointer_type.pointee_name); } // Many-pointer types: compare element names by content @@ -240,9 +241,15 @@ pub const Type = union(enum) { return std.mem.eql(u8, self.many_pointer_type.element_name, target.many_pointer_type.element_name); } // *T → [*]T: pointer to element is implicitly convertible to many-pointer + // null (*void) → [*]T is also allowed if (self.isPointer() and target.isManyPointer()) { + if (std.mem.eql(u8, self.pointer_type.pointee_name, "void")) return true; return std.mem.eql(u8, self.pointer_type.pointee_name, target.many_pointer_type.element_name); } + // [*]T → *void: any many-pointer converts to void pointer + if (self.isManyPointer() and target.isPointer()) { + return std.mem.eql(u8, target.pointer_type.pointee_name, "void"); + } const src_float = self.isFloat(); const dst_float = target.isFloat();