ona/source/kym/vm.zig

322 lines
7.6 KiB
Zig
Executable File

const coral = @import("coral");
pub const CompileError = coral.io.AllocationError || error {
};
pub const Environment = struct {
allocator: coral.io.Allocator,
globals: ValueTable,
stack: coral.stack.Dense(Value),
calls: coral.stack.Dense(CallFrame),
const CallFrame = struct {
object: ?*Object,
ip: usize,
slots: []Value,
};
pub const InitOptions = struct {
stack_max: u32,
calls_max: u32,
};
pub const NewOptions = struct {
userdata_size: usize = 0,
userinfo: usize = 0,
behavior: *const Object.Behavior = &.{},
};
pub const NewScriptOptions = struct {
name: []const u8,
data: []const u8,
};
pub fn call(self: *Environment, object: *Object, arguments: []const Value) RuntimeError!Value {
var global_object = Object{
.ref_count = 0,
.userinfo = 0,
.userdata = &.{},
.fields = self.globals,
.behavior = &.{},
};
return object.behavior.caller(.{
.environment = self,
.arguments = arguments,
.object = &global_object,
});
}
pub fn deinit(self: *Environment) void {
self.stack.deinit(self.allocator);
self.calls.deinit(self.allocator);
}
pub fn global_set(self: *Environment, global_name: []const u8, value: Value) coral.io.AllocationError!void {
try self.globals.assign(self.allocator, global_name, value);
}
pub fn init(allocator: coral.io.Allocator, init_options: InitOptions) !Environment {
var environment = Environment{
.allocator = allocator,
.globals = .{},
.stack = .{},
.calls = .{},
};
errdefer {
environment.stack.deinit(allocator);
environment.calls.deinit(allocator);
}
try environment.stack.grow(allocator, init_options.stack_max);
try environment.calls.grow(allocator, init_options.calls_max);
return environment;
}
pub fn new(self: *Environment, options: NewOptions) coral.io.AllocationError!*Object {
const object = try coral.io.allocate_one(Object, self.allocator);
errdefer coral.io.deallocate(self.allocator, object);
const userdata = try coral.io.allocate_many(u8, options.userdata_size, self.allocator);
errdefer coral.io.deallocate(self.allocator, userdata);
object.* = .{
.userdata = userdata,
.ref_count = 1,
.userinfo = options.userinfo,
.behavior = options.behavior,
.fields = .{},
};
return object;
}
pub fn new_array(self: *Environment) coral.io.AllocationError!*Object {
// TODO: Implement.
return self.new(.none, .{});
}
pub fn new_script(self: *Environment, options: NewScriptOptions) RuntimeError!*Object {
// TODO: Implement.
_ = self;
_ = options;
return error.OutOfMemory;
}
pub fn new_string(self: *Environment, string_data: []const u8) coral.io.AllocationError!*Object {
const string_behavior = &.{};
if (string_data.len == 0) {
// Empty string.
return self.new(.{.behavior = string_behavior});
}
const string_object = try self.new(.{
.userdata_size = string_data.len,
.behavior = string_behavior
});
errdefer self.release(string_object);
coral.io.copy(string_object.userdata, string_data);
return string_object;
}
pub fn new_string_from_float(self: *Environment, float: Float) coral.io.AllocationError!*Object {
// TODO: Implement.
_ = float;
return self.new_string("0.0");
}
pub fn new_string_from_integer(self: *Environment, integer: Integer) coral.io.AllocationError!*Object {
// TODO: Implement.
_ = integer;
return self.new_string("0");
}
pub fn new_string_from_object(self: *Environment, object: *Object) coral.io.AllocationError!*Object {
// TODO: Implement.
_ = object;
return self.new_string("");
}
pub fn new_table(self: *Environment) coral.io.AllocationError!*Object {
// TODO: Implement.
return self.new(.none, .{});
}
pub fn raw_get(_: Environment, object: *Object, field_name: []const u8) Value {
return object.fields.lookup(field_name) orelse .nil;
}
pub fn raw_set(environment: Environment, object: *Object, field_name: []const u8, value: Value) coral.io.AllocationError!void {
try object.fields.assign(environment.allocator, field_name, value);
}
pub fn release(self: Environment, object: *Object) void {
coral.debug.assert(object.ref_count != 0);
object.ref_count -= 1;
if (object.ref_count == 0) {
coral.io.deallocate(self.allocator, object.userdata);
coral.io.deallocate(self.allocator, object);
}
}
pub fn virtual_call(self: *Environment, object: *Object, index: Value, arguments: []const Value) RuntimeError!Value {
const value = try self.virtual_get(object, index);
switch (value) {
.object => |callee| {
defer self.release(callee);
return callee.behavior.caller(.{
.environment = self,
.object = callee,
.caller = object,
.arguments = arguments,
});
},
else => return error.InvalidOperation,
}
}
pub fn virtual_get(self: *Environment, object: *Object, index: Value) RuntimeError!Value {
return object.behavior.getter(.{
.environment = self,
.object = object,
.index = index,
});
}
pub fn virtual_set(self: *Environment, object: *Object, index: Value, value: Value) RuntimeError!void {
return object.behavior.setter(.{
.environment = self,
.object = object,
.index = index,
.value = value,
});
}
};
pub const Float = f32;
pub const Integer = i32;
pub const Object = struct {
ref_count: usize,
userinfo: usize,
userdata: []u8,
behavior: *const Behavior,
fields: ValueTable,
pub const Behavior = struct {
caller: *const Caller = default_call,
getter: *const Getter = default_get,
setter: *const Setter = default_set,
destructor: *const Destructor = default_destruct,
fn default_call(_: CallContext) RuntimeError!Value {
return error.InvalidOperation;
}
fn default_destruct(_: DestructContext) void {
}
fn default_get(context: GetContext) RuntimeError!Value {
const index = try switch (context.index) {
.object => |object| context.environment.new_string_from_object(object),
.integer => |integer| context.environment.new_string_from_integer(integer),
.float => |float| context.environment.new_string_from_float(float),
else => return error.InvalidOperation,
};
defer context.environment.release(index);
// A string is just a serious of bytes (i.e. userdata with no userinfo).
coral.debug.assert(index.userinfo == 0);
return context.object.fields.lookup(index.userdata) orelse .nil;
}
fn default_set(_: SetContext) RuntimeError!void {
return error.InvalidOperation;
}
};
pub const CallContext = struct {
environment: *Environment,
object: *Object,
arguments: []const Value,
pub fn check(self: CallContext, condition: bool, failure_message: []const u8) RuntimeError!void {
if (condition) return;
// TODO: Emit failure message.
_ = self;
_ = failure_message;
return error.CheckFailure;
}
};
pub const Caller = fn (context: CallContext) RuntimeError!Value;
pub const DestructContext = struct {
environment: *Environment,
object: *Object,
};
pub const Destructor = fn (context: DestructContext) void;
pub const GetContext = struct {
environment: *Environment,
object: *const Object,
index: Value,
};
pub const Getter = fn (context: GetContext) RuntimeError!Value;
pub const SetContext = struct {
environment: *Environment,
object: *Object,
index: Value,
value: Value,
};
pub const Setter = fn (context: SetContext) RuntimeError!void;
pub fn as_value(self: *Object) Value {
return .{.object = self};
}
};
pub const RuntimeError = coral.io.AllocationError || error {
InvalidOperation,
CheckFailure,
};
pub const Value = union(enum) {
nil,
false,
true,
float: Float,
integer: Integer,
object: *Object,
};
const ValueTable = coral.table.Hashed(coral.table.string_key, Value);