701 lines
16 KiB
Zig

const app = @import("../app.zig");
const Compiler = @import("./Compiler.zig");
const builtin = @import("builtin");
const coral = @import("coral");
const file = @import("../file.zig");
const kym = @import("../kym.zig");
const tree = @import("./tree.zig");
name: *kym.RuntimeRef,
arity: u8,
opcodes: OpcodeList,
constants: ConstList,
bindings: []?*kym.RuntimeRef,
const Box = struct {
};
const Builtin = enum {
import,
print,
vec2,
vec3,
};
const ConstList = coral.list.Stack(*kym.RuntimeRef);
const OpcodeList = coral.list.Stack(union (enum) {
ret,
pop,
push_nil,
push_true,
push_false,
push_const: u16,
push_local: u8,
push_top,
push_table: u32,
push_builtin: Builtin,
push_binding: u8,
push_boxed,
set_local: u8,
get_dynamic,
set_dynamic,
get_box,
set_box,
call: u8,
bind: u8,
not,
neg,
add,
sub,
mul,
div,
eql,
cgt,
clt,
cge,
cle,
jt: u32,
jf: u32,
});
const Self = @This();
pub fn dump(chunk: Self, env: *kym.RuntimeEnv) kym.RuntimeError!*kym.RuntimeRef {
var opcode_cursor = @as(u32, 0);
var buffer = coral.list.ByteStack.make(env.allocator);
defer buffer.free();
const writer = coral.list.stack_as_writer(&buffer);
_ = coral.utf8.print_string(writer, "\n");
while (opcode_cursor < chunk.opcodes.values.len) : (opcode_cursor += 1) {
_ = coral.utf8.print_formatted(writer, "[{instruction}]: ", .{.instruction = opcode_cursor});
_ = switch (chunk.opcodes.values[opcode_cursor]) {
.ret => coral.utf8.print_string(writer, "ret\n"),
.pop => coral.utf8.print_string(writer, "pop\n"),
.push_nil => coral.utf8.print_string(writer, "push nil\n"),
.push_true => coral.utf8.print_string(writer, "push true\n"),
.push_false => coral.utf8.print_string(writer, "push false\n"),
.push_const => |push_const| print: {
if (push_const >= chunk.constants.values.len) {
return env.raise(error.IllegalState, "invalid constant", .{});
}
const string_ref = try env.to_string(chunk.constants.values[push_const]);
defer env.discard(string_ref);
const string = string_ref.as_string();
coral.debug.assert(string != null);
break: print coral.utf8.print_formatted(writer, "push const <{value}>\n", .{.value = string.?});
},
.push_local => |push_local| coral.utf8.print_formatted(writer, "push local <{local}>\n", .{
.local = push_local,
}),
.push_top => coral.utf8.print_string(writer, "push top\n"),
.push_table => |push_table| coral.utf8.print_formatted(writer, "push table <{count}>\n", .{
.count = push_table,
}),
.push_boxed => coral.utf8.print_string(writer, "push boxed\n"),
.push_binding => |push_binding| coral.utf8.print_formatted(writer, "push binding <{binding}>\n", .{
.binding = push_binding,
}),
.push_builtin => |push_builtin| coral.utf8.print_formatted(writer, "push builtin <{builtin}>\n", .{
.builtin = switch (push_builtin) {
.import => "import",
.print => "print",
.vec2 => "vec2",
.vec3 => "vec3",
},
}),
.bind => |bind| coral.utf8.print_formatted(writer, "bind <{count}>\n", .{
.count = bind,
}),
.set_local => |local_set| coral.utf8.print_formatted(writer, "set local <{local}>\n", .{
.local = local_set,
}),
.get_box => coral.utf8.print_string(writer, "get box\n"),
.set_box => coral.utf8.print_string(writer, "set box\n"),
.get_dynamic => coral.utf8.print_string(writer, "get dynamic\n"),
.set_dynamic => coral.utf8.print_string(writer, "set dynamic\n"),
.call => |call| coral.utf8.print_formatted(writer, "call <{count}>\n", .{.count = call}),
.not => coral.utf8.print_string(writer, "not\n"),
.neg => coral.utf8.print_string(writer, "neg\n"),
.add => coral.utf8.print_string(writer, "add\n"),
.sub => coral.utf8.print_string(writer, "sub\n"),
.mul => coral.utf8.print_string(writer, "mul\n"),
.div => coral.utf8.print_string(writer, "div\n"),
.eql => coral.utf8.print_string(writer, "eql\n"),
.cgt => coral.utf8.print_string(writer, "cgt\n"),
.clt => coral.utf8.print_string(writer, "clt\n"),
.cge => coral.utf8.print_string(writer, "cge\n"),
.cle => coral.utf8.print_string(writer, "cle\n"),
.jf => |jf| coral.utf8.print_formatted(writer, "jf <{instruction}>\n", .{.instruction = jf}),
.jt => |jt| coral.utf8.print_formatted(writer, "jt <{instruction}>\n", .{.instruction = jt}),
};
}
return env.new_string(buffer.values);
}
pub fn execute(self: Self, env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeError!?*kym.RuntimeRef {
var opcode_cursor = @as(u32, 0);
while (opcode_cursor < self.opcodes.values.len) : (opcode_cursor += 1) {
switch (self.opcodes.values[opcode_cursor]) {
.ret => break,
.pop => {
if (try env.pop_local()) |ref| {
env.discard(ref);
}
},
.push_nil => try env.locals.push_one(null),
.push_true => try env.locals.push_one(try env.new_boolean(true)),
.push_false => try env.locals.push_one(try env.new_boolean(false)),
.push_const => |push_const| {
if (push_const >= self.constants.values.len) {
return env.raise(error.IllegalState, "invalid constant", .{});
}
try env.locals.push_one(self.constants.values[push_const].acquire());
},
.push_local => |push_local| {
if (push_local >= (env.locals.values.len - frame.locals_top)) {
return env.raise(error.IllegalState, "invalid local", .{});
}
if (env.locals.values[frame.locals_top + push_local]) |local| {
try env.locals.push_one(local.acquire());
} else {
try env.locals.push_one(null);
}
},
.push_top => {
const frame_locals = env.locals.values[frame.locals_top ..];
if (frame_locals.len == 0) {
return env.raise(error.IllegalState, "stack overflow", .{});
}
if (frame_locals[frame_locals.len - 1]) |local| {
try env.locals.push_one(local.acquire());
} else {
try env.locals.push_one(null);
}
},
.push_table => |push_table| {
const table = try env.new_table();
errdefer env.discard(table);
{
var popped = @as(usize, 0);
while (popped < push_table) : (popped += 1) {
const index = try env.expect(try env.pop_local());
defer env.discard(index);
if (try env.pop_local()) |value| {
defer env.discard(value);
try env.set(table, index, value);
}
}
}
try env.locals.push_one(table);
},
.push_boxed => {
const value = try env.pop_local();
defer {
if (value) |ref| {
env.discard(ref);
}
}
const boxable = try env.new_boxed(value);
errdefer env.discard(boxable);
try env.locals.push_one(boxable);
},
.push_binding => |push_binding| {
if (push_binding > self.bindings.len) {
return env.raise(error.IllegalState, "binding out of range", .{});
}
try env.locals.push_one(if (self.bindings[push_binding]) |value| value.acquire() else null);
},
.bind => |bind| {
const lambda = try env.expect(try env.pop_local());
errdefer env.discard(lambda);
const chunk = @as(*Self, @ptrCast(@alignCast(lambda.as_dynamic(typeinfo) orelse {
return env.raise(error.IllegalState, "cannot bind objects to a {typename}", .{
.typename = lambda.typename(),
});
})));
chunk.bindings = try coral.io.allocate_many(env.allocator, bind, @as(?*kym.RuntimeRef, null));
for (0 .. bind) |index| {
const value = try env.pop_local();
errdefer {
if (value) |ref| {
env.discard(ref);
}
}
const binding = &chunk.bindings[index];
if (binding.*) |*existing_binding| {
env.discard(existing_binding.*);
}
binding.* = value;
}
try env.locals.push_one(lambda);
},
.push_builtin => |push_builtin| {
const builtin_syscall = try env.new_syscall(switch (push_builtin) {
.import => syscall_import,
.print => syscall_print,
.vec2 => syscall_vec2,
.vec3 => syscall_vec3,
});
errdefer env.discard(builtin_syscall);
try env.locals.push_one(builtin_syscall);
},
.set_local => |local_set| {
const local = &env.locals.values[frame.locals_top + local_set];
if (local.*) |previous_local| {
env.discard(previous_local);
}
local.* = try env.pop_local();
},
.get_box => {
const box = try env.expect(try env.pop_local());
defer env.discard(box);
const boxed = box.as_boxed() orelse {
return env.raise(error.TypeMismatch, "{typename} is not unboxable", .{.typename = box.typename()});
};
try env.locals.push_one(if (boxed.*) |value| value.acquire() else null);
},
.set_box => {
const box = try env.expect(try env.pop_local());
errdefer env.discard(box);
const boxed = box.as_boxed() orelse {
return env.raise(error.TypeMismatch, "{typename} is not unboxable", .{.typename = box.typename()});
};
if (boxed.*) |value| {
env.discard(value);
}
boxed.* = box;
},
.get_dynamic => {
const index = try env.expect(try env.pop_local());
defer env.discard(index);
const indexable = try env.expect(try env.pop_local());
defer env.discard(indexable);
const value = try env.get(indexable, index);
errdefer {
if (value) |ref| {
env.discard(ref);
}
}
try env.locals.push_one(value);
},
.set_dynamic => {
const value = try env.pop_local();
defer {
if (value) |ref| {
env.discard(ref);
}
}
const index = try env.expect(try env.pop_local());
defer env.discard(index);
const indexable = try env.expect(try env.pop_local());
defer env.discard(indexable);
try env.set(indexable, index, value);
},
.call => |call| {
const result = call: {
const callable = try env.expect(try env.pop_local());
defer env.discard(callable);
const call_frame = try env.push_frame(callable, call);
defer env.pop_frame();
break: call try env.call_frame(callable, call_frame);
};
errdefer {
if (result) |ref| {
env.discard(ref);
}
}
try env.locals.push_one(result);
},
.not => {
if (try env.pop_local()) |value| {
defer env.discard(value);
try env.locals.push_one(try env.new_boolean(!value.is_truthy()));
} else {
try env.locals.push_one(try env.new_boolean(true));
}
},
.neg => {
const value = try env.expect(try env.pop_local());
defer env.discard(value);
try env.locals.push_one(try env.neg(value));
},
.add => {
const rhs = try env.expect(try env.pop_local());
defer env.discard(rhs);
const lhs = try env.expect(try env.pop_local());
defer env.discard(lhs);
try env.locals.push_one(try env.add(lhs, rhs));
},
.sub => {
const rhs = try env.expect(try env.pop_local());
defer env.discard(rhs);
const lhs = try env.expect(try env.pop_local());
defer env.discard(lhs);
try env.locals.push_one(try env.sub(lhs, rhs));
},
.mul => {
const rhs = try env.expect(try env.pop_local());
defer env.discard(rhs);
const lhs = try env.expect(try env.pop_local());
defer env.discard(lhs);
try env.locals.push_one(try env.mul(lhs, rhs));
},
.div => {
const rhs = try env.expect(try env.pop_local());
defer env.discard(rhs);
const lhs = try env.expect(try env.pop_local());
defer env.discard(lhs);
try env.locals.push_one(try env.div(lhs, rhs));
},
.eql => {
if (try env.pop_local()) |rhs| {
env.discard(rhs);
if (try env.pop_local()) |lhs| {
env.discard(lhs);
try env.locals.push_one(try env.new_boolean(lhs.equals(rhs)));
} else {
try env.locals.push_one(try env.new_boolean(false));
}
} else {
if (try env.pop_local()) |lhs| {
env.discard(lhs);
try env.locals.push_one(try env.new_boolean(false));
} else {
try env.locals.push_one(try env.new_boolean(true));
}
}
},
.cgt => {
const rhs = try env.expect(try env.pop_local());
defer env.discard(rhs);
const lhs = try env.expect(try env.pop_local());
defer env.discard(lhs);
try env.locals.push_one(try env.new_boolean(try env.compare(lhs, rhs) > 0));
},
.clt => {
const rhs = try env.expect(try env.pop_local());
defer env.discard(rhs);
const lhs = try env.expect(try env.pop_local());
defer env.discard(lhs);
try env.locals.push_one(try env.new_boolean(try env.compare(lhs, rhs) < 0));
},
.cge => {
const rhs = try env.expect(try env.pop_local());
defer env.discard(rhs);
const lhs = try env.expect(try env.pop_local());
defer env.discard(lhs);
try env.locals.push_one(try env.new_boolean(try env.compare(lhs, rhs) >= 0));
},
.cle => {
const rhs = try env.expect(try env.pop_local());
defer env.discard(rhs);
const lhs = try env.expect(try env.pop_local());
defer env.discard(lhs);
try env.locals.push_one(try env.new_boolean(try env.compare(lhs, rhs) <= 0));
},
.jf => |jf| {
if (try env.pop_local()) |condition| {
defer env.discard(condition);
if (!condition.is_truthy()) {
opcode_cursor = jf;
}
} else {
opcode_cursor = jf;
}
},
.jt => |jt| {
if (try env.pop_local()) |condition| {
defer env.discard(condition);
if (condition.is_truthy()) {
opcode_cursor = jt;
}
}
},
}
}
return env.pop_local();
}
pub fn free(self: *Self, env: *kym.RuntimeEnv) void {
while (self.constants.pop()) |constant| {
env.discard(constant);
}
self.constants.free();
self.opcodes.free();
env.discard(self.name);
if (self.bindings.len != 0) {
for (self.bindings) |binding| {
if (binding) |value| {
env.discard(value);
}
}
env.allocator.deallocate(self.bindings);
}
self.bindings = &.{};
}
pub fn make(env: *kym.RuntimeEnv, name: *const kym.RuntimeRef, environment: *const tree.Environment) kym.RuntimeError!Self {
var chunk = Self{
.name = name.acquire(),
.opcodes = OpcodeList.make(env.allocator),
.constants = ConstList.make(env.allocator),
.bindings = &.{},
.arity = environment.argument_count,
};
var compiler = Compiler{
.chunk = &chunk,
.env = env,
};
try compiler.compile_environment(environment);
if (builtin.mode == .Debug) {
const name_string = try env.to_string(name);
defer env.discard(name_string);
const name_bytes = name_string.as_string();
coral.debug.assert(name_bytes != null);
const allocation = try coral.utf8.alloc_formatted(env.allocator, "compiled {name}...", .{.name = name_bytes.?});
defer env.allocator.deallocate(allocation);
app.log_info(allocation);
const string_ref = try chunk.dump(env);
defer env.discard(string_ref);
const string = string_ref.as_string();
coral.debug.assert(string != null);
app.log_info(string.?);
}
return chunk;
}
fn syscall_import(env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeError!?*kym.RuntimeRef {
return env.import(file.Path.from(&.{try env.unwrap_string(try frame.expect_arg(env, 0))}));
}
fn syscall_print(env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeError!?*kym.RuntimeRef {
env.print(try env.unwrap_string(try frame.expect_arg(env, 0)));
return null;
}
fn syscall_vec2(env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeError!?*kym.RuntimeRef {
const x = @as(f32, @floatCast(try env.unwrap_float(try frame.expect_arg(env, 0))));
if (frame.has_arg(env, 1)) |y| {
return env.new_vector2(x, @floatCast(try env.unwrap_float(y)));
}
return env.new_vector2(x, x);
}
fn syscall_vec3(env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeError!?*kym.RuntimeRef {
const x = @as(f32, @floatCast(try env.unwrap_float(try frame.expect_arg(env, 0))));
if (frame.has_arg(env, 1)) |y| {
return env.new_vector3(
x,
@floatCast(try env.unwrap_float(y)),
@floatCast(try env.unwrap_float(try frame.expect_arg(env, 2))),
);
}
return env.new_vector3(x, x, x);
}
pub const typeinfo = &kym.Typeinfo{
.size = @sizeOf(Self),
.name = "func",
.destruct = typeinfo_destruct,
.call = typeinfo_call,
};
fn typeinfo_call(env: *kym.RuntimeEnv, userdata: []coral.io.Byte, frame: kym.Frame) kym.RuntimeError!?*kym.RuntimeRef {
const chunk = @as(*Self, @ptrCast(@alignCast(userdata)));
if (frame.arg_count < chunk.arity) {
return env.raise(error.BadOperation, "expected `{expected_count}` {noun}, {provided_count} provided", .{
.expected_count = frame.arg_count,
.provided_count = chunk.arity,
.noun = if (frame.arg_count == 1) "argument" else "arguments",
});
}
return chunk.execute(env, frame);
}
fn typeinfo_destruct(env: *kym.RuntimeEnv, userdata: []coral.io.Byte) void {
@as(*Self, @ptrCast(@alignCast(userdata))).free(env);
}