This commit is contained in:
agra
2026-02-14 23:05:57 +02:00
parent 3fd14bafac
commit da1a69ade8
3 changed files with 583 additions and 742 deletions

View File

@@ -57,6 +57,18 @@ pub const Value = union(enum) {
};
}
pub fn asIndex(self: Value) !usize {
return @intCast(self.asInt() orelse return error.TypeError);
}
pub fn isTruthy(self: Value) bool {
return switch (self) {
.bool_val => |bv| bv,
.int_val => |iv| iv != 0,
else => true,
};
}
pub fn asFloat(self: Value) ?f64 {
return switch (self) {
.float_val => |v| v,
@@ -312,6 +324,18 @@ pub const Compiler = struct {
try self.instructions.append(self.allocator, instruction);
}
fn patchJump(self: *Compiler, idx: usize) void {
self.instructions.items[idx] = .{ .jump = @intCast(@as(i64, @intCast(self.instructions.items.len)) - @as(i64, @intCast(idx)) - 1) };
}
fn patchJumpIfFalse(self: *Compiler, idx: usize) void {
self.instructions.items[idx] = .{ .jump_if_false = @intCast(@as(i64, @intCast(self.instructions.items.len)) - @as(i64, @intCast(idx)) - 1) };
}
fn patchJumpIfTrue(self: *Compiler, idx: usize) void {
self.instructions.items[idx] = .{ .jump_if_true = @intCast(@as(i64, @intCast(self.instructions.items.len)) - @as(i64, @intCast(idx)) - 1) };
}
fn addString(self: *Compiler, str: []const u8) !u32 {
const idx: u32 = @intCast(self.strings.items.len);
try self.strings.append(self.allocator, str);
@@ -399,9 +423,7 @@ pub const Compiler = struct {
try self.emit(.{ .jump_if_false = 0 });
try self.emit(.pop);
try self.compileNode(binop.rhs);
self.instructions.items[jump_idx] = .{
.jump_if_false = @intCast(@as(i64, @intCast(self.instructions.items.len)) - @as(i64, @intCast(jump_idx)) - 1),
};
self.patchJumpIfFalse(jump_idx);
} else if (binop.op == .or_op) {
// Short-circuit OR: LHS, dup, jump_if_true +N, pop, RHS
try self.compileNode(binop.lhs);
@@ -410,9 +432,7 @@ pub const Compiler = struct {
try self.emit(.{ .jump_if_true = 0 });
try self.emit(.pop);
try self.compileNode(binop.rhs);
self.instructions.items[jump_idx] = .{
.jump_if_true = @intCast(@as(i64, @intCast(self.instructions.items.len)) - @as(i64, @intCast(jump_idx)) - 1),
};
self.patchJumpIfTrue(jump_idx);
} else {
try self.compileNode(binop.lhs);
try self.compileNode(binop.rhs);
@@ -465,9 +485,7 @@ pub const Compiler = struct {
.neq => .neq,
else => unreachable,
});
self.instructions.items[jump_idx] = .{
.jump_if_false = @intCast(@as(i64, @intCast(self.instructions.items.len)) - @as(i64, @intCast(jump_idx)) - 1),
};
self.patchJumpIfFalse(jump_idx);
}
},
.unary_op => |unop| {
@@ -608,14 +626,11 @@ pub const Compiler = struct {
if (ie.else_branch) |eb| {
const jump_end_idx = self.instructions.items.len;
try self.emit(.{ .jump = 0 }); // placeholder
// Patch jump_if_false to here
self.instructions.items[jump_false_idx] = .{ .jump_if_false = @intCast(self.instructions.items.len - jump_false_idx - 1) };
self.patchJumpIfFalse(jump_false_idx);
try self.compileNode(eb);
// Patch jump to end
self.instructions.items[jump_end_idx] = .{ .jump = @intCast(self.instructions.items.len - jump_end_idx - 1) };
self.patchJump(jump_end_idx);
} else {
// Patch jump_if_false to here
self.instructions.items[jump_false_idx] = .{ .jump_if_false = @intCast(self.instructions.items.len - jump_false_idx - 1) };
self.patchJumpIfFalse(jump_false_idx);
}
},
.call => |call_node| {
@@ -637,18 +652,8 @@ pub const Compiler = struct {
if (callee_name) |name| {
// Check if it's a builtin
const base = if (std.mem.lastIndexOfScalar(u8, name, '.')) |idx| name[idx + 1 ..] else name;
if (std.mem.eql(u8, base, "print")) {
try self.emit(.{ .call_builtin = .{ .id = .print, .arg_count = @intCast(call_node.args.len) } });
} else if (std.mem.eql(u8, base, "write")) {
try self.emit(.{ .call_builtin = .{ .id = .write, .arg_count = @intCast(call_node.args.len) } });
} else if (std.mem.eql(u8, base, "sqrt")) {
try self.emit(.{ .call_builtin = .{ .id = .sqrt, .arg_count = @intCast(call_node.args.len) } });
} else if (std.mem.eql(u8, base, "size_of")) {
try self.emit(.{ .call_builtin = .{ .id = .size_of, .arg_count = @intCast(call_node.args.len) } });
} else if (std.mem.eql(u8, base, "cast")) {
try self.emit(.{ .call_builtin = .{ .id = .cast, .arg_count = @intCast(call_node.args.len) } });
} else if (std.mem.eql(u8, base, "alloc")) {
try self.emit(.{ .call_builtin = .{ .id = .alloc, .arg_count = @intCast(call_node.args.len) } });
if (std.meta.stringToEnum(BuiltinId, base)) |id| {
try self.emit(.{ .call_builtin = .{ .id = id, .arg_count = @intCast(call_node.args.len) } });
} else {
try self.emit(.{ .call = .{ .func_name = name, .arg_count = @intCast(call_node.args.len) } });
}
@@ -670,8 +675,7 @@ pub const Compiler = struct {
try self.compileNode(arm.body);
try end_jumps.append(self.allocator, self.instructions.items.len);
try self.emit(.{ .jump = 0 }); // placeholder jump to end
// Patch jump_if_false
self.instructions.items[jump_next_idx] = .{ .jump_if_false = @intCast(self.instructions.items.len - jump_next_idx - 1) };
self.patchJumpIfFalse(jump_next_idx);
} else {
// else arm: unconditionally execute body
try self.emit(.pop); // pop the subject copy
@@ -683,7 +687,7 @@ pub const Compiler = struct {
try self.emit(.pop); // pop remaining subject
// Patch all end jumps
for (end_jumps.items) |idx| {
self.instructions.items[idx] = .{ .jump = @intCast(self.instructions.items.len - idx - 1) };
self.patchJump(idx);
}
},
.struct_literal => |sl| {
@@ -772,12 +776,11 @@ pub const Compiler = struct {
try self.emit(.{ .jump = back_offset });
// Patch jump_if_false to after the loop
const after_loop = self.instructions.items.len;
self.instructions.items[jump_false_idx] = .{ .jump_if_false = @intCast(after_loop - jump_false_idx - 1) };
self.patchJumpIfFalse(jump_false_idx);
// Patch all break jumps to after the loop
for (self.break_patches.items) |patch_idx| {
self.instructions.items[patch_idx] = .{ .jump = @as(i32, @intCast(after_loop)) - @as(i32, @intCast(patch_idx)) - 1 };
self.patchJump(patch_idx);
}
// Restore outer loop context
@@ -942,7 +945,7 @@ pub const VM = struct {
.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);
const idx: usize = try idx_val.asIndex();
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 } });
@@ -980,31 +983,11 @@ pub const VM = struct {
},
// Arithmetic
.add => {
const b = try self.pop();
const a = try self.pop();
try self.push(try self.arith(a, b, .add_op));
},
.sub => {
const b = try self.pop();
const a = try self.pop();
try self.push(try self.arith(a, b, .sub_op));
},
.mul => {
const b = try self.pop();
const a = try self.pop();
try self.push(try self.arith(a, b, .mul_op));
},
.div => {
const b = try self.pop();
const a = try self.pop();
try self.push(try self.arith(a, b, .div_op));
},
.mod => {
const b = try self.pop();
const a = try self.pop();
try self.push(try self.arith(a, b, .mod_op));
},
.add => try self.execArith(.add_op),
.sub => try self.execArith(.sub_op),
.mul => try self.execArith(.mul_op),
.div => try self.execArith(.div_op),
.mod => try self.execArith(.mod_op),
.bit_and => {
const b = try self.pop();
const a = try self.pop();
@@ -1030,44 +1013,15 @@ pub const VM = struct {
},
// Comparison
.eq => {
const b = try self.pop();
const a = try self.pop();
try self.push(.{ .bool_val = self.valEqual(a, b) });
},
.neq => {
const b = try self.pop();
const a = try self.pop();
try self.push(.{ .bool_val = !self.valEqual(a, b) });
},
.lt => {
const b = try self.pop();
const a = try self.pop();
try self.push(.{ .bool_val = self.valLess(a, b) });
},
.lte => {
const b = try self.pop();
const a = try self.pop();
try self.push(.{ .bool_val = self.valLess(a, b) or self.valEqual(a, b) });
},
.gt => {
const b = try self.pop();
const a = try self.pop();
try self.push(.{ .bool_val = self.valLess(b, a) });
},
.gte => {
const b = try self.pop();
const a = try self.pop();
try self.push(.{ .bool_val = self.valLess(b, a) or self.valEqual(a, b) });
},
.eq => try self.execComparison(.eq),
.neq => try self.execComparison(.neq),
.lt => try self.execComparison(.lt),
.lte => try self.execComparison(.lte),
.gt => try self.execComparison(.gt),
.gte => try self.execComparison(.gte),
.not => {
const v = try self.pop();
const b = switch (v) {
.bool_val => |bv| bv,
.int_val => |iv| iv != 0,
else => true,
};
try self.push(.{ .bool_val = !b });
try self.push(.{ .bool_val = !v.isTruthy() });
},
// Control flow
@@ -1076,23 +1030,13 @@ pub const VM = struct {
},
.jump_if_false => |offset| {
const v = try self.pop();
const is_true = switch (v) {
.bool_val => |bv| bv,
.int_val => |iv| iv != 0,
else => true,
};
if (!is_true) {
if (!v.isTruthy()) {
frame.ip = @intCast(@as(i64, frame.ip) + offset);
}
},
.jump_if_true => |offset| {
const v = try self.pop();
const is_true = switch (v) {
.bool_val => |bv| bv,
.int_val => |iv| iv != 0,
else => true,
};
if (is_true) {
if (v.isTruthy()) {
frame.ip = @intCast(@as(i64, frame.ip) + offset);
}
},
@@ -1187,7 +1131,7 @@ pub const VM = struct {
const idx_val = try self.pop();
const arr = try self.pop();
if (arr == .array_val) {
const idx: usize = @intCast(idx_val.asInt() orelse return error.TypeError);
const idx: usize = try idx_val.asIndex();
if (idx < arr.array_val.elements.len) {
try self.push(arr.array_val.elements[idx]);
} else {
@@ -1195,7 +1139,7 @@ pub const VM = struct {
}
} else if (arr == .string_val) {
// String indexing: return byte as int
const idx: usize = @intCast(idx_val.asInt() orelse return error.TypeError);
const idx: usize = try idx_val.asIndex();
if (idx < arr.string_val.len) {
try self.push(.{ .int_val = @intCast(arr.string_val[idx]) });
} else {
@@ -1203,7 +1147,7 @@ pub const VM = struct {
}
} else if (arr == .pointer_val) {
// Many-pointer indexing: ptr[i]
const idx: usize = @intCast(idx_val.asInt() orelse return error.TypeError);
const idx: usize = try idx_val.asIndex();
try self.push(arr.pointer_val.target[idx]);
} else {
return error.TypeError;
@@ -1214,14 +1158,14 @@ pub const VM = struct {
const idx_val = try self.pop();
const arr = try self.pop();
if (arr == .array_val) {
const idx: usize = @intCast(idx_val.asInt() orelse return error.TypeError);
const idx: usize = try idx_val.asIndex();
if (idx < arr.array_val.elements.len) {
arr.array_val.elements[idx] = val;
}
try self.push(arr);
} else if (arr == .string_val) {
// String index assignment: mutate byte
const idx: usize = @intCast(idx_val.asInt() orelse return error.TypeError);
const idx: usize = try idx_val.asIndex();
const byte_val: u8 = @intCast(val.asInt() orelse return error.TypeError);
if (idx < arr.string_val.len) {
const mutable = @constCast(arr.string_val);
@@ -1230,7 +1174,7 @@ pub const VM = struct {
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);
const idx: usize = try idx_val.asIndex();
arr.pointer_val.target[idx] = val;
try self.push(arr);
} else {
@@ -1260,6 +1204,28 @@ pub const VM = struct {
const ArithOp = enum { add_op, sub_op, mul_op, div_op, mod_op };
fn execArith(self: *VM, op: ArithOp) !void {
const b = try self.pop();
const a = try self.pop();
try self.push(try self.arith(a, b, op));
}
const CmpOp = enum { eq, neq, lt, lte, gt, gte };
fn execComparison(self: *VM, comptime op: CmpOp) !void {
const b = try self.pop();
const a = try self.pop();
const result = switch (op) {
.eq => self.valEqual(a, b),
.neq => !self.valEqual(a, b),
.lt => self.valLess(a, b),
.lte => self.valLess(a, b) or self.valEqual(a, b),
.gt => self.valLess(b, a),
.gte => self.valLess(b, a) or self.valEqual(a, b),
};
try self.push(.{ .bool_val = result });
}
fn arith(self: *VM, a: Value, b: Value, op: ArithOp) !Value {
_ = self;
// Both int
@@ -1357,27 +1323,13 @@ pub const VM = struct {
for (self.root_decls) |decl| {
switch (decl.data) {
.fn_decl => |fd| {
if (std.mem.eql(u8, fd.name, name)) {
var compiler = Compiler.init(self.allocator, self.sema_result, self.root_decls, self.codegen);
const chunk = try compiler.compileFunction(fd);
try self.functions.put(name, chunk);
if (self.functions.getPtr(name)) |ptr| {
return self.invokeChunk(ptr, arg_count);
}
}
if (std.mem.eql(u8, fd.name, name))
return self.compileFunctionAndInvoke(name, fd, arg_count);
},
.namespace_decl => |ns| {
for (ns.decls) |d| {
if (d.data == .fn_decl) {
if (std.mem.eql(u8, d.data.fn_decl.name, name)) {
var compiler = Compiler.init(self.allocator, self.sema_result, self.root_decls, self.codegen);
const chunk = try compiler.compileFunction(d.data.fn_decl);
try self.functions.put(name, chunk);
if (self.functions.getPtr(name)) |ptr| {
return self.invokeChunk(ptr, arg_count);
}
}
}
if (d.data == .fn_decl and std.mem.eql(u8, d.data.fn_decl.name, name))
return self.compileFunctionAndInvoke(name, d.data.fn_decl, arg_count);
}
},
else => {},
@@ -1553,6 +1505,29 @@ pub const VM = struct {
return result;
}
fn cacheTypeGlobal(self: *VM, name: []const u8, ty: Type) VMError!Value {
const val = Value{ .type_val = ty };
try self.globals.put(name, val);
return val;
}
fn compileAndEvalGlobal(self: *VM, name: []const u8, expr: *Node) VMError!Value {
var compiler = Compiler.init(self.allocator, self.sema_result, self.root_decls, self.codegen);
const chunk = compiler.compile(expr) catch return error.CompileError;
const result = self.evalInFreshVM(&chunk) catch return error.CompileError;
try self.globals.put(name, result);
return result;
}
fn compileFunctionAndInvoke(self: *VM, name: []const u8, fd: ast.FnDecl, arg_count: u8) !void {
var compiler = Compiler.init(self.allocator, self.sema_result, self.root_decls, self.codegen);
const chunk = try compiler.compileFunction(fd);
try self.functions.put(name, chunk);
if (self.functions.getPtr(name)) |ptr| {
return self.invokeChunk(ptr, arg_count);
}
}
fn resolveGlobal(self: *VM, name: []const u8) VMError!Value {
// Check cache first
if (self.globals.get(name)) |val| return val;
@@ -1562,21 +1537,13 @@ pub const VM = struct {
switch (decl.data) {
.const_decl => |cd| {
if (std.mem.eql(u8, cd.name, name)) {
var compiler = Compiler.init(self.allocator, self.sema_result, self.root_decls, self.codegen);
const chunk = compiler.compile(cd.value) catch return error.CompileError;
const result = self.evalInFreshVM(&chunk) catch return error.CompileError;
try self.globals.put(name, result);
return result;
return self.compileAndEvalGlobal(name, cd.value);
}
},
.var_decl => |vd| {
if (std.mem.eql(u8, vd.name, name)) {
if (vd.value) |val_expr| {
var compiler = Compiler.init(self.allocator, self.sema_result, self.root_decls, self.codegen);
const chunk = compiler.compile(val_expr) catch return error.CompileError;
const result = self.evalInFreshVM(&chunk) catch return error.CompileError;
try self.globals.put(name, result);
return result;
return self.compileAndEvalGlobal(name, val_expr);
}
return .{ .void_val = {} };
}
@@ -1585,11 +1552,7 @@ pub const VM = struct {
// Check inside namespace for matching declarations
for (ns.decls) |d| {
if (d.data == .const_decl and std.mem.eql(u8, d.data.const_decl.name, name)) {
var compiler = Compiler.init(self.allocator, self.sema_result, self.root_decls, self.codegen);
const chunk = compiler.compile(d.data.const_decl.value) catch return error.CompileError;
const result = self.evalInFreshVM(&chunk) catch return error.CompileError;
try self.globals.put(name, result);
return result;
return self.compileAndEvalGlobal(name, d.data.const_decl.value);
}
}
},
@@ -1601,37 +1564,26 @@ pub const VM = struct {
for (self.root_decls) |decl| {
switch (decl.data) {
.struct_decl => |sd| {
if (std.mem.eql(u8, sd.name, name)) {
const val = Value{ .type_val = .{ .struct_type = name } };
try self.globals.put(name, val);
return val;
}
if (std.mem.eql(u8, sd.name, name))
return self.cacheTypeGlobal(name, .{ .struct_type = name });
},
.enum_decl => |ed| {
if (std.mem.eql(u8, ed.name, name)) {
const ty: Type = if (ed.variant_types.len > 0) .{ .union_type = name } else .{ .enum_type = name };
const val = Value{ .type_val = ty };
try self.globals.put(name, val);
return val;
return self.cacheTypeGlobal(name, ty);
}
},
.union_decl => |ud| {
if (std.mem.eql(u8, ud.name, name)) {
const val = Value{ .type_val = .{ .union_type = name } };
try self.globals.put(name, val);
return val;
}
if (std.mem.eql(u8, ud.name, name))
return self.cacheTypeGlobal(name, .{ .union_type = name });
},
else => {},
}
}
// Check if it's a primitive type name (s32, f64, bool, etc.)
if (Type.fromName(name)) |ty| {
const val = Value{ .type_val = ty };
try self.globals.put(name, val);
return val;
}
if (Type.fromName(name)) |ty|
return self.cacheTypeGlobal(name, ty);
return error.UndefinedVariable;
}