From 2f5eb84259c53f43ac6c2ae48f64889f30fc1cb2 Mon Sep 17 00:00:00 2001 From: agra Date: Wed, 18 Feb 2026 13:08:35 +0200 Subject: [PATCH] ... --- examples/32-http-server.sx | 2 +- examples/50-smoke.sx | 78 ++++++++++++++++ src/codegen.zig | 178 +++++++++++++++++++++++++++++++++--- tests/expected/50-smoke.txt | 24 ++++- 4 files changed, 267 insertions(+), 15 deletions(-) diff --git a/examples/32-http-server.sx b/examples/32-http-server.sx index a78aa75..bc9f6c8 100644 --- a/examples/32-http-server.sx +++ b/examples/32-http-server.sx @@ -17,7 +17,7 @@ main :: () -> s32 { addr := SockAddr.{ sin_len = 16, sin_family = 2, sin_port = htons(PORT) }; - if bind(fd, addr, 16) < 0 { + if bind(fd, @addr, 16) < 0 { print("error: bind()\n"); return 1; } diff --git a/examples/50-smoke.sx b/examples/50-smoke.sx index fc139cd..5eaa864 100644 --- a/examples/50-smoke.sx +++ b/examples/50-smoke.sx @@ -78,6 +78,8 @@ vec3 :: (x: f32, y: f32, z: f32) -> Vector(3, f32) { .[x, y, z]; } +point_sum :: (p: Point) -> s32 { p.x + p.y; } + // #run compile-time constants CT_VAL :: #run add(10, 15); CT_MUL :: #run mul(6, 7); @@ -1106,5 +1108,81 @@ END; print("local-enum: {}\n", ls); } + // ======================================================== + // 20. UFCS RETURN TYPE INFERENCE + // ======================================================== + print("=== 20. UFCS Return Type ===\n"); + { + p := Point.{3, 4}; + print("direct: {}\n", point_sum(p)); + print("ufcs: {}\n", p.point_sum()); + } + + // ======================================================== + // 21. TYPE-NAMED VARIABLES (s2, u8, etc.) + // ======================================================== + print("=== 21. Type-Named Vars ===\n"); + { + s2 := 42; + print("s2: {}\n", s2); + s2 = s2 + 1; + print("s2+1: {}\n", s2); + } + + // ======================================================== + // 22. IF-EXPRESSION RETURNING STRUCT + // ======================================================== + print("=== 22. If-Struct ===\n"); + { + flag := true; + p := if flag { Point.{10, 20}; } else { Point.{30, 40}; }; + print("if-struct: {} {}\n", p.x, p.y); + q := if !flag { Point.{10, 20}; } else { Point.{30, 40}; }; + print("else-struct: {} {}\n", q.x, q.y); + } + + // ======================================================== + // 23. NESTED ARRAYS (2D) + // ======================================================== + print("=== 23. Nested Arrays ===\n"); + { + matrix : [2][3]s32 = .[ .[1, 2, 3], .[4, 5, 6] ]; + print("m[0][0]: {}\n", matrix[0][0]); + print("m[0][2]: {}\n", matrix[0][2]); + print("m[1][0]: {}\n", matrix[1][0]); + print("m[1][2]: {}\n", matrix[1][2]); + } + + // ======================================================== + // 24. STRING COMPARISON + // ======================================================== + print("=== 24. String Comparison ===\n"); + { + a := "hello"; + b := "hello"; + c := "world"; + print("str-eq: {}\n", a == b); + print("str-neq: {}\n", a != c); + print("str-diff: {}\n", a == c); + empty := ""; + print("empty-eq: {}\n", empty == ""); + } + + // ======================================================== + // 25. ARRAY LOOP MUTATION + // ======================================================== + print("=== 25. Array Loop Mutation ===\n"); + { + arr : [4]s32 = .[0, 0, 0, 0]; + i := 0; + while i < 4 { + arr[i] = xx (i + 1); + i += 1; + } + print("loop-fill: {} {} {} {}\n", arr[0], arr[1], arr[2], arr[3]); + arr[2] += 10; + print("compound: {}\n", arr[2]); + } + print("=== DONE ===\n"); } diff --git a/src/codegen.zig b/src/codegen.zig index 8bbea79..57457e5 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -2673,9 +2673,16 @@ pub const CodeGen = struct { const elem_llvm_ty = self.typeToLLVM(elem_sx_ty); const len = @min(al.elements.len, arr_info.length); for (0..len) |i| { - const val = try self.genExprAsType(al.elements[i], elem_sx_ty); + const elem_node = al.elements[i]; + const val = try self.genExprAsType(elem_node, elem_sx_ty); const gep = self.gepArrayElement(llvm_arr_ty, arr_alloca, self.constInt32(@intCast(i)), "arr_elem"); - _ = c.LLVMBuildStore(self.builder, val, gep); + // Array literals return allocas via genArrayLiteral — load value before storing + if (elem_node.data == .array_literal) { + const loaded = c.LLVMBuildLoad2(self.builder, elem_llvm_ty, val, "agg_load"); + _ = c.LLVMBuildStore(self.builder, loaded, gep); + } else { + _ = c.LLVMBuildStore(self.builder, val, gep); + } } // Zero-init remaining elements for (len..arr_info.length) |i| { @@ -2917,9 +2924,13 @@ pub const CodeGen = struct { return null; } - // Target must be an identifier - if (asgn.target.data != .identifier) return self.emitError("assignment target must be a variable"); - const name = asgn.target.data.identifier.name; + // Target must be an identifier (or type_expr for names like s2, u8 that match type patterns) + const name = if (asgn.target.data == .identifier) + asgn.target.data.identifier.name + else if (asgn.target.data == .type_expr) + asgn.target.data.type_expr.name + else + return self.emitError("assignment target must be a variable"); const lookup = self.lookupValue(name) orelse return self.emitErrorFmt("undefined variable '{s}'", .{name}); const entry = lookup.asNamedValue() orelse @@ -3127,12 +3138,14 @@ pub const CodeGen = struct { return null; } if (obj_ty.isArray()) { + const arr_info = obj_ty.array_type; + const elem_ty = self.resolveTypeFromName(arr_info.element_name) orelse return self.emitError("unknown array element type"); if (ie.object.data == .identifier) { if (self.named_values.get(ie.object.data.identifier.name)) |entry| { const idx = try self.genExpr(ie.index); - const val = try self.genExpr(asgn.value); + const val = try self.genExprAsType(asgn.value, elem_ty); const gep_ptr = self.gepArrayElement(self.typeToLLVM(obj_ty), entry.ptr, idx, "arridx"); - _ = c.LLVMBuildStore(self.builder, val, gep_ptr); + self.storeOrCompound(asgn.op, gep_ptr, val, elem_ty, "arrcur"); return null; } } @@ -3140,9 +3153,9 @@ pub const CodeGen = struct { if (ie.object.data == .field_access) { const field_ptr = try self.genAddressOf(ie.object); const idx = try self.genExpr(ie.index); - const val = try self.genExpr(asgn.value); + const val = try self.genExprAsType(asgn.value, elem_ty); const gep_ptr = self.gepArrayElement(self.typeToLLVM(obj_ty), field_ptr, idx, "field_arridx"); - _ = c.LLVMBuildStore(self.builder, val, gep_ptr); + self.storeOrCompound(asgn.op, gep_ptr, val, elem_ty, "farrcur"); return null; } } @@ -3262,6 +3275,13 @@ pub const CodeGen = struct { return self.icmp(pred, lhs_tag, rhs_tag, "tag_cmp"); } + // String comparison: compare lengths, then memcmp content + if ((result_type == .string_type or result_type.isSlice()) and (binop.op == .eq or binop.op == .neq)) { + const lhs = try self.genExpr(binop.lhs); + const rhs = try self.genExpr(binop.rhs); + return self.genStringComparison(binop.op, lhs, rhs); + } + const lhs = try self.genExprAsType(binop.lhs, result_type); const rhs = try self.genExprAsType(binop.rhs, result_type); return self.genBinaryOp(binop.op, lhs, rhs, result_type); @@ -3413,6 +3433,21 @@ pub const CodeGen = struct { .comptime_expr => |ct| { return self.genExpr(ct.expr); }, + .type_expr => |te| { + // type_expr can appear when a variable name matches a type (e.g. s2, u8) + // Fall back to identifier behavior: check named_values first + if (self.lookupValue(te.name)) |v| { + switch (v) { + .local => |nv| return c.LLVMBuildLoad2(self.builder, self.typeToLLVM(nv.ty), nv.ptr, "loadtmp"), + .comptime_global => |ct| { + if (!ct.is_resolved) try self.resolveComptimeGlobal(ct); + return c.LLVMBuildLoad2(self.builder, self.typeToLLVM(ct.ty), ct.global, "ct_load"); + }, + .global_mutable => |gm| return c.LLVMBuildLoad2(self.builder, self.typeToLLVM(gm.ty), gm.ptr, "global_load"), + } + } + return self.emitErrorFmt("type '{s}' used as expression", .{te.name}); + }, else => return self.emitError("unsupported expression"), } } @@ -3969,9 +4004,17 @@ pub const CodeGen = struct { const len = @min(al.elements.len, arr_info.length); for (0..len) |i| { - const val = try self.genExprAsType(al.elements[i], elem_sx_ty); + const elem_node = al.elements[i]; + const val = try self.genExprAsType(elem_node, elem_sx_ty); const gep = self.gepArrayElement(llvm_arr_ty, alloca, self.constInt32(@intCast(i)), "arr_elem"); - _ = c.LLVMBuildStore(self.builder, val, gep); + // Array literals return allocas via genArrayLiteral — load value before storing + if (elem_node.data == .array_literal) { + const elem_llvm_ty = self.typeToLLVM(elem_sx_ty); + const loaded = c.LLVMBuildLoad2(self.builder, elem_llvm_ty, val, "agg_load"); + _ = c.LLVMBuildStore(self.builder, loaded, gep); + } else { + _ = c.LLVMBuildStore(self.builder, val, gep); + } } return alloca; } @@ -5071,6 +5114,31 @@ pub const CodeGen = struct { if (obj_ty == .string_type) { return self.extractFatPtrField(obj_val, fa.field, "string"); } + if (obj_ty.isSlice()) { + return self.extractFatPtrField(obj_val, fa.field, "slice"); + } + if (obj_ty.isStruct()) { + const sname = obj_ty.struct_type; + const info = try self.getStructInfo(sname); + const idx = try self.findFieldIndex(info.field_names, fa.field, sname); + // Store the struct value to a temp alloca, then GEP to load the field + const tmp = self.buildEntryBlockAlloca(info.llvm_type, "tmp_struct"); + _ = c.LLVMBuildStore(self.builder, obj_val, tmp); + return self.loadStructField(info.llvm_type, tmp, @intCast(idx), self.typeToLLVM(info.field_types[idx])); + } + if (obj_ty.isUnion()) { + if (self.lookupTaggedEnumInfo(obj_ty.union_type)) |info| { + for (info.variant_names, 0..) |vn, i| { + if (std.mem.eql(u8, vn, fa.field)) { + const variant_ty = info.variant_types[i]; + if (variant_ty == .void_type) return self.emitErrorFmt("cannot access payload of void variant '{s}'", .{fa.field}); + const tmp = self.buildEntryBlockAlloca(info.llvm_type, "tmp_enum"); + _ = c.LLVMBuildStore(self.builder, obj_val, tmp); + return self.loadStructField(info.llvm_type, tmp, info.payload_field_index, self.typeToLLVM(variant_ty)); + } + } + } + } return self.emitError("field access on non-struct/non-vector expression"); } @@ -5117,6 +5185,16 @@ pub const CodeGen = struct { const gep = self.gepArrayElement(self.typeToLLVM(obj_ty), field_ptr, idx, "field_arridx"); return self.loadTyped(elem_ty, gep, "field_arrval"); } + // General fallback: store value to temp alloca and GEP into it + { + const arr_val = try self.genExpr(ie.object); + const arr_llvm_ty = self.typeToLLVM(obj_ty); + const tmp = self.buildEntryBlockAlloca(arr_llvm_ty, "arr_tmp"); + _ = c.LLVMBuildStore(self.builder, arr_val, tmp); + const idx = try self.genExpr(ie.index); + const gep = self.gepArrayElement(arr_llvm_ty, tmp, idx, "gen_arridx"); + return self.loadTyped(elem_ty, gep, "gen_arrval"); + } } if (obj_ty == .string_type) { // String indexing: extract ptr from slice, GEP + load u8 @@ -5255,6 +5333,55 @@ pub const CodeGen = struct { }; } + fn genStringComparison(self: *CodeGen, op: ast.BinaryOp.Op, lhs: c.LLVMValueRef, rhs: c.LLVMValueRef) !c.LLVMValueRef { + const b = self.builder; + const i64_ty = self.i64Type(); + const i32_ty = self.i32Type(); + const i1_ty = self.i1Type(); + const ptr_ty = self.ptrType(); + + // Extract ptr and len from both fat pointers + const lhs_ptr = self.extractValue(lhs, 0, "lptr"); + const lhs_len = self.extractValue(lhs, 1, "llen"); + const rhs_ptr = self.extractValue(rhs, 0, "rptr"); + const rhs_len = self.extractValue(rhs, 1, "rlen"); + + // Compare lengths first + const len_eq = c.LLVMBuildICmp(b, c.LLVMIntEQ, lhs_len, rhs_len, "len_eq"); + + // Set up basic blocks for conditional memcmp + const cur_bb = self.getCurrentBlock(); + const memcmp_bb = self.appendBB("str.memcmp"); + const merge_bb = self.appendBB("str.merge"); + + _ = c.LLVMBuildCondBr(b, len_eq, memcmp_bb, merge_bb); + + // memcmp block: lengths match, compare content + c.LLVMPositionBuilderAtEnd(b, memcmp_bb); + const memcmp_fn = c.LLVMGetNamedFunction(self.module, "memcmp") orelse blk: { + var params = [_]c.LLVMTypeRef{ ptr_ty, ptr_ty, i64_ty }; + const fn_type = c.LLVMFunctionType(i32_ty, ¶ms, 3, 0); + break :blk c.LLVMAddFunction(self.module, "memcmp", fn_type); + }; + var args = [_]c.LLVMValueRef{ lhs_ptr, rhs_ptr, lhs_len }; + const cmp_result = c.LLVMBuildCall2(b, c.LLVMGlobalGetValueType(memcmp_fn), memcmp_fn, &args, 3, "memcmp"); + const content_eq = c.LLVMBuildICmp(b, c.LLVMIntEQ, cmp_result, c.LLVMConstInt(i32_ty, 0, 0), "content_eq"); + _ = c.LLVMBuildBr(b, merge_bb); + + // Merge: phi(len_mismatch=false, memcmp_result) + c.LLVMPositionBuilderAtEnd(b, merge_bb); + const phi = c.LLVMBuildPhi(b, i1_ty, "str_eq"); + const false_val = c.LLVMConstInt(i1_ty, 0, 0); + var phi_vals = [_]c.LLVMValueRef{ false_val, content_eq }; + var phi_bbs = [_]c.LLVMBasicBlockRef{ cur_bb, memcmp_bb }; + c.LLVMAddIncoming(phi, &phi_vals, &phi_bbs, 2); + + if (op == .neq) { + return c.LLVMBuildNot(b, phi, "str_neq"); + } + return phi; + } + fn genShortCircuitOp(self: *CodeGen, binop: ast.BinaryOp, is_and: bool) !c.LLVMValueRef { const lhs_val = self.valueToBool(try self.genExpr(binop.lhs)); const lhs_bb = self.getCurrentBlock(); @@ -6585,8 +6712,8 @@ pub const CodeGen = struct { null; const i64_type = self.i64Type(); - // Enum/union case constants use the backing type; Any dispatch uses i64 - const case_int_type = if (enum_name) |en| self.getEnumLLVMType(en) else if (union_name) |un| self.getEnumLLVMType(un) else i64_type; + // Enum/union case constants use the backing type; bool uses i1; others use i64 + const case_int_type = if (enum_name) |en| self.getEnumLLVMType(en) else if (union_name) |un| self.getEnumLLVMType(un) else if (subject_ty == .boolean) self.i1Type() else i64_type; const merge_bb = self.appendBB("match_end"); // Create case basic blocks @@ -6838,6 +6965,13 @@ pub const CodeGen = struct { return std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ fa.object.data.identifier.name, fa.field }) catch return null; } } + // UFCS: obj.method(args) → method is the callee name + const method_z = self.allocator.dupeZ(u8, fa.field) catch return null; + if (self.generic_templates.contains(fa.field) or + c.LLVMGetNamedFunction(self.module, method_z.ptr) != null) + { + return fa.field; + } } return null; } @@ -7037,6 +7171,15 @@ pub const CodeGen = struct { }; }, }; + // Array display name: "[N]T" + if (name.len > 2 and name[0] == '[') { + if (std.mem.indexOfScalar(u8, name[1..], ']')) |close| { + const n_str = name[1 .. 1 + close]; + const elem = name[2 + close ..]; + const length = std.fmt.parseInt(u32, n_str, 10) catch return null; + return .{ .array_type = .{ .element_name = elem, .length = length } }; + } + } // Vector display name: "Vector(N,T)" if (name.len > 8 and std.mem.startsWith(u8, name, "Vector(") and name[name.len - 1] == ')') { const inner = name[7 .. name.len - 1]; // "N,T" @@ -7075,6 +7218,11 @@ pub const CodeGen = struct { if (self.lookupValue(ident.name)) |v| return v.ty(); return Type.s(64); }, + .type_expr => |te| { + // type_expr can appear when a variable name matches a type (e.g. s2, u8) + if (self.lookupValue(te.name)) |v| return v.ty(); + return Type.s(64); + }, .if_expr => |ie| { return self.inferType(ie.then_branch); }, @@ -7317,6 +7465,10 @@ pub const CodeGen = struct { const elem_name = elem_ty.displayName(self.allocator) catch return Type.s(64); return .{ .array_type = .{ .element_name = elem_name, .length = @intCast(al.elements.len) } }; }, + .struct_literal => |sl| { + if (sl.struct_name) |sname| return .{ .struct_type = sname }; + return Type.s(64); + }, .while_expr, .for_expr, .break_expr, .continue_expr => .void_type, else => Type.s(64), }; diff --git a/tests/expected/50-smoke.txt b/tests/expected/50-smoke.txt index d889976..3e458f6 100644 --- a/tests/expected/50-smoke.txt +++ b/tests/expected/50-smoke.txt @@ -100,7 +100,7 @@ promoted-x: 1.000000 promoted-data0: 1.000000 arr[2]: 30 arr.len: 5 -arr-assign: [1, 99, 0] +arr-assign: [1, 99, 3] sl[0]: 1 sl.len: 5 sl-assign: [10, 55, 0] @@ -258,4 +258,26 @@ for-struct: Point{x: 3, y: 4} === 19. Local Fn Return === local-struct: 42 99 local-enum: .circle(2.500000) +=== 20. UFCS Return Type === +direct: 7 +ufcs: 7 +=== 21. Type-Named Vars === +s2: 42 +s2+1: 43 +=== 22. If-Struct === +if-struct: 10 20 +else-struct: 30 40 +=== 23. Nested Arrays === +m[0][0]: 1 +m[0][2]: 3 +m[1][0]: 4 +m[1][2]: 6 +=== 24. String Comparison === +str-eq: true +str-neq: true +str-diff: false +empty-eq: true +=== 25. Array Loop Mutation === +loop-fill: 1 2 3 4 +compound: 13 === DONE ===