From 3fde0800928c768d20ed841e824c377c243df16a Mon Sep 17 00:00:00 2001 From: agra Date: Tue, 10 Feb 2026 18:58:04 +0200 Subject: [PATCH] quick sort --- examples/23-quicksort.sx | 43 +++++++++++ readme.md | 48 ++++++++++++ specs.md | 30 +++++--- src/ast.zig | 5 ++ src/codegen.zig | 162 +++++++++++++++++++++++++++++++++++++-- src/parser.zig | 53 ++++++++++--- src/sema.zig | 11 ++- src/types.zig | 4 + 8 files changed, 326 insertions(+), 30 deletions(-) create mode 100644 examples/23-quicksort.sx diff --git a/examples/23-quicksort.sx b/examples/23-quicksort.sx new file mode 100644 index 0000000..248b522 --- /dev/null +++ b/examples/23-quicksort.sx @@ -0,0 +1,43 @@ +#import "modules/std.sx"; + +quickSort :: (items: []$T) { + partition :: (items: []T, lo: s32, hi: s32) -> s32 { + pivot := items[hi]; + i := lo - 1; + j := lo; + while j < hi { + if items[j] < pivot { + i += 1; + tmp := items[i]; + items[i] = items[j]; + items[j] = tmp; + } + j += 1; + } + i += 1; + tmp := items[i]; + items[i] = items[hi]; + items[hi] = tmp; + i; + } + + sort :: (items: []T, lo: s32, hi: s32) { + if lo < hi { + pi := partition(items, lo, hi); + sort(items, lo, pi - 1); + sort(items, pi + 1, hi); + } + } + + sort(items, 0, items.len - 1); +} + +main :: () { + arr := []s32.[1, 2, 3, 5, 2, 2, 3, 4, 5, 6, 6, 1]; + quickSort(arr); + for arr { + if it_index > 0 { write(", "); } + print("{}", it); + } + write("\n"); +} diff --git a/readme.md b/readme.md index 63e74d8..3d245a0 100644 --- a/readme.md +++ b/readme.md @@ -10,6 +10,54 @@ Q: Can we have an system language to build declarative ui ? NOTE: > i hope you have memory... currently it doesn't free anything :D +## Quick Sort Example + +```sx +#import "modules/std.sx"; + +quickSort :: (items: []$T) { + partition :: (items: []T, lo: s32, hi: s32) -> s32 { + pivot := items[hi]; + i := lo - 1; + j := lo; + while j < hi { + if items[j] < pivot { + i += 1; + tmp := items[i]; + items[i] = items[j]; + items[j] = tmp; + } + j += 1; + } + i += 1; + tmp := items[i]; + items[i] = items[hi]; + items[hi] = tmp; + i; + } + + sort :: (items: []T, lo: s32, hi: s32) { + if lo < hi { + pi := partition(items, lo, hi); + sort(items, lo, pi - 1); + sort(items, pi + 1, hi); + } + } + + sort(items, 0, items.len - 1); +} + +main :: () { + arr := []s32.[1, 2, 3, 5, 2, 2, 3, 4, 5, 6, 6, 1]; + quickSort(arr); + for arr { + if it_index > 0 { write(", "); } + print("{}", it); + } + write("\n"); +} +``` + ## Building Requires **Zig 0.16+** and **LLVM 19**. diff --git a/specs.md b/specs.md index 9a7bfc6..5359584 100644 --- a/specs.md +++ b/specs.md @@ -135,7 +135,7 @@ v1.x = 3.0; // assign to field x of struct v1 #### Struct Interpolation Struct values in string interpolation print as `TypeName{field:value, ...}`: ```sx -print("{v1}"); // Vec4{x:1.0, y:2.0, z:3.0, w:0.0} +print("{}", v1); // Vec4{x:1.0, y:2.0, z:3.0, w:0.0} ``` ### Union Types (Tagged Unions) @@ -174,7 +174,7 @@ if s == { #### Union Interpolation Union values in string interpolation print as ``: ```sx -print("{s}"); // +print("{}", s); // ``` ### Array Types @@ -184,6 +184,22 @@ buffer : [5]f32 = .[0, 2, 3.5, 4, 0]; val := buffer[2]; // 3.5 ``` +### Slice Types +A slice `[]T` is a fat pointer `{ptr, i32}` referencing a contiguous sequence of `T` elements. Same runtime layout as `string`. +```sx +// Arrays implicitly coerce to slices at call sites +arr : [5]s32 = .[3, 1, 4, 1, 5]; +sortSlice(arr); // [5]s32 → []s32 coercion + +// Slice operations +items[i] // read element at index +items[i] = val; // write element at index +items.len // length (s32) +items.ptr // raw pointer +``` + +Slices support generic type parameters: `[]$T` introduces type parameter `T` inferred from the element type of the argument (array or slice). + ### Vector Types (SIMD) LLVM SIMD vectors, parameterized by length and element type. ```sx @@ -575,16 +591,6 @@ Used for module access (`std.print`) and struct member access. ``` The enum type is inferred from context (expected type from declaration or parameter). -### String Interpolation -Curly braces inside string literals interpolate expressions: -```sx -"z: {z}" -``` -The expression inside `{}` is evaluated and formatted according to its type: -- `s32` — decimal integer -- `f64` — decimal float -- `string` — as-is - --- ## 5. Statements diff --git a/src/ast.zig b/src/ast.zig index b1ee246..5247768 100644 --- a/src/ast.zig +++ b/src/ast.zig @@ -45,6 +45,7 @@ pub const Node = struct { import_decl: ImportDecl, namespace_decl: NamespaceDecl, array_type_expr: ArrayTypeExpr, + slice_type_expr: SliceTypeExpr, array_literal: ArrayLiteral, parameterized_type_expr: ParameterizedTypeExpr, index_expr: IndexExpr, @@ -279,6 +280,10 @@ pub const ArrayTypeExpr = struct { element_type: *Node, // type_expr for the element type }; +pub const SliceTypeExpr = struct { + element_type: *Node, // type_expr for the element type +}; + pub const ArrayLiteral = struct { elements: []const *Node, type_expr: ?*Node = null, diff --git a/src/codegen.zig b/src/codegen.zig index 3293d25..8ded5d9 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -81,6 +81,8 @@ pub const CodeGen = struct { // Cache of auto-generated to_string functions for complex types // Variadic function info: maps function name to variadic metadata variadic_functions: std.StringHashMap(VariadicInfo), + // Maps function name to resolved sx parameter types (for accurate type conversion at call sites) + fn_param_types: std.StringHashMap([]const Type), // Enriched Any type entries: maps type name to tag + category + sx type any_type_entries: std.StringHashMap(AnyTypeEntry), // Current match arm type entries (set during category match arm body generation) @@ -173,6 +175,7 @@ pub const CodeGen = struct { .namespaces = std.StringHashMap(void).init(allocator), .builtin_functions = std.StringHashMap(void).init(allocator), .variadic_functions = std.StringHashMap(VariadicInfo).init(allocator), + .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), }; @@ -689,6 +692,13 @@ pub const CodeGen = struct { const elem_name = elem_type.displayName(self.allocator) catch unreachable; return .{ .array_type = .{ .element_name = elem_name, .length = length } }; } + // Slice type: []T + if (tn.data == .slice_type_expr) { + const ste = tn.data.slice_type_expr; + const elem_type = self.resolveType(ste.element_type); + const elem_name = elem_type.displayName(self.allocator) catch unreachable; + return .{ .slice_type = .{ .element_name = elem_name } }; + } // Parameterized type: Vector(N, T) or generic struct instantiation if (tn.data == .parameterized_type_expr) { const pte = tn.data.parameterized_type_expr; @@ -1088,6 +1098,18 @@ pub const CodeGen = struct { const fn_type = try self.buildFnType(fd.params, fd.return_type, fd.name); const name_z = try self.allocator.dupeZ(u8, llvm_name); _ = c.LLVMAddFunction(self.module, name_z.ptr, fn_type); + // Track resolved parameter types for accurate call-site conversion + var param_types = std.ArrayList(Type).empty; + for (fd.params) |param| { + if (param.is_comptime) continue; + if (param.is_variadic) { + const elem_name = if (param.type_expr.data == .type_expr) param.type_expr.data.type_expr.name else "s32"; + try param_types.append(self.allocator, .{ .slice_type = .{ .element_name = elem_name } }); + } else { + try param_types.append(self.allocator, self.resolveType(param.type_expr)); + } + } + try self.fn_param_types.put(llvm_name, try param_types.toOwnedSlice(self.allocator)); // Track variadic function info for call site packing for (fd.params, 0..) |param, i| { if (param.is_variadic) { @@ -2039,7 +2061,28 @@ pub const CodeGen = struct { } } } - return self.emitError("index assignment requires a string or array target"); + if (obj_ty.isSlice()) { + const slice_info = obj_ty.slice_type; + const elem_ty = Type.fromName(slice_info.element_name) orelse return self.emitError("unknown slice element type"); + const elem_llvm_ty = self.typeToLLVM(elem_ty); + // Load slice value to get ptr + const slice_val = blk: { + if (ie.object.data == .identifier) { + if (self.named_values.get(ie.object.data.identifier.name)) |entry| { + break :blk c.LLVMBuildLoad2(self.builder, self.getStringStructType(), entry.ptr, "slice_load"); + } + } + break :blk try self.genExpr(ie.object); + }; + const ptr = c.LLVMBuildExtractValue(self.builder, slice_val, 0, "slice_ptr"); + const idx = try self.genExpr(ie.index); + const val = try self.genExpr(asgn.value); + var gep_indices = [_]c.LLVMValueRef{idx}; + const gep_ptr = c.LLVMBuildGEP2(self.builder, elem_llvm_ty, ptr, &gep_indices, 1, "sliceidx"); + _ = c.LLVMBuildStore(self.builder, val, gep_ptr); + return null; + } + return self.emitError("index assignment requires a string, array, or slice target"); } fn unescapeString(allocator: std.mem.Allocator, raw: []const u8) ![]u8 { @@ -2165,11 +2208,12 @@ pub const CodeGen = struct { return self.genUnionLiteral(ul, null); }, .array_literal => |al| { - // Typed array/vector literal: Type.[elems] + // Typed array/vector/slice literal: Type.[elems] if (al.type_expr) |te| { const ty = self.resolveType(te); if (ty.isVector()) return self.genVectorLiteral(al, ty); if (ty.isArray()) return self.genArrayLiteral(al, ty); + if (ty.isSlice()) return self.genSliceLiteral(al, ty); } // If current return type is vector, build as vector SSA value if (self.current_return_type.isVector()) { @@ -2527,6 +2571,40 @@ pub const CodeGen = struct { return alloca; } + fn genSliceLiteral(self: *CodeGen, al: ast.ArrayLiteral, slice_ty: Type) !c.LLVMValueRef { + const elem_name = slice_ty.slice_type.element_name; + const elem_sx_ty = Type.fromName(elem_name) orelse return self.emitErrorFmt("unknown slice element type '{s}'", .{elem_name}); + const n: u32 = @intCast(al.elements.len); + + // 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"); + + // Fill elements + for (0..n) |i| { + const val = try self.genExprAsType(al.elements[i], elem_sx_ty); + var indices = [_]c.LLVMValueRef{ + c.LLVMConstInt(c.LLVMInt32TypeInContext(self.context), 0, 0), + c.LLVMConstInt(c.LLVMInt32TypeInContext(self.context), @intCast(i), 0), + }; + const gep = c.LLVMBuildGEP2(self.builder, llvm_arr_ty, arr_alloca, &indices, 2, "slice_elem"); + _ = c.LLVMBuildStore(self.builder, val, gep); + } + + // Build slice {ptr, len} + const i32_ty = c.LLVMInt32TypeInContext(self.context); + const zero = c.LLVMConstInt(i32_ty, 0, 0); + var gep_indices = [_]c.LLVMValueRef{ zero, zero }; + const elem_ptr = c.LLVMBuildGEP2(self.builder, llvm_arr_ty, arr_alloca, &gep_indices, 2, "slice_data"); + const slice_llvm_ty = self.getStringStructType(); + var slice_val = c.LLVMGetUndef(slice_llvm_ty); + slice_val = c.LLVMBuildInsertValue(self.builder, slice_val, elem_ptr, 0, "slice_ptr"); + const len_val = c.LLVMConstInt(i32_ty, n, 0); + slice_val = c.LLVMBuildInsertValue(self.builder, slice_val, len_val, 1, "slice_len"); + return slice_val; + } + fn genVectorLiteral(self: *CodeGen, al: ast.ArrayLiteral, vec_ty: Type) !c.LLVMValueRef { const vec_info = vec_ty.vector_type; const elem_sx_ty = Type.fromName(vec_info.element_name) orelse return self.emitErrorFmt("unknown vector element type '{s}'", .{vec_info.element_name}); @@ -2646,6 +2724,41 @@ pub const CodeGen = struct { return self.genVectorLiteral(node.data.array_literal, target_ty); } + // Array literal with target slice type: build stack-backed slice + if (node.data == .array_literal and target_ty.isSlice()) { + return self.genSliceLiteral(node.data.array_literal, target_ty); + } + + // Array to slice coercion: [N]T → []T + if (target_ty.isSlice()) { + const src_ty = self.inferType(node); + if (src_ty.isArray()) { + const arr_info = src_ty.array_type; + // Get the alloca pointer for the array (not the loaded value) + const arr_alloca = blk: { + if (node.data == .identifier) { + if (self.named_values.get(node.data.identifier.name)) |entry| { + break :blk entry.ptr; + } + } + // Fallback: generate the expression and hope it returns a pointer + break :blk try self.genExpr(node); + }; + // GEP to get pointer to first element + const i32_ty = c.LLVMInt32TypeInContext(self.context); + const zero = c.LLVMConstInt(i32_ty, 0, 0); + var indices = [_]c.LLVMValueRef{ zero, zero }; + const elem_ptr = c.LLVMBuildGEP2(self.builder, self.typeToLLVM(src_ty), arr_alloca, &indices, 2, "arr_data"); + // Build slice struct {ptr, len} + const slice_ty = self.getStringStructType(); + var slice_val = c.LLVMGetUndef(slice_ty); + slice_val = c.LLVMBuildInsertValue(self.builder, slice_val, elem_ptr, 0, "slice_ptr"); + const len_val = c.LLVMConstInt(i32_ty, arr_info.length, 0); + slice_val = c.LLVMBuildInsertValue(self.builder, slice_val, len_val, 1, "slice_len"); + return slice_val; + } + } + const val = try self.genExpr(node); const src_ty = self.inferType(node); @@ -3672,10 +3785,20 @@ pub const CodeGen = struct { try arg_vals.append(self.allocator, slice_val); } } else { - // Normal (non-variadic) call + // Normal (non-variadic) call — use stored sx param types when available + const stored_param_types = self.fn_param_types.get(callee_name) orelse blk: { + if (self.current_namespace) |ns| { + const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns, callee_name }); + break :blk self.fn_param_types.get(qualified); + } + break :blk null; + }; for (call_node.args, 0..) |arg, i| { if (i < num_params) { - const param_ty = self.llvmTypeToSxType(param_llvm_types[i]); + const param_ty = if (stored_param_types != null and i < stored_param_types.?.len) + stored_param_types.?[i] + else + self.llvmTypeToSxType(param_llvm_types[i]); try arg_vals.append(self.allocator, try self.genExprAsType(arg, param_ty)); } else { try arg_vals.append(self.allocator, try self.genExpr(arg)); @@ -3754,6 +3877,7 @@ 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) if (param.type_expr.data == .type_expr) { const type_name = param.type_expr.data.type_expr.name; // Check if this type name is a type parameter @@ -3772,6 +3896,32 @@ pub const CodeGen = struct { } } } + // 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; + if (elem_node.data == .type_expr) { + const type_name = elem_node.data.type_expr.name; + 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]); + const elem_ty = if (arg_ty.isArray()) + Type.fromName(arg_ty.array_type.element_name) orelse arg_ty + else if (arg_ty.isSlice()) + Type.fromName(arg_ty.slice_type.element_name) orelse arg_ty + else + arg_ty; + if (bindings.get(type_name)) |existing| { + try bindings.put(type_name, Type.widen(existing, elem_ty)); + } else { + try bindings.put(type_name, elem_ty); + } + } + break; + } + } + } + } } if (has_comptime_values) { @@ -3802,13 +3952,15 @@ pub const CodeGen = struct { const args_slice = try arg_vals.toOwnedSlice(self.allocator); const fn_type = c.LLVMGlobalGetValueType(callee_fn); + const ret_ty = c.LLVMGetReturnType(fn_type); + const call_name: [*c]const u8 = if (ret_ty == c.LLVMVoidTypeInContext(self.context)) "" else "calltmp"; return c.LLVMBuildCall2( self.builder, fn_type, callee_fn, if (args_slice.len > 0) args_slice.ptr else null, @intCast(args_slice.len), - "calltmp", + call_name, ); } diff --git a/src/parser.zig b/src/parser.zig index 5a35ff6..44f0f68 100644 --- a/src/parser.zig +++ b/src/parser.zig @@ -206,9 +206,15 @@ pub const Parser = struct { fn parseTypeExpr(self: *Parser) anyerror!*Node { const start = self.current.loc.start; - // Array type: [N]T + // Array type: [N]T or Slice type: []T if (self.current.tag == .l_bracket) { self.advance(); // skip '[' + if (self.current.tag == .r_bracket) { + // Slice type: []T + self.advance(); // skip ']' + const elem_type = try self.parseTypeExpr(); + return try self.createNode(start, .{ .slice_type_expr = .{ .element_type = elem_type } }); + } const len_node = try self.parseExpr(); try self.expect(.r_bracket); const elem_type = try self.parseTypeExpr(); @@ -231,11 +237,20 @@ pub const Parser = struct { // Qualified name: ns.Type or ns.Type(args) while (self.current.tag == .dot) { + const dot_lexer = self.lexer; + const dot_current = self.current; + const dot_prev_end = self.prev_end; self.advance(); if (self.current.tag == .identifier or self.current.tag.isTypeKeyword()) { name = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ name, self.tokenSlice(self.current) }); self.advance(); - } else break; + } else { + // Not a qualified name continuation — restore the dot + self.lexer = dot_lexer; + self.current = dot_current; + self.prev_end = dot_prev_end; + break; + } } // Parameterized type: Vector(N, T) or later generic struct instantiation @@ -533,17 +548,27 @@ pub const Parser = struct { if (!found) { try type_params.append(self.allocator, .{ .name = param.name, .constraint = param.type_expr }); } - } else if (param.type_expr.data == .type_expr and param.type_expr.data.type_expr.is_generic) { - var found = false; - for (type_params.items) |existing| { - if (std.mem.eql(u8, existing.name, param.type_expr.data.type_expr.name)) { - found = true; - break; + } 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| { + var found = false; + for (type_params.items) |existing| { + if (std.mem.eql(u8, existing.name, gte.data.type_expr.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 }); } - } - if (!found) { - const type_constraint = try self.createNode(param.type_expr.span.start, .{ .type_expr = .{ .name = "Type" } }); - try type_params.append(self.allocator, .{ .name = param.type_expr.data.type_expr.name, .constraint = type_constraint }); } } } @@ -1034,6 +1059,10 @@ pub const Parser = struct { null; return try self.createNode(start, .{ .return_stmt = .{ .value = value } }); }, + .l_bracket => { + // Type expression in expression position: []T.[...] or [N]T.[...] + return try self.parseTypeExpr(); + }, .l_brace => { return self.parseBlock(); }, diff --git a/src/sema.zig b/src/sema.zig index 72ee22d..2bb9bea 100644 --- a/src/sema.zig +++ b/src/sema.zig @@ -224,6 +224,13 @@ pub const Analyzer = struct { const elem_name = elem_type.displayName(self.allocator) catch return .void_type; return .{ .array_type = .{ .element_name = elem_name, .length = length } }; } + // Slice type: []T + if (tn.data == .slice_type_expr) { + const ste = tn.data.slice_type_expr; + const elem_type = self.resolveTypeNode(ste.element_type); + const elem_name = elem_type.displayName(self.allocator) catch return .void_type; + return .{ .slice_type = .{ .element_name = elem_name } }; + } // Parameterized type: Vector(N, T) or generic struct if (tn.data == .parameterized_type_expr) { // For now, skip generic instantiation — just return void_type @@ -406,7 +413,7 @@ pub const Analyzer = struct { try self.analyzeNode(val); } }, - .enum_decl, .struct_decl, .union_decl, .array_type_expr, .array_literal, .parameterized_type_expr, .index_expr, .insert_expr => {}, + .enum_decl, .struct_decl, .union_decl, .array_type_expr, .slice_type_expr, .array_literal, .parameterized_type_expr, .index_expr, .insert_expr => {}, .namespace_decl => |ns| { try self.pushScope(); for (ns.decls) |d| { @@ -630,6 +637,7 @@ pub const Analyzer = struct { .builtin_expr, .import_decl, .array_type_expr, + .slice_type_expr, .array_literal, .parameterized_type_expr, .index_expr, @@ -876,6 +884,7 @@ pub fn findNodeAtOffset(node: *Node, offset: u32) ?*Node { .union_decl, .import_decl, .array_type_expr, + .slice_type_expr, .array_literal, .parameterized_type_expr, .index_expr, diff --git a/src/types.zig b/src/types.zig index e06aeda..4434c21 100644 --- a/src/types.zig +++ b/src/types.zig @@ -180,6 +180,10 @@ 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; + // 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); + } const src_float = self.isFloat(); const dst_float = target.isFloat();