extend default to s64

This commit is contained in:
agra
2026-02-11 01:05:21 +02:00
parent 70435d3c85
commit 25e1372731
11 changed files with 754 additions and 208 deletions

View File

@@ -15,6 +15,12 @@ pub const Value = union(enum) {
array_val: ArrayValue,
type_val: Type,
function_val: FunctionVal,
pointer_val: PointerValue,
null_val: void,
pub const PointerValue = struct {
target: [*]Value,
};
pub const StructValue = struct {
type_name: []const u8,
@@ -96,6 +102,11 @@ pub const Value = union(enum) {
try buf.append(allocator, ']');
return buf.items;
},
.pointer_val => |pv| {
const inner = try pv.target[0].format(allocator);
return std.fmt.allocPrint(allocator, "&{s}", .{inner});
},
.null_val => allocator.dupe(u8, "null"),
};
}
};
@@ -160,6 +171,13 @@ pub const Instruction = union(enum) {
get_field: u16,
set_field: u16,
// Pointers
address_of_local: u16, // push pointer to local slot
address_of_index, // pop idx, pop array, push pointer to element
deref, // pop pointer, push dereferenced value
deref_set, // pop value, pop pointer, store through pointer
push_null, // push null pointer
// Arrays
make_array: u32, // element count on stack
get_index,
@@ -295,6 +313,29 @@ pub const Compiler = struct {
return idx;
}
/// Look up a struct field index by name, handling pointer auto-deref.
fn resolveFieldIndex(self: *Compiler, object: *Node, field: []const u8) ?u16 {
if (self.sema_result) |sr| {
const obj_ty = sr.type_map.get(object) orelse return null;
const struct_name: ?[]const u8 = if (obj_ty.isStruct())
obj_ty.struct_type
else if (obj_ty.isPointer())
obj_ty.pointer_type.pointee_name
else
null;
if (struct_name) |sn| {
if (sr.struct_types.get(sn)) |info| {
for (info.field_names, 0..) |fname, idx| {
if (std.mem.eql(u8, fname, field)) {
return @intCast(idx);
}
}
}
}
}
return null;
}
fn resolveLocal(self: *Compiler, name: []const u8) ?u16 {
var i = self.locals.items.len;
while (i > 0) {
@@ -451,12 +492,29 @@ pub const Compiler = struct {
}
},
.unary_op => |unop| {
try self.compileNode(unop.operand);
switch (unop.op) {
.negate => try self.emit(.negate),
.not => try self.emit(.not),
.xx => {}, // cast — handle later
.address_of => {}, // pointers not supported in comptime
if (unop.op == .address_of) {
if (unop.operand.data == .identifier) {
if (self.resolveLocal(unop.operand.data.identifier.name)) |slot| {
try self.emit(.{ .address_of_local = slot });
} else {
return error.UnsupportedExpression;
}
} else if (unop.operand.data == .index_expr) {
// &arr[i] — push array, push index, address_of_index
try self.compileNode(unop.operand.data.index_expr.object);
try self.compileNode(unop.operand.data.index_expr.index);
try self.emit(.address_of_index);
} else {
return error.UnsupportedExpression;
}
} else {
try self.compileNode(unop.operand);
switch (unop.op) {
.negate => try self.emit(.negate),
.not => try self.emit(.not),
.xx => {}, // cast — handle later
.address_of => unreachable, // handled above
}
}
},
.comptime_expr => |ct| {
@@ -533,6 +591,26 @@ pub const Compiler = struct {
try self.emit(.{ .set_local = slot });
}
}
} else if (asgn.target.data == .field_access) {
// obj.field = val (works with auto-deref for pointers)
const fa = asgn.target.data.field_access;
const field_idx = self.resolveFieldIndex(fa.object, fa.field) orelse return error.UnsupportedExpression;
try self.compileNode(fa.object);
if (asgn.op != .assign) return error.UnsupportedExpression;
try self.compileNode(asgn.value);
try self.emit(.{ .set_field = field_idx });
// Store back to local
if (fa.object.data == .identifier) {
if (self.resolveLocal(fa.object.data.identifier.name)) |slot| {
try self.emit(.{ .set_local = slot });
}
}
} else if (asgn.target.data == .deref_expr) {
// p.* = val
try self.compileNode(asgn.target.data.deref_expr.operand);
if (asgn.op != .assign) return error.UnsupportedExpression;
try self.compileNode(asgn.value);
try self.emit(.deref_set);
}
},
.return_stmt => |rs| {
@@ -655,20 +733,10 @@ pub const Compiler = struct {
}
}
}
// Look up field index from sema struct_types
if (self.sema_result) |sr| {
// Infer the object type to find the struct name
const obj_ty = sr.type_map.get(fa.object);
if (obj_ty != null and obj_ty.?.isStruct()) {
if (sr.struct_types.get(obj_ty.?.struct_type)) |info| {
for (info.field_names, 0..) |fname, idx| {
if (std.mem.eql(u8, fname, fa.field)) {
try self.emit(.{ .get_field = @intCast(idx) });
return;
}
}
}
}
// Look up field index from sema struct_types (handles pointer auto-deref)
if (self.resolveFieldIndex(fa.object, fa.field)) |field_idx| {
try self.emit(.{ .get_field = field_idx });
return;
}
// Fallback: use field name for well-known string fields
// (sema may not have type info for nodes in imported function bodies)
@@ -748,6 +816,16 @@ pub const Compiler = struct {
const offset = @as(i32, @intCast(target)) - @as(i32, @intCast(self.instructions.items.len)) - 1;
try self.emit(.{ .jump = offset });
},
.deref_expr => |de| {
try self.compileNode(de.operand);
try self.emit(.deref);
},
.null_literal => {
try self.emit(.push_null);
},
.pointer_type_expr, .many_pointer_type_expr => {
try self.emit(.push_void); // type expressions not meaningful as values
},
.defer_stmt => {}, // defer not meaningful in comptime
.insert_expr => {}, // handled by codegen, not VM
else => {
@@ -871,6 +949,51 @@ pub const VM = struct {
self.stack[abs_slot] = val;
},
// Pointers
.address_of_local => |slot| {
const abs_slot = frame.base_slot + slot;
// Grow stack if needed so the target slot exists
while (self.sp <= abs_slot) {
self.stack[self.sp] = .{ .void_val = {} };
self.sp += 1;
}
const ptr: [*]Value = @ptrCast(&self.stack[abs_slot]);
try self.push(.{ .pointer_val = .{ .target = ptr } });
},
.address_of_index => {
const idx_val = try self.pop();
const arr = try self.pop();
const idx: usize = @intCast(idx_val.asInt() orelse return error.TypeError);
if (arr == .array_val) {
if (idx >= arr.array_val.elements.len) return error.IndexOutOfBounds;
try self.push(.{ .pointer_val = .{ .target = arr.array_val.elements.ptr + idx } });
} else {
return error.TypeError;
}
},
.deref => {
const v = try self.pop();
if (v == .pointer_val) {
try self.push(try self.cloneValue(v.pointer_val.target[0]));
} else if (v == .null_val) {
return error.NullDereference;
} else {
return error.TypeError;
}
},
.deref_set => {
const val = try self.pop();
const ptr_v = try self.pop();
if (ptr_v == .pointer_val) {
ptr_v.pointer_val.target[0] = val;
} else if (ptr_v == .null_val) {
return error.NullDereference;
} else {
return error.TypeError;
}
},
.push_null => try self.push(.{ .null_val = {} }),
// Global variables (lazily resolved from root_decls)
.get_global => |name_idx| {
const name = if (name_idx < frame.chunk.strings.len) frame.chunk.strings[name_idx] else return error.InvalidGlobal;
@@ -1014,7 +1137,9 @@ pub const VM = struct {
try self.push(.{ .struct_val = .{ .type_name = sm.type_name, .field_names = sm.field_names, .fields = fields } });
},
.get_field => |idx| {
const obj = try self.pop();
const raw_obj = try self.pop();
// Auto-deref pointer
const obj = if (raw_obj == .pointer_val) raw_obj.pointer_val.target[0] else raw_obj;
if (obj == .struct_val) {
if (idx < obj.struct_val.fields.len) {
try self.push(obj.struct_val.fields[idx]);
@@ -1034,12 +1159,22 @@ pub const VM = struct {
},
.set_field => |idx| {
const val = try self.pop();
const obj = try self.pop();
if (obj == .struct_val) {
if (idx < obj.struct_val.fields.len) {
obj.struct_val.fields[idx] = val;
const raw_obj = try self.pop();
if (raw_obj == .pointer_val) {
// Auto-deref: mutate field in-place through pointer
const target = raw_obj.pointer_val.target;
if (target[0] == .struct_val) {
const sv = target[0].struct_val;
if (idx < sv.fields.len) {
sv.fields[idx] = val;
}
}
try self.push(obj);
try self.push(raw_obj); // push pointer back
} else if (raw_obj == .struct_val) {
if (idx < raw_obj.struct_val.fields.len) {
raw_obj.struct_val.fields[idx] = val;
}
try self.push(raw_obj);
} else {
return error.TypeError;
}
@@ -1073,6 +1208,10 @@ pub const VM = struct {
} else {
return error.IndexOutOfBounds;
}
} else if (arr == .pointer_val) {
// Many-pointer indexing: ptr[i]
const idx: usize = @intCast(idx_val.asInt() orelse return error.TypeError);
try self.push(arr.pointer_val.target[idx]);
} else {
return error.TypeError;
}
@@ -1096,6 +1235,11 @@ pub const VM = struct {
mutable[idx] = byte_val;
}
try self.push(arr);
} else if (arr == .pointer_val) {
// Many-pointer index assignment: ptr[i] = val
const idx: usize = @intCast(idx_val.asInt() orelse return error.TypeError);
arr.pointer_val.target[idx] = val;
try self.push(arr);
} else {
return error.TypeError;
}
@@ -1166,11 +1310,37 @@ pub const VM = struct {
} };
}
/// Clone a value, allocating new backing storage for composites (structs/arrays)
/// so the clone is independent. Does not follow pointers.
fn cloneValue(self: *VM, val: Value) !Value {
return switch (val) {
.struct_val => |sv| {
const new_fields = try self.allocator.alloc(Value, sv.fields.len);
for (sv.fields, 0..) |f, i| {
new_fields[i] = try self.cloneValue(f);
}
return .{ .struct_val = .{ .type_name = sv.type_name, .field_names = sv.field_names, .fields = new_fields } };
},
.array_val => |av| {
const new_elements = try self.allocator.alloc(Value, av.elements.len);
for (av.elements, 0..) |e, i| {
new_elements[i] = try self.cloneValue(e);
}
return .{ .array_val = .{ .elements = new_elements } };
},
else => val,
};
}
fn valEqual(self: *VM, a: Value, b: Value) bool {
_ = self;
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
if (a == .null_val and b == .null_val) return true;
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
const af = a.asFloat();
const bf = b.asFloat();
@@ -1377,6 +1547,7 @@ pub const VM = struct {
StackUnderflow,
IndexOutOfBounds,
DivisionByZero,
NullDereference,
UnsupportedExpression,
OutOfMemory,
};
@@ -1752,3 +1923,167 @@ test "VM: function with return statement" {
);
try std.testing.expectEqual(@as(i64, 36), result.int_val);
}
test "VM: address-of and deref" {
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
const alloc = arena.allocator();
// x := 42; ptr := &x; ptr.*
const code = [_]Instruction{
.{ .push_int = 42 },
.{ .set_local = 0 }, // x = 42
.{ .address_of_local = 0 }, // &x
.{ .set_local = 1 }, // ptr = &x
.{ .get_local = 1 }, // ptr
.deref, // ptr.*
};
const chunk = Chunk{
.code = &code,
.strings = &.{},
.local_count = 2,
.name = "<test>",
};
var vm = VM.init(alloc, null, &.{}, null);
const result = try vm.execute(&chunk);
try std.testing.expectEqual(@as(i64, 42), result.int_val);
}
test "VM: deref_set modifies through pointer" {
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
const alloc = arena.allocator();
// x := 10; ptr := &x; ptr.* = 99; x
const code = [_]Instruction{
.{ .push_int = 10 },
.{ .set_local = 0 }, // x = 10
.{ .address_of_local = 0 }, // &x
.{ .set_local = 1 }, // ptr = &x
.{ .get_local = 1 }, // ptr
.{ .push_int = 99 },
.deref_set, // ptr.* = 99
.{ .get_local = 0 }, // x (should be 99 now)
};
const chunk = Chunk{
.code = &code,
.strings = &.{},
.local_count = 2,
.name = "<test>",
};
var vm = VM.init(alloc, null, &.{}, null);
const result = try vm.execute(&chunk);
try std.testing.expectEqual(@as(i64, 99), result.int_val);
}
test "VM: null pointer" {
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
const alloc = arena.allocator();
const code = [_]Instruction{
.push_null,
};
const chunk = Chunk{
.code = &code,
.strings = &.{},
.local_count = 0,
.name = "<test>",
};
var vm = VM.init(alloc, null, &.{}, null);
const result = try vm.execute(&chunk);
try std.testing.expect(result == .null_val);
}
test "VM: pointer to struct field access" {
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
const alloc = arena.allocator();
// Build: struct{x: 10, y: 20}, &struct, get_field(1) — auto-deref
const code = [_]Instruction{
.{ .push_int = 10 },
.{ .push_int = 20 },
.{ .make_struct = .{ .type_name = "Point", .field_count = 2, .field_names = &.{ "x", "y" } } },
.{ .set_local = 0 }, // v = Point{10, 20}
.{ .address_of_local = 0 }, // &v
.{ .get_field = 1 }, // auto-deref, get y
};
const chunk = Chunk{
.code = &code,
.strings = &.{},
.local_count = 1,
.name = "<test>",
};
var vm = VM.init(alloc, null, &.{}, null);
const result = try vm.execute(&chunk);
try std.testing.expectEqual(@as(i64, 20), result.int_val);
}
test "VM: pointer to struct field mutation" {
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
const alloc = arena.allocator();
// v = Point{10, 20}; ptr = &v; ptr.x = 99; v.x
const code = [_]Instruction{
.{ .push_int = 10 },
.{ .push_int = 20 },
.{ .make_struct = .{ .type_name = "Point", .field_count = 2, .field_names = &.{ "x", "y" } } },
.{ .set_local = 0 }, // v = Point{10, 20}
.{ .address_of_local = 0 }, // &v
.{ .set_local = 1 }, // ptr = &v
.{ .get_local = 1 }, // ptr
.{ .push_int = 99 },
.{ .set_field = 0 }, // ptr.x = 99 (auto-deref)
.{ .set_local = 1 }, // store ptr back
.{ .get_local = 0 }, // v
.{ .get_field = 0 }, // v.x
};
const chunk = Chunk{
.code = &code,
.strings = &.{},
.local_count = 2,
.name = "<test>",
};
var vm = VM.init(alloc, null, &.{}, null);
const result = try vm.execute(&chunk);
try std.testing.expectEqual(@as(i64, 99), result.int_val);
}
test "VM: many-pointer indexing" {
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
const alloc = arena.allocator();
// arr = [10, 20, 30]; mp = &arr[0]; mp[2]
const code = [_]Instruction{
.{ .push_int = 10 },
.{ .push_int = 20 },
.{ .push_int = 30 },
.{ .make_array = 3 },
.{ .set_local = 0 }, // arr = [10, 20, 30]
.{ .get_local = 0 }, // arr
.{ .push_int = 0 },
.address_of_index, // &arr[0]
.{ .set_local = 1 }, // mp = &arr[0]
.{ .get_local = 1 }, // mp
.{ .push_int = 2 },
.get_index, // mp[2]
};
const chunk = Chunk{
.code = &code,
.strings = &.{},
.local_count = 2,
.name = "<test>",
};
var vm = VM.init(alloc, null, &.{}, null);
const result = try vm.execute(&chunk);
try std.testing.expectEqual(@as(i64, 30), result.int_val);
}