From bba26ca0d76305a5d1e79a47759d5c223567335c Mon Sep 17 00:00:00 2001 From: agra Date: Tue, 10 Feb 2026 20:23:37 +0200 Subject: [PATCH] slice print --- examples/23-quicksort.sx | 8 +-- examples/modules/std.sx | 12 ++++ src/codegen.zig | 117 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 129 insertions(+), 8 deletions(-) diff --git a/examples/23-quicksort.sx b/examples/23-quicksort.sx index 248b522..bc4ef9d 100644 --- a/examples/23-quicksort.sx +++ b/examples/23-quicksort.sx @@ -33,11 +33,7 @@ quickSort :: (items: []$T) { } main :: () { - arr := []s32.[1, 2, 3, 5, 2, 2, 3, 4, 5, 6, 6, 1]; + arr : []s32 = .[333, 2, 3, 5, 2, 2, 3, 4, 5, 6, 6, 1]; quickSort(arr); - for arr { - if it_index > 0 { write(", "); } - print("{}", it); - } - write("\n"); + print("{}\n", arr); } diff --git a/examples/modules/std.sx b/examples/modules/std.sx index 79e729c..dd65f37 100644 --- a/examples/modules/std.sx +++ b/examples/modules/std.sx @@ -119,6 +119,17 @@ array_to_string :: (a: $T) -> string { concat(result, "]"); } +slice_to_string :: (items: []$T) -> string { + result := "["; + i := 0; + while i < items.len { + if i > 0 { result = concat(result, ", "); } + result = concat(result, any_to_string(field_value(items, i))); + i += 1; + } + concat(result, "]"); +} + union_to_string :: (u: $T) -> string { tag := cast(s32) u; result := concat(".", field_name(T, tag)); @@ -143,6 +154,7 @@ any_to_string :: (val: Any) -> string { case enum: result = enum_to_string(cast(type) val); case vector: result = vector_to_string(cast(type) val); case array: result = array_to_string(cast(type) val); + case slice: result = slice_to_string(cast(type) val); case union: result = union_to_string(cast(type) val); } result; diff --git a/src/codegen.zig b/src/codegen.zig index 8ded5d9..94058e2 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -87,6 +87,13 @@ pub const CodeGen = struct { any_type_entries: std.StringHashMap(AnyTypeEntry), // Current match arm type entries (set during category match arm body generation) 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), + + const DeferredFn = struct { + fd: ast.FnDecl, + name: []const u8, // qualified name (may differ from fd.name for namespaced functions) + }; const TypeCategory = enum { struct_cat, @@ -178,6 +185,7 @@ pub const CodeGen = struct { .fn_param_types = std.StringHashMap([]const Type).init(allocator), .any_type_id_map = std.StringHashMap(u64).init(allocator), .any_type_entries = std.StringHashMap(AnyTypeEntry).init(allocator), + .deferred_fn_bodies = std.ArrayList(DeferredFn).empty, }; } @@ -196,6 +204,7 @@ pub const CodeGen = struct { self.variadic_functions.deinit(); self.any_type_id_map.deinit(); self.any_type_entries.deinit(); + self.deferred_fn_bodies.deinit(self.allocator); c.LLVMDisposeBuilder(self.builder); c.LLVMDisposeModule(self.module); c.LLVMContextDispose(self.context); @@ -286,6 +295,34 @@ pub const CodeGen = struct { return gop.value_ptr.*; } + /// Check if a function should have its body compilation deferred until after all types are registered. + /// Functions with non-variadic Any parameters (like any_to_string) use type-based match expressions + /// that need all types registered before compilation. + fn shouldDeferFnBody(fd: ast.FnDecl) bool { + for (fd.params) |param| { + if (!param.is_variadic and param.type_expr.data == .type_expr and + std.mem.eql(u8, param.type_expr.data.type_expr.name, "Any")) + { + return true; + } + } + return false; + } + + /// Pre-register a type in the Any type system so category matching (case slice:, case array:, etc.) + /// 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), + .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), + .array_type => |info| _ = try self.getAnyTypeId(try std.fmt.allocPrint(self.allocator, "[{d}]{s}", .{ info.length, info.element_name }), sx_type), + .slice_type => |info| _ = try self.getAnyTypeId(try std.fmt.allocPrint(self.allocator, "[]{s}", .{info.element_name}), sx_type), + else => {}, + } + } + /// 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). @@ -309,6 +346,7 @@ pub const CodeGen = struct { .union_type => |name| try self.getAnyTypeId(name, ty), .vector_type => |info| try self.getAnyTypeId(try std.fmt.allocPrint(self.allocator, "vec[{d}]{s}", .{ info.length, info.element_name }), ty), .array_type => |info| try self.getAnyTypeId(try std.fmt.allocPrint(self.allocator, "[{d}]{s}", .{ info.length, info.element_name }), ty), + .slice_type => |info| try self.getAnyTypeId(try std.fmt.allocPrint(self.allocator, "[]{s}", .{info.element_name}), ty), .meta_type => ANY_TAG_TYPE, else => ANY_TAG_S32, }; @@ -365,6 +403,12 @@ pub const CodeGen = struct { _ = 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"); + _ = c.LLVMBuildStore(self.builder, val, alloca); + break :blk c.LLVMBuildPtrToInt(self.builder, alloca, i64_ty, "any_slice"); + }, .meta_type => blk: { // Meta type is a pointer (global string) — convert via ptrtoint break :blk c.LLVMBuildPtrToInt(self.builder, val, i64_ty, "any_type"); @@ -558,13 +602,19 @@ pub const CodeGen = struct { } // 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. for (root.data.root.decls) |decl| { switch (decl.data) { .fn_decl => |fd| { if (fd.body.data == .builtin_expr) { // skip } else if (fd.type_params.len == 0) { - try self.genFnBody(fd); + if (shouldDeferFnBody(fd)) { + try self.deferred_fn_bodies.append(self.allocator, .{ .fd = fd, .name = fd.name }); + } else { + try self.genFnBody(fd); + } } }, .const_decl => |cd| { @@ -579,6 +629,11 @@ pub const CodeGen = struct { } } + // Pass 3: Compile deferred function bodies (after all types are registered) + for (self.deferred_fn_bodies.items) |deferred| { + try self.genFnBodyAs(deferred.fd, deferred.name); + } + // Execute comptime side effects via bytecode VM (e.g., #run main();) for (self.comptime_side_effects.items) |expr| { _ = try self.comptimeEval(expr, .void_type); @@ -1174,7 +1229,11 @@ pub const CodeGen = struct { // skip } else if (fd.type_params.len == 0) { const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, fd.name }); - try self.genFnBodyAs(fd, qualified); + if (shouldDeferFnBody(fd)) { + try self.deferred_fn_bodies.append(self.allocator, .{ .fd = fd, .name = qualified }); + } else { + try self.genFnBodyAs(fd, qualified); + } } }, .const_decl => |cd| { @@ -3145,6 +3204,21 @@ pub const CodeGen = struct { return phi; } + // Slice: extract ptr, GEP to element, load, box as Any + if (val_ty.isSlice()) { + const sinfo = val_ty.slice_type; + const elem_ty = Type.fromName(sinfo.element_name) orelse + return self.emitErrorFmt("unknown slice element type '{s}'", .{sinfo.element_name}); + const elem_llvm_ty = self.typeToLLVM(elem_ty); + // val is {ptr, i32} — extract ptr + const data_ptr = c.LLVMBuildExtractValue(self.builder, val, 0, "fv_sdata"); + const idx = try self.genExpr(call_node.args[1]); + var gep_indices = [_]c.LLVMValueRef{idx}; + const elem_ptr = c.LLVMBuildGEP2(self.builder, elem_llvm_ty, data_ptr, &gep_indices, 1, "fv_selem"); + const elem = c.LLVMBuildLoad2(self.builder, elem_llvm_ty, elem_ptr, "fv_seval"); + return self.buildAnyValue(elem, elem_ty); + } + // Array: GEP + load + box as Any if (val_ty.isArray()) { const ainfo = val_ty.array_type; @@ -4020,6 +4094,22 @@ pub const CodeGen = struct { } defer self.current_namespace = saved_namespace; + // Pre-register Any type IDs for variadic args before function instantiation, + // so type category matching (case slice:, case array:, etc.) in any_to_string + // can find registered types during compilation of the function body. + for (fd.params, 0..) |param, pi| { + if (param.is_variadic) { + const elem_name_raw = if (param.type_expr.data == .type_expr) param.type_expr.data.type_expr.name else ""; + if (std.mem.eql(u8, elem_name_raw, "Any") or std.mem.eql(u8, elem_name_raw, "std.Any")) { + for (pi..call_node.args.len) |ai| { + const arg_ty = self.inferType(call_node.args[ai]); + try self.preRegisterAnyType(arg_ty); + } + } + break; + } + } + _ = try self.instantiateGeneric(fd, type_bindings, mangled); // Register variadic info for the mangled function (adjusted for removed comptime params) @@ -4145,6 +4235,25 @@ pub const CodeGen = struct { break; } } + } else if (fd.params[cast_arg_idx].type_expr.data == .slice_type_expr) { + // Slice type param: (items: []$T) — extract element type from concrete slice + const elem_node = fd.params[cast_arg_idx].type_expr.data.slice_type_expr.element_type; + if (elem_node.data == .type_expr) { + const tp_name = elem_node.data.type_expr.name; + for (fd.type_params) |tp| { + if (std.mem.eql(u8, tp.name, tp_name)) { + // Extract element type from concrete slice type + const elem_ty = if (sx_type.isSlice()) + Type.fromName(sx_type.slice_type.element_name) orelse sx_type + else if (sx_type.isArray()) + Type.fromName(sx_type.array_type.element_name) orelse sx_type + else + sx_type; + try bindings.put(tp.name, elem_ty); + break; + } + } + } } } @@ -4247,6 +4356,10 @@ pub const CodeGen = struct { const ptr = c.LLVMBuildIntToPtr(self.builder, any_i64, c.LLVMPointerTypeInContext(self.context, 0), "any_to_vec_ptr"); break :blk c.LLVMBuildLoad2(self.builder, llvm_ty, ptr, "any_to_vec"); }, + .slice_type => blk: { + const ptr = c.LLVMBuildIntToPtr(self.builder, any_i64, c.LLVMPointerTypeInContext(self.context, 0), "any_to_slice_ptr"); + break :blk c.LLVMBuildLoad2(self.builder, self.getStringStructType(), ptr, "any_to_slice"); + }, else => c.LLVMBuildTrunc(self.builder, any_i64, c.LLVMInt32TypeInContext(self.context), "any_to_default"), }; }