diff --git a/examples/28-sdl-graphics.sx b/examples/28-sdl-graphics.sx index 70bdeba..bd627db 100644 --- a/examples/28-sdl-graphics.sx +++ b/examples/28-sdl-graphics.sx @@ -15,8 +15,7 @@ Matrix44 :: struct { } mat4_zero :: (m: *Matrix44) { - i := 0; - while i < 16 { m.data[i] = 0.0; i += 1; } + memset(xx @m.data[0], 0, 16 * size_of(f32)); } mat4_identity :: (m: *Matrix44) { @@ -41,8 +40,7 @@ mat4_multiply :: (out: *Matrix44, a: *Matrix44, b: *Matrix44) { } j += 1; } - i := 0; - while i < 16 { out.data[i] = tmp[i]; i += 1; } + out.data = tmp; } mat4_perspective :: (m: *Matrix44, fov: f32, aspect: f32, near: f32, far: f32) { @@ -149,7 +147,7 @@ main :: () { glDepthFunc(GL_LESS); // Shaders - vert_src : [:0]u8 = #string GLSL + vert_src : [:0]u8 = #string glsl #version 330 core layout (location = 0) in vec3 aPos; layout (location = 1) in vec3 aNormal; @@ -162,7 +160,7 @@ void main() { vNormal = aNormal; vPos = aPos; } -GLSL; +glsl; frag_src : [:0]u8 = #string GLSL #version 330 core diff --git a/examples/32-http-server.sx b/examples/32-http-server.sx index b863c27..3c02b81 100644 --- a/examples/32-http-server.sx +++ b/examples/32-http-server.sx @@ -58,7 +58,7 @@ main :: () -> s32 { close(client); } - arena_destroy(@arena); + arena_deinit(@arena); close(fd); 0; } diff --git a/examples/50-smoke.sx b/examples/50-smoke.sx index 62aba01..dafa090 100644 --- a/examples/50-smoke.sx +++ b/examples/50-smoke.sx @@ -545,6 +545,14 @@ END; mpw_ptr[2] = 99; print("mp-write: {}\n", mpw[2]); + // Pointer-null comparison + np : *s32 = null; + print("ptr==null: {}\n", np == null); + print("ptr!=null: {}\n", np != null); + np2 := @pv.x; + print("ptr2==null: {}\n", np2 == null); + print("ptr2!=null: {}\n", np2 != null); + // --- Vectors --- vc := vec3(1, 3, 2); print("vec-construct: {}\n", vc); diff --git a/examples/modules/allocators.sx b/examples/modules/allocators.sx index 3b1bb10..2d48d29 100644 --- a/examples/modules/allocators.sx +++ b/examples/modules/allocators.sx @@ -27,53 +27,49 @@ gpa_free :: (ctx: *void, ptr: *void) { } gpa_create :: (gpa: *GPA) -> Allocator { - ctx : *void = xx gpa; - Allocator.{ ctx = ctx, alloc = gpa_alloc, free = gpa_free }; + Allocator.{ ctx = gpa, alloc = gpa_alloc, free = gpa_free }; } // --- Arena: multi-chunk bump allocator (Zig-inspired) --- ArenaChunk :: struct { - next: *void; // *ArenaChunk - cap: s64; // total chunk size including this header + next: *ArenaChunk; + cap: s64; } Arena :: struct { - first: *void; // *ArenaChunk — head of list (newest first) - end_index: s64; // bump position within current chunk's usable area - parent: Allocator; // backing allocator + first: *ArenaChunk; + end_index: s64; + parent: Allocator; } arena_add_chunk :: (a: *Arena, min_size: s64) { - first_i : s64 = xx a.first; - prev_cap := if first_i != 0 then { c : *ArenaChunk = xx a.first; c.cap; } else 0; + prev_cap := if a.first != null then a.first.cap else 0; needed := min_size + 16 + 16; len := (prev_cap + needed) * 3 / 2; raw := a.parent.alloc(a.parent.ctx, len); chunk : *ArenaChunk = xx raw; chunk.next = a.first; chunk.cap = len; - a.first = xx chunk; + a.first = chunk; a.end_index = 0; } arena_alloc :: (ctx: *void, size: s64) -> *void { a : *Arena = xx ctx; aligned := (size + 7) & (0 - 8); - first_i : s64 = xx a.first; - if first_i != 0 { - chunk : *ArenaChunk = xx a.first; - usable := chunk.cap - 16; + if a.first != null { + usable := a.first.cap - 16; if a.end_index + aligned <= usable { buf : [*]u8 = xx a.first; - ptr : *void = xx @buf[16 + a.end_index]; + ptr := @buf[16 + a.end_index]; a.end_index = a.end_index + aligned; return ptr; } } arena_add_chunk(a, aligned); buf : [*]u8 = xx a.first; - ptr : *void = xx @buf[16 + a.end_index]; + ptr := @buf[16 + a.end_index]; a.end_index = a.end_index + aligned; ptr; } @@ -86,34 +82,29 @@ arena_create :: (a: *Arena, parent: Allocator, size: s64) -> Allocator { a.end_index = 0; a.parent = parent; arena_add_chunk(a, size); - ctx : *void = xx a; - Allocator.{ ctx = ctx, alloc = arena_alloc, free = arena_free }; + Allocator.{ ctx = a, alloc = arena_alloc, free = arena_free }; } arena_reset :: (a: *Arena) { // Keep first chunk (newest/largest), free the rest - first_i : s64 = xx a.first; - if first_i != 0 { - chunk : *ArenaChunk = xx a.first; - it : s64 = xx chunk.next; - while it != 0 { - c : *ArenaChunk = xx it; - next_i : s64 = xx c.next; - a.parent.free(a.parent.ctx, xx c); - it = next_i; + if a.first != null { + it := a.first.next; + while it != null { + next := it.next; + a.parent.free(a.parent.ctx, it); + it = next; } - chunk.next = null; + a.first.next = null; } a.end_index = 0; } arena_deinit :: (a: *Arena) { - it : s64 = xx a.first; - while it != 0 { - c : *ArenaChunk = xx it; - next_i : s64 = xx c.next; - a.parent.free(a.parent.ctx, xx c); - it = next_i; + it := a.first; + while it != null { + next := it.next; + a.parent.free(a.parent.ctx, it); + it = next; } a.first = null; a.end_index = 0; @@ -133,7 +124,7 @@ buf_alloc :: (ctx: *void, size: s64) -> *void { if b.pos + aligned > b.len { return null; } - ptr : *void = xx @b.buf[b.pos]; + ptr := @b.buf[b.pos]; b.pos = b.pos + aligned; ptr; } @@ -145,8 +136,7 @@ buf_create :: (b: *BufAlloc, buf: [*]u8, len: s64) -> Allocator { b.buf = buf; b.len = len; b.pos = 0; - ctx : *void = xx b; - Allocator.{ ctx = ctx, alloc = buf_alloc, free = buf_free }; + Allocator.{ ctx = b, alloc = buf_alloc, free = buf_free }; } buf_reset :: (b: *BufAlloc) { diff --git a/examples/modules/socket.sx b/examples/modules/socket.sx index 3871f6d..ebc0eab 100644 --- a/examples/modules/socket.sx +++ b/examples/modules/socket.sx @@ -29,5 +29,5 @@ SockAddr :: struct { } htons :: (port: s64) -> u16 { - cast(u16) ((port / 256) | ((port % 256) * 256)); + cast(u16) (((port & 0xFF) << 8) | ((port >> 8) & 0xFF)); } diff --git a/examples/modules/std.sx b/examples/modules/std.sx index e58878c..0062e5f 100644 --- a/examples/modules/std.sx +++ b/examples/modules/std.sx @@ -32,8 +32,7 @@ context : Context = ---; // --- Slice & string allocation --- cstring :: (size: s64) -> string { - p : s64 = xx context.allocator.ctx; - raw := if p != 0 then context.allocator.alloc(context.allocator.ctx, size + 1) else malloc(size + 1); + raw := if context.allocator.ctx != null then context.allocator.alloc(context.allocator.ctx, size + 1) else malloc(size + 1); memset(raw, 0, size + 1); s : string = ---; s.ptr = xx raw; @@ -42,8 +41,7 @@ cstring :: (size: s64) -> string { } alloc_slice :: ($T: Type, count: s64) -> []T { - p : s64 = xx context.allocator.ctx; - raw := if p != 0 then context.allocator.alloc(context.allocator.ctx, count * size_of(T)) else malloc(count * size_of(T)); + raw := if context.allocator.ctx != null then context.allocator.alloc(context.allocator.ctx, count * size_of(T)) else malloc(count * size_of(T)); memset(raw, 0, count * size_of(T)); s : []T = ---; s.ptr = xx raw; @@ -55,19 +53,16 @@ int_to_string :: (n: s64) -> string { if n == 0 { return "0"; } neg := n < 0; v := if neg then 0 - n else n; - tmp := v; - len := 0; - while tmp > 0 { len += 1; tmp = tmp / 10; } - total := if neg then len + 1 else len; - buf := cstring(total); - i := total - 1; + // Single pass: fill digits backwards into temp string, then substr + tmp := cstring(20); + i := 19; while v > 0 { - buf[i] = (v % 10) + 48; + tmp[i] = (v % 10) + 48; v = v / 10; i -= 1; } - if neg { buf[0] = 45; } - buf; + if neg { tmp[i] = 45; i -= 1; } + substr(tmp, i + 1, 20 - i - 1); } bool_to_string :: (b: bool) -> string { @@ -100,6 +95,17 @@ float_to_string :: (f: f64) -> string { buf; } +hex_group :: (buf: string, offset: s64, val: s64) { + i := offset + 3; + v := val; + while i >= offset { + d := v % 16; + buf[i] = if d < 10 then d + 48 else d - 10 + 97; + v = v / 16; + i -= 1; + } +} + int_to_hex_string :: (n: s64) -> string { if n == 0 { return "0"; } @@ -117,42 +123,11 @@ int_to_hex_string :: (n: s64) -> string { if g3 < 0 { g3 = g3 + 65536; } buf := cstring(16); - // Group 3: digits 0-3 (bits 48-63) - i := 3; - v := g3; - while i >= 0 { - d := v % 16; - buf[i] = if d < 10 then d + 48 else d - 10 + 97; - v = v / 16; - i -= 1; - } - // Group 2: digits 4-7 (bits 32-47) - i = 7; - v = g2; - while i >= 4 { - d := v % 16; - buf[i] = if d < 10 then d + 48 else d - 10 + 97; - v = v / 16; - i -= 1; - } - // Group 1: digits 8-11 (bits 16-31) - i = 11; - v = g1; - while i >= 8 { - d := v % 16; - buf[i] = if d < 10 then d + 48 else d - 10 + 97; - v = v / 16; - i -= 1; - } - // Group 0: digits 12-15 (bits 0-15) - i = 15; - v = g0; - while i >= 12 { - d := v % 16; - buf[i] = if d < 10 then d + 48 else d - 10 + 97; - v = v / 16; - i -= 1; - } + hex_group(buf, 0, g3); + hex_group(buf, 4, g2); + hex_group(buf, 8, g1); + hex_group(buf, 12, g0); + // Skip leading zeros (keep at least 1 digit) start := 0; while start < 15 { diff --git a/src/codegen.zig b/src/codegen.zig index 1aa56dc..e7cd9d3 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -775,7 +775,7 @@ pub const CodeGen = struct { }, .vector_type, .array_type => self.allocaStoreAsI64(self.typeToLLVM(ty), val, "any_vec"), .slice_type => self.allocaStoreAsI64(self.getStringStructType(), val, "any_slice"), - .pointer_type, .many_pointer_type => self.ptrToInt(val, "any_ptr"), + .pointer_type, .many_pointer_type, .function_type => self.ptrToInt(val, "any_ptr"), .meta_type => |mt| self.allocaStoreAsI64(self.getStringStructType(), self.buildStringSlice(val, self.constInt64(mt.name.len)), "any_type"), else => self.sExt(val, i64_ty, "any_val"), }; @@ -3299,8 +3299,27 @@ pub const CodeGen = struct { fn genFieldAssignment(self: *CodeGen, asgn: ast.Assignment) !c.LLVMValueRef { const fa = asgn.target.data.field_access; - // Object must be an identifier for now - if (fa.object.data != .identifier) return self.emitError("field assignment target must be a variable"); + // Non-identifier object (e.g. a.first.next = val — chained pointer field assignment) + if (fa.object.data != .identifier) { + const obj_ty = self.inferType(fa.object); + if (obj_ty.isPointer()) { + // Chained pointer auto-deref: expr.field = val where expr is *Struct + const pointee_ty = self.resolveTypeFromName(obj_ty.pointer_type.pointee_name) orelse + return self.emitError("unknown pointee type for chained field assignment"); + if (pointee_ty.isStruct()) { + const sname = pointee_ty.struct_type; + const info = try self.getStructInfo(sname); + const fi = try self.findFieldIndex(info.field_names, fa.field, sname); + const field_ty = info.field_types[fi]; + const ptr_val = try self.genExpr(fa.object); + const gep = self.structGEP(info.llvm_type, ptr_val, @intCast(fi), "chain_pfield_ptr"); + const rhs = try self.genExprAsType(asgn.value, field_ty); + self.storeOrCompound(asgn.op, gep, rhs, field_ty, "chain_pcur"); + return null; + } + } + return self.emitError("field assignment target must be a variable"); + } const obj_name = fa.object.data.identifier.name; const entry = self.getNamedOrGlobal(obj_name) orelse return self.emitErrorFmt("undefined variable '{s}'", .{obj_name}); @@ -3970,6 +3989,14 @@ pub const CodeGen = struct { return; } + // Forward declaration: register name early so self-referential *T works + try self.type_registry.put(sd.name, .{ .struct_info = .{ + .field_names = &.{}, + .field_types = &.{}, + .field_defaults = &.{}, + .llvm_type = null, + } }); + // Pre-pass: hoist inline type declarations from field types for (sd.field_types, 0..) |ft, i| { try self.hoistInlineTypeDecl(sd.name, sd.field_names[i], ft); @@ -4008,6 +4035,15 @@ pub const CodeGen = struct { } fn registerTaggedEnum(self: *CodeGen, ud: ast.EnumDecl) !void { + // Forward declaration: register name early so self-referential *T works + try self.type_registry.put(ud.name, .{ .tagged_enum = .{ + .variant_names = &.{}, + .variant_types = &.{}, + .llvm_type = null, + .max_payload_size = 0, + .payload_field_index = 0, + } }); + // Pre-pass: hoist inline type declarations from variant types for (ud.variant_types, 0..) |vt_opt, i| { if (vt_opt) |vt| { @@ -4189,6 +4225,15 @@ pub const CodeGen = struct { } fn registerUnionType(self: *CodeGen, ud: ast.UnionDecl) !void { + // Forward declaration: register name early so self-referential *T works + try self.type_registry.put(ud.name, .{ .union_info = .{ + .field_names = &.{}, + .field_types = &.{}, + .llvm_type = null, + .total_size = 0, + .promoted_fields = std.StringHashMap(PromotedField).init(self.allocator), + } }); + // Hoist inline type declarations from field types for (ud.field_types, 0..) |ft, i| { try self.hoistInlineTypeDecl(ud.name, ud.field_names[i], ft); @@ -5524,6 +5569,24 @@ pub const CodeGen = struct { // Non-identifier object: evaluate expression and check type const obj_val = try self.genExpr(fa.object); const obj_ty = self.inferType(fa.object); + // Pointer auto-deref: expr.field where expr is *T → load through pointer + if (obj_ty.isPointer()) { + const pointee_ty = self.resolveTypeFromName(obj_ty.pointer_type.pointee_name) orelse + return self.emitError("unknown pointee type for auto-deref"); + if (pointee_ty.isStruct()) { + const sname = pointee_ty.struct_type; + const info = try self.getStructInfo(sname); + const idx = self.findNameIndex(info.field_names, fa.field) orelse + return self.emitErrorFmt("no field '{s}' in struct '{s}'", .{ fa.field, sname }); + const gep = self.structGEP(info.llvm_type, obj_val, @intCast(idx), "pfield"); + return self.loadTyped(info.field_types[idx], gep, "pfieldval"); + } + if (pointee_ty.isSlice()) { + const slice_val = c.LLVMBuildLoad2(self.builder, self.getStringStructType(), obj_val, "pslice_load"); + return self.extractFatPtrField(slice_val, fa.field, "*slice"); + } + return self.emitErrorFmt("no field '{s}' on pointer", .{fa.field}); + } if (obj_ty.isVector()) { return self.genVectorExtract(obj_val, fa.field); } diff --git a/src/comptime.zig b/src/comptime.zig index 01947b5..9f39eb6 100644 --- a/src/comptime.zig +++ b/src/comptime.zig @@ -1122,9 +1122,9 @@ pub const VM = struct { alloc_field_names[0] = "ctx"; alloc_field_names[1] = "alloc"; alloc_field_names[2] = "free"; - alloc_fields[0] = .{ .int_val = 0 }; // null ctx pointer - alloc_fields[1] = .{ .int_val = 0 }; // null alloc - alloc_fields[2] = .{ .int_val = 0 }; // null free + alloc_fields[0] = .{ .null_val = {} }; // null ctx pointer + alloc_fields[1] = .{ .null_val = {} }; // null alloc + alloc_fields[2] = .{ .null_val = {} }; // null free const ctx_fields = try self.allocator.alloc(Value, 2); const ctx_field_names = try self.allocator.alloc([]const u8, 2); @@ -1771,8 +1771,10 @@ pub const VM = struct { if (a == .int_val and b == .int_val) return a.int_val == b.int_val; if (a == .bool_val and b == .bool_val) return a.bool_val == b.bool_val; if (a == .string_val and b == .string_val) return std.mem.eql(u8, a.string_val, b.string_val); - // Pointer comparison + // Pointer comparison (null_val == int_val(0) for null pointer compatibility) if (a == .null_val and b == .null_val) return true; + if (a == .null_val and b == .int_val) return b.int_val == 0; + if (a == .int_val and b == .null_val) return a.int_val == 0; if (a == .null_val or b == .null_val) return false; if (a == .pointer_val and b == .pointer_val) return a.pointer_val.target == b.pointer_val.target; // Float comparison diff --git a/src/lsp/document.zig b/src/lsp/document.zig index 09354ea..a6f9898 100644 --- a/src/lsp/document.zig +++ b/src/lsp/document.zig @@ -197,7 +197,7 @@ pub const DocumentStore = struct { .ty = sym.ty, .def_span = sym.def_span, .scope_depth = 0, - .origin = file_path, + .origin = sym.origin orelse file_path, }); } } @@ -263,7 +263,7 @@ pub const DocumentStore = struct { .ty = sym.ty, .def_span = sym.def_span, .scope_depth = 0, - .origin = imp.path, + .origin = sym.origin orelse imp.path, }); } } diff --git a/src/sema.zig b/src/sema.zig index 180c698..b27f713 100644 --- a/src/sema.zig +++ b/src/sema.zig @@ -555,6 +555,8 @@ pub const Analyzer = struct { for (indices.items) |idx| { if (idx >= scope_start) { const sym = self.symbols.items[idx]; + // Skip imported symbols — local declarations are allowed to shadow them + if (sym.origin != null) continue; if (sym.scope_depth == self.scope_depth) { try self.diagnostics.append(self.allocator, .{ .level = .warn, diff --git a/src/types.zig b/src/types.zig index c612fa6..7fefdbb 100644 --- a/src/types.zig +++ b/src/types.zig @@ -531,6 +531,9 @@ pub const Type = union(enum) { return Type.s(capped); } + // Pointer types: both are pointers → return first (all are opaque ptr at LLVM level) + if ((a.isPointer() or a.isManyPointer()) and (b.isPointer() or b.isManyPointer())) return a; + return a; } }; diff --git a/tests/expected/50-smoke.txt b/tests/expected/50-smoke.txt index 9919f35..67dffe9 100644 --- a/tests/expected/50-smoke.txt +++ b/tests/expected/50-smoke.txt @@ -133,6 +133,10 @@ auto-deref: 10 mp[0]: 10 mp[3]: 40 mp-write: 99 +ptr==null: true +ptr!=null: false +ptr2==null: false +ptr2!=null: true vec-construct: [1.000000, 3.000000, 2.000000] vec-add: [5.000000, 7.000000, 9.000000] vec-sub: [4.000000, 3.000000, 2.000000]