594 lines
16 KiB
Zig
594 lines
16 KiB
Zig
const Ast = @import("./kym/Ast.zig");
|
|
|
|
const Chunk = @import("./kym/Chunk.zig");
|
|
|
|
const Table = @import("./kym/Table.zig");
|
|
|
|
const coral = @import("coral");
|
|
|
|
const file = @import("./file.zig");
|
|
|
|
pub const Any = union (enum) {
|
|
boolean: bool,
|
|
fixed: Fixed,
|
|
float: Float,
|
|
string: []const coral.io.Byte,
|
|
symbol: []const coral.io.Byte,
|
|
lambda,
|
|
dynamic: *const DynamicObject,
|
|
|
|
pub fn expect_dynamic(self: Any) ?*const DynamicObject {
|
|
return switch (self) {
|
|
.dynamic => |dynamic| dynamic,
|
|
else => null,
|
|
};
|
|
}
|
|
|
|
pub fn expect_number(self: Any) ?Float {
|
|
return switch (self) {
|
|
.fixed => |fixed| @floatFromInt(fixed),
|
|
.float => |float| float,
|
|
else => null,
|
|
};
|
|
}
|
|
|
|
pub fn expect_string(self: Any) ?[]const coral.io.Byte {
|
|
return switch (self) {
|
|
.string => |string| string,
|
|
else => null,
|
|
};
|
|
}
|
|
};
|
|
|
|
pub const Caller = coral.io.Generator(RuntimeError!?*RuntimeRef, *RuntimeEnv);
|
|
|
|
pub const DynamicObject = struct {
|
|
userdata: []coral.io.Byte,
|
|
typeinfo: *const Typeinfo,
|
|
|
|
fn call(self: *DynamicObject, env: *RuntimeEnv) RuntimeError!?*RuntimeRef {
|
|
return self.typeinfo.call(.{
|
|
.env = env,
|
|
.userdata = self.userdata,
|
|
});
|
|
}
|
|
};
|
|
|
|
pub const ErrorHandler = coral.io.Generator(void, ErrorInfo);
|
|
|
|
pub const ErrorInfo = struct {
|
|
message: []const coral.io.Byte,
|
|
frames: []const Frame,
|
|
};
|
|
|
|
pub const Fixed = i32;
|
|
|
|
pub const Float = f64;
|
|
|
|
pub const Frame = struct {
|
|
name: []const coral.io.Byte,
|
|
arg_count: u8,
|
|
locals_top: usize,
|
|
};
|
|
|
|
pub const Method = struct {
|
|
env: *RuntimeEnv,
|
|
userdata: []coral.io.Byte,
|
|
};
|
|
|
|
pub const RuntimeEnv = struct {
|
|
interned_symbols: RefTable,
|
|
allocator: coral.io.Allocator,
|
|
error_handler: ErrorHandler,
|
|
syscallables: RefTable,
|
|
local_refs: RefStack,
|
|
frames: FrameStack,
|
|
ref_values: RefSlab,
|
|
|
|
const FrameStack = coral.list.Stack(Frame);
|
|
|
|
const RefStack = coral.list.Stack(?*RuntimeRef);
|
|
|
|
const RefTable = coral.map.StringTable(*RuntimeRef);
|
|
|
|
const RefSlab = coral.map.Slab(struct {
|
|
ref_count: usize,
|
|
|
|
object: union (enum) {
|
|
false,
|
|
true,
|
|
float: Float,
|
|
fixed: Fixed,
|
|
string: []coral.io.Byte,
|
|
symbol: []coral.io.Byte,
|
|
lambda: Caller,
|
|
dynamic: *DynamicObject,
|
|
},
|
|
});
|
|
|
|
pub fn acquire(self: *RuntimeEnv, ref: *const RuntimeRef) *RuntimeRef {
|
|
const key = @intFromPtr(ref);
|
|
var ref_data = self.ref_values.remove(key);
|
|
|
|
coral.debug.assert(ref_data != null);
|
|
|
|
ref_data.?.ref_count += 1;
|
|
|
|
coral.debug.assert(self.ref_values.insert_at(key, ref_data.?));
|
|
|
|
return @ptrFromInt(key);
|
|
}
|
|
|
|
pub fn call(
|
|
self: *RuntimeEnv,
|
|
callable_ref: *const RuntimeRef,
|
|
arg_count: u8,
|
|
name: []const coral.io.Byte,
|
|
) RuntimeError!?*RuntimeRef {
|
|
try self.frames.push_one(.{
|
|
.name = name,
|
|
.arg_count = arg_count,
|
|
.locals_top = self.local_refs.values.len,
|
|
});
|
|
|
|
defer {
|
|
const frame = self.frames.pop() orelse unreachable;
|
|
|
|
{
|
|
var pops_remaining = (self.local_refs.values.len - frame.locals_top) + frame.arg_count;
|
|
|
|
while (pops_remaining != 0) : (pops_remaining -= 1) {
|
|
self.discard(self.local_refs.pop() orelse unreachable);
|
|
}
|
|
}
|
|
}
|
|
|
|
return switch ((self.ref_values.lookup(@intFromPtr(callable_ref)) orelse unreachable).object) {
|
|
.lambda => |lambda| lambda.invoke(self),
|
|
|
|
.dynamic => |dynamic| dynamic.typeinfo.call(.{
|
|
.env = self,
|
|
.userdata = dynamic.userdata,
|
|
}),
|
|
|
|
else => self.raise(error.TypeMismatch, "object is not callable"),
|
|
};
|
|
}
|
|
|
|
pub fn bind_syscaller(self: *RuntimeEnv, comptime name: []const coral.io.Byte, caller: Caller) RuntimeError!void {
|
|
const lambda_ref = try self.new_lambda(caller);
|
|
|
|
if (try self.syscallables.replace(name, self.acquire(lambda_ref))) |replaced_entry| {
|
|
self.discard(replaced_entry.value);
|
|
}
|
|
}
|
|
|
|
pub fn discard(self: *RuntimeEnv, ref: ?*RuntimeRef) void {
|
|
const key = @intFromPtr(ref orelse return);
|
|
var ref_data = self.ref_values.remove(key) orelse unreachable;
|
|
|
|
coral.debug.assert(ref_data.ref_count != 0);
|
|
|
|
ref_data.ref_count -= 1;
|
|
|
|
if (ref_data.ref_count == 0) {
|
|
switch (ref_data.object) {
|
|
.false, .true, .float, .fixed, .lambda => {},
|
|
.string => |string| self.allocator.deallocate(string),
|
|
.symbol => |symbol| self.allocator.deallocate(symbol),
|
|
|
|
.dynamic => |dynamic| {
|
|
dynamic.typeinfo.clean(.{
|
|
.env = self,
|
|
.userdata = dynamic.userdata,
|
|
});
|
|
|
|
self.allocator.deallocate(dynamic.userdata);
|
|
self.allocator.deallocate(dynamic);
|
|
},
|
|
}
|
|
} else {
|
|
coral.debug.assert(self.ref_values.insert_at(key, ref_data));
|
|
}
|
|
}
|
|
|
|
pub fn execute_file(self: *RuntimeEnv, file_access: file.Access, file_path: file.Path) RuntimeError!?*RuntimeRef {
|
|
if ((try file.allocate_and_load(self.allocator, file_access, file_path))) |file_data| {
|
|
defer self.allocator.deallocate(file_data);
|
|
|
|
if (file_path.to_string()) |string| {
|
|
return self.execute_script(string, file_data);
|
|
}
|
|
}
|
|
|
|
return self.raise(error.BadOperation, "failed to load file");
|
|
}
|
|
|
|
pub fn execute_script(
|
|
self: *RuntimeEnv,
|
|
name: []const coral.io.Byte,
|
|
data: []const coral.io.Byte,
|
|
) RuntimeError!?*RuntimeRef {
|
|
var ast = Ast.make(self.allocator, name);
|
|
|
|
defer ast.free();
|
|
|
|
ast.parse(data) catch |parse_error| switch (parse_error) {
|
|
error.BadSyntax => return self.raise(error.BadSyntax, ast.error_message()),
|
|
error.OutOfMemory => return error.OutOfMemory,
|
|
};
|
|
|
|
var chunk = Chunk.make(self);
|
|
|
|
defer chunk.free();
|
|
|
|
try chunk.compile_ast(ast);
|
|
|
|
const chunk_ref = try self.new_lambda(chunk.as_caller());
|
|
|
|
defer self.discard(chunk_ref);
|
|
|
|
return self.call(chunk_ref, 0, name);
|
|
}
|
|
|
|
pub fn free(self: *RuntimeEnv) void {
|
|
while (self.local_refs.pop()) |ref| {
|
|
self.discard(ref);
|
|
}
|
|
|
|
{
|
|
var iterable = self.interned_symbols.as_iterable();
|
|
|
|
while (iterable.next()) |entry| {
|
|
self.discard(entry.value);
|
|
}
|
|
}
|
|
|
|
{
|
|
var iterable = self.syscallables.as_iterable();
|
|
|
|
while (iterable.next()) |entry| {
|
|
self.discard(entry.value);
|
|
}
|
|
}
|
|
|
|
self.frames.free();
|
|
self.local_refs.free();
|
|
self.syscallables.free();
|
|
self.ref_values.free();
|
|
self.interned_symbols.free();
|
|
}
|
|
|
|
pub fn get_dynamic(
|
|
self: *RuntimeEnv,
|
|
indexable: *const DynamicObject,
|
|
index_ref: *const RuntimeRef,
|
|
) RuntimeError!?*RuntimeRef {
|
|
return indexable.typeinfo.get(.{
|
|
.env = self,
|
|
.userdata = indexable.userdata,
|
|
}, index_ref);
|
|
}
|
|
|
|
pub fn get_local(self: *RuntimeEnv, local: u8) RuntimeError!?*RuntimeRef {
|
|
if (local >= self.local_refs.values.len) {
|
|
return self.raise(error.IllegalState, "out of bounds local get");
|
|
}
|
|
|
|
return self.acquire(self.local_refs.values[local] orelse return null);
|
|
}
|
|
|
|
pub fn get_syscallable(self: *RuntimeEnv, name: []const coral.io.Byte) ?*RuntimeRef {
|
|
if (self.syscallables.lookup(name)) |system_ref| {
|
|
return self.acquire(system_ref);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
pub fn pop_local(self: *RuntimeEnv) RuntimeError!?*RuntimeRef {
|
|
return self.local_refs.pop() orelse self.raise(error.IllegalState, "stack underflow");
|
|
}
|
|
|
|
pub fn push_boolean(self: *RuntimeEnv, boolean: bool) RuntimeError!void {
|
|
return self.local_refs.push_one(try self.new_boolean(boolean));
|
|
}
|
|
|
|
pub fn push_fixed(self: *RuntimeEnv, fixed: Fixed) RuntimeError!void {
|
|
return self.local_refs.push_one(try self.new_fixed(fixed));
|
|
}
|
|
|
|
pub fn push_float(self: *RuntimeEnv, float: Float) RuntimeError!void {
|
|
return self.local_refs.push_one(try self.new_float(float));
|
|
}
|
|
|
|
pub fn push_ref(self: *RuntimeEnv, ref: ?*RuntimeRef) RuntimeError!void {
|
|
return self.local_refs.push_one(if (ref) |live_ref| self.acquire(live_ref) else null);
|
|
}
|
|
|
|
pub fn set_local(self: *RuntimeEnv, local: u8, ref: ?*RuntimeRef) RuntimeError!void {
|
|
if (local >= self.local_refs.values.len) {
|
|
return self.raise(error.IllegalState, "out of bounds local set");
|
|
}
|
|
|
|
self.local_refs.values[local] = if (ref) |live_ref| self.acquire(live_ref) else null;
|
|
}
|
|
|
|
pub fn make(allocator: coral.io.Allocator, error_handler: ErrorHandler) coral.io.AllocationError!RuntimeEnv {
|
|
return RuntimeEnv{
|
|
.local_refs = RefStack.make(allocator),
|
|
.ref_values = RefSlab.make(allocator),
|
|
.frames = FrameStack.make(allocator),
|
|
.syscallables = RefTable.make(allocator, .{}),
|
|
.interned_symbols = RefTable.make(allocator, .{}),
|
|
.error_handler = error_handler,
|
|
.allocator = allocator,
|
|
};
|
|
}
|
|
|
|
pub fn new_boolean(self: *RuntimeEnv, value: bool) RuntimeError!*RuntimeRef {
|
|
return @ptrFromInt(try self.ref_values.insert(.{
|
|
.ref_count = 1,
|
|
.object = if (value) .true else .false,
|
|
}));
|
|
}
|
|
|
|
pub fn new_dynamic(
|
|
self: *RuntimeEnv,
|
|
userdata: []const coral.io.Byte,
|
|
typeinfo: *const Typeinfo,
|
|
) RuntimeError!*RuntimeRef {
|
|
const userdata_copy = try coral.io.allocate_copy(self.allocator, userdata);
|
|
|
|
errdefer self.allocator.deallocate(userdata_copy);
|
|
|
|
const dynamic_object = try coral.io.allocate_one(self.allocator, DynamicObject{
|
|
.typeinfo = typeinfo,
|
|
.userdata = userdata_copy,
|
|
});
|
|
|
|
errdefer self.allocator.deallocate(dynamic_object);
|
|
|
|
return @ptrFromInt(try self.ref_values.insert(.{
|
|
.ref_count = 1,
|
|
.object = .{.dynamic = dynamic_object},
|
|
}));
|
|
}
|
|
|
|
pub fn new_fixed(self: *RuntimeEnv, value: Fixed) RuntimeError!*RuntimeRef {
|
|
return @ptrFromInt(try self.ref_values.insert(.{
|
|
.ref_count = 1,
|
|
.object = .{.fixed = value},
|
|
}));
|
|
}
|
|
|
|
pub fn new_float(self: *RuntimeEnv, value: Float) RuntimeError!*RuntimeRef {
|
|
return @ptrFromInt(try self.ref_values.insert(.{
|
|
.ref_count = 1,
|
|
.object = .{.float = value},
|
|
}));
|
|
}
|
|
|
|
pub fn new_lambda(self: *RuntimeEnv, caller: Caller) RuntimeError!*RuntimeRef {
|
|
return @ptrFromInt(try self.ref_values.insert(.{
|
|
.ref_count = 1,
|
|
.object = .{.lambda = caller},
|
|
}));
|
|
}
|
|
|
|
pub fn new_string(self: *RuntimeEnv, string_data: []const coral.io.Byte) RuntimeError!*RuntimeRef {
|
|
const string_copy = try coral.io.allocate_copy(self.allocator, string_data);
|
|
|
|
errdefer self.allocator.deallocate(string_copy);
|
|
|
|
return @ptrFromInt(try self.ref_values.insert(.{
|
|
.ref_count = 1,
|
|
.object = .{.string = string_copy},
|
|
}));
|
|
}
|
|
|
|
pub fn new_symbol(self: *RuntimeEnv, symbol_data: []const coral.io.Byte) RuntimeError!*RuntimeRef {
|
|
if (self.interned_symbols.lookup(symbol_data)) |symbol_ref| {
|
|
return self.acquire(symbol_ref);
|
|
} else {
|
|
const symbol_copy = try coral.io.allocate_copy(self.allocator, symbol_data);
|
|
|
|
const symbol_ref = @as(*RuntimeRef, new: {
|
|
errdefer self.allocator.deallocate(symbol_copy);
|
|
|
|
break: new @ptrFromInt(try self.ref_values.insert(.{
|
|
.ref_count = 1,
|
|
.object = .{.symbol = symbol_copy},
|
|
}));
|
|
});
|
|
|
|
errdefer self.discard(symbol_ref);
|
|
|
|
coral.debug.assert(try self.interned_symbols.insert(symbol_copy, symbol_ref));
|
|
|
|
return self.acquire(symbol_ref);
|
|
}
|
|
}
|
|
|
|
pub fn raise(self: *RuntimeEnv, error_value: RuntimeError, message: []const coral.io.Byte) RuntimeError {
|
|
self.error_handler.invoke(.{
|
|
.message = message,
|
|
.frames = self.frames.values,
|
|
});
|
|
|
|
return error_value;
|
|
}
|
|
|
|
pub fn set_dynamic(
|
|
self: *RuntimeEnv,
|
|
indexable: *const DynamicObject,
|
|
index_ref: *const RuntimeRef,
|
|
value_ref: ?*const RuntimeRef,
|
|
) RuntimeError!void {
|
|
return indexable.typeinfo.set(.{
|
|
.env = self,
|
|
.userdata = indexable.userdata,
|
|
}, index_ref, value_ref);
|
|
}
|
|
|
|
pub fn unbox(self: *RuntimeEnv, ref: *const RuntimeRef) Any {
|
|
return switch ((self.ref_values.lookup(@intFromPtr(ref)) orelse unreachable).object) {
|
|
.false => .{.boolean = false},
|
|
.true => .{.boolean = true},
|
|
.fixed => |fixed| .{.fixed = fixed},
|
|
.float => |float| .{.float = float},
|
|
.string => |string| .{.string = string},
|
|
.symbol => |symbol| .{.symbol = symbol},
|
|
.lambda => .lambda,
|
|
.dynamic => |dynamic| .{.dynamic = dynamic},
|
|
};
|
|
}
|
|
|
|
pub fn view_arg(self: *RuntimeEnv, index: usize) RuntimeError!?*const RuntimeRef {
|
|
const frame = self.frames.peek() orelse return self.raise(error.IllegalState, "stack underflow");
|
|
|
|
if (index >= frame.arg_count) {
|
|
return null;
|
|
}
|
|
|
|
return self.local_refs.values[frame.locals_top - (1 + index)];
|
|
}
|
|
};
|
|
|
|
pub const RuntimeError = coral.io.AllocationError || error {
|
|
IllegalState,
|
|
TypeMismatch,
|
|
BadOperation,
|
|
BadSyntax,
|
|
};
|
|
|
|
pub const RuntimeRef = opaque {};
|
|
|
|
pub const Typeinfo = struct {
|
|
name: []const coral.io.Byte,
|
|
call: *const fn (method: Method) RuntimeError!?*RuntimeRef = default_call,
|
|
clean: *const fn (method: Method) void = default_clean,
|
|
get: *const fn (method: Method, index: *const RuntimeRef) RuntimeError!?*RuntimeRef = default_get,
|
|
set: *const fn (method: Method, index: *const RuntimeRef, value: ?*const RuntimeRef) RuntimeError!void = default_set,
|
|
|
|
fn default_call(method: Method) RuntimeError!?*RuntimeRef {
|
|
return method.env.raise(error.TypeMismatch, "object is not callable");
|
|
}
|
|
|
|
fn default_clean(_: Method) void {
|
|
// Nothing to clean by default.
|
|
}
|
|
|
|
fn default_get(method: Method, _: *const RuntimeRef) RuntimeError!?*RuntimeRef {
|
|
return method.env.raise(error.TypeMismatch, "object is not indexable");
|
|
}
|
|
|
|
fn default_set(method: Method, _: *const RuntimeRef, _: ?*const RuntimeRef) RuntimeError!void {
|
|
return method.env.raise(error.TypeMismatch, "object is not indexable");
|
|
}
|
|
};
|
|
|
|
pub fn get_field(
|
|
env: *RuntimeEnv,
|
|
indexable_ref: *const RuntimeRef,
|
|
field_name: []const coral.io.Byte,
|
|
) RuntimeError!?*RuntimeRef {
|
|
const field_name_ref = try env.new_symbol(field_name);
|
|
|
|
defer env.discard(field_name_ref);
|
|
|
|
return env.get_dynamic(try unbox_dynamic(env, indexable_ref), field_name_ref);
|
|
}
|
|
|
|
pub fn hash(env: *RuntimeEnv, ref: *const RuntimeRef) usize {
|
|
return switch (env.unbox(ref)) {
|
|
.boolean => 0,
|
|
.float => 0,
|
|
.fixed => 0,
|
|
.string => |string| coral.io.djb2_hash(@typeInfo(usize).Int, string),
|
|
.symbol => 0,
|
|
.lambda => 0,
|
|
.dynamic => 0,
|
|
};
|
|
}
|
|
|
|
pub const new_table = Table.new;
|
|
|
|
pub fn test_difference(
|
|
env: *RuntimeEnv,
|
|
lhs_comparable_ref: *const RuntimeRef,
|
|
rhs_comparable_ref: *const RuntimeRef,
|
|
) RuntimeError!Float {
|
|
return switch (env.unbox(lhs_comparable_ref)) {
|
|
.boolean => env.raise(error.TypeMismatch, "boolean objects are not comparable"),
|
|
|
|
.fixed => |lhs_fixed| switch (env.unbox(rhs_comparable_ref)) {
|
|
.fixed => |rhs_fixed| @as(Float, @floatFromInt(rhs_fixed)) - @as(Float, @floatFromInt(lhs_fixed)),
|
|
else => env.raise(error.TypeMismatch, "right-hand object is not comparable with fixed objects"),
|
|
},
|
|
|
|
.float => |lhs_float| switch (env.unbox(rhs_comparable_ref)) {
|
|
.float => |rhs_float| rhs_float - lhs_float,
|
|
else => env.raise(error.TypeMismatch, "right-hand object is not comparable with float objects"),
|
|
},
|
|
|
|
.string => |lhs_string| switch (env.unbox(rhs_comparable_ref)) {
|
|
.string => |rhs_string| @floatFromInt(coral.io.compare(lhs_string, rhs_string)),
|
|
.symbol => |rhs_symbol| @floatFromInt(coral.io.compare(lhs_string, rhs_symbol)),
|
|
else => env.raise(error.TypeMismatch, "right-hand object is not comparable with string objects"),
|
|
},
|
|
|
|
.symbol => |lhs_symbol| switch (env.unbox(rhs_comparable_ref)) {
|
|
.symbol => env.raise(error.TypeMismatch, "cannot compare symbol objects"),
|
|
.string => |rhs_string| @floatFromInt(coral.io.compare(lhs_symbol, rhs_string)),
|
|
else => env.raise(error.TypeMismatch, "right-hand object is not comparable with symbol objects"),
|
|
},
|
|
|
|
.lambda => env.raise(error.TypeMismatch, "lambda objects are not comparable"),
|
|
.dynamic => env.raise(error.TypeMismatch, "dynamic objects are not comparable"),
|
|
};
|
|
}
|
|
|
|
pub fn test_equality(env: *RuntimeEnv, lhs_ref: *const RuntimeRef, rhs_ref: *const RuntimeRef) bool {
|
|
return switch (env.unbox(lhs_ref)) {
|
|
.boolean => |lhs_boolean| switch (env.unbox(rhs_ref)) {
|
|
.boolean => |rhs_boolean| rhs_boolean == lhs_boolean,
|
|
else => false,
|
|
},
|
|
|
|
.fixed => |lhs_fixed| switch (env.unbox(rhs_ref)) {
|
|
.fixed => |rhs_fixed| rhs_fixed == lhs_fixed,
|
|
.float => |rhs_float| rhs_float == @as(Float, @floatFromInt(lhs_fixed)),
|
|
else => false,
|
|
},
|
|
|
|
.float => |lhs_float| switch (env.unbox(rhs_ref)) {
|
|
.float => |rhs_float| rhs_float == lhs_float,
|
|
.fixed => |rhs_fixed| @as(Float, @floatFromInt(rhs_fixed)) == lhs_float,
|
|
else => false,
|
|
},
|
|
|
|
.string => |lhs_string| switch (env.unbox(rhs_ref)) {
|
|
.string => |rhs_string| coral.io.equals(lhs_string, rhs_string),
|
|
.symbol => |rhs_symbol| coral.io.equals(lhs_string, rhs_symbol),
|
|
else => false,
|
|
},
|
|
|
|
.symbol => |lhs_symbol| switch (env.unbox(rhs_ref)) {
|
|
.symbol => |rhs_symbol| lhs_symbol.ptr == rhs_symbol.ptr,
|
|
.string => |rhs_string| coral.io.equals(lhs_symbol, rhs_string),
|
|
else => false,
|
|
},
|
|
|
|
else => lhs_ref == rhs_ref,
|
|
};
|
|
}
|
|
|
|
pub fn unbox_dynamic(env: *RuntimeEnv, ref: *const RuntimeRef) RuntimeError!*const DynamicObject {
|
|
return env.unbox(ref).expect_dynamic() orelse env.raise(error.TypeMismatch, "expected dynamic object");
|
|
}
|
|
|
|
pub fn unbox_string(env: *RuntimeEnv, ref: *const RuntimeRef) RuntimeError![]const coral.io.Byte {
|
|
return env.unbox(ref).expect_string() orelse env.raise(error.TypeMismatch, "expected string object");
|
|
}
|