ona/source/ona/kym.zig
kayomn 1f8f3fd9dc
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Consolidate syscalls and calls into new expressions tree
2023-07-31 02:29:50 +01:00

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");
}