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