extend default to s64
This commit is contained in:
387
src/comptime.zig
387
src/comptime.zig
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user