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: []const u8 = &.{}, identity: ?*const anyopaque = null, 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, .identity = &self.globals, .userdata = &.{}, .fields = self.globals, .behavior = &.{}, }; return object.behavior.caller(.{ .env = self, .args = arguments, .caller = &global_object, .callee = object, }); } pub fn check(self: Environment, condition: bool, failure_message: []const u8) RuntimeError!void { if (condition) return; // TODO: Emit failure message. _ = self; _ = failure_message; return error.CheckFailure; } 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, 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, options.stack_max); try environment.calls.grow(allocator, 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.len, self.allocator); errdefer coral.io.deallocate(self.allocator, userdata); coral.io.copy(userdata, options.userdata); object.* = .{ .userdata = userdata, .ref_count = 1, .identity = options.identity orelse userdata.ptr, .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) coral.io.AllocationError!*Object { // TODO: Implement. _ = self; _ = options; return error.OutOfMemory; } pub fn new_string(self: *Environment, string_data: []const u8) coral.io.AllocationError!*Object { const object = try self.new(.{ .userdata = string_data, .behavior = &.{}, }); errdefer self.release(object); return object; } pub fn new_string_from(self: *Environment, formats: []const coral.format.Value) coral.io.AllocationError!*Object { // TODO: Implement. coral.format.print(coral.io.null_writer, formats); return self.new_string(""); } 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_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 user_cast(self: *Environment, object: *Object, expected_identity: *const anyopaque, comptime CastTarget: type) RuntimeError!*CastTarget { // TODO: Emit failure message. _ = self; if (object.identity != expected_identity) { // Identity does not match what was expected. return error.InvalidOperation; } if (object.userdata.len != @sizeOf(CastTarget)) { // Userdata size does not match target type. return error.InvalidOperation; } return @ptrCast(*CastTarget, @alignCast(@alignOf(CastTarget), object.userdata)); } 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, identity: *const anyopaque, userdata: []u8, behavior: *const Behavior, fields: ValueTable, pub const Behavior = struct { caller: *const Caller = default_call, deinitialize: *const Deinitializer = default_deinitialize, getter: *const Getter = default_get, setter: *const Setter = default_set, fn default_call(_: CallContext) RuntimeError!Value { return error.InvalidOperation; } fn default_deinitialize(_: DeinitializeContext) void { // Nothing to deinitialize by default. } fn default_get(context: GetContext) RuntimeError!Value { switch (context.index) { .object => |index| { if (!index.is_string()) return error.InvalidOperation; return context.obj.fields.lookup(index.userdata) orelse .nil; }, .integer => |integer| { const index = context.env.new_string_from(&.{.{.signed = integer}}); defer context.env.release(index); return context.obj.fields.lookup(index.userdata) orelse .nil; }, else => return error.InvalidOperation, } } fn default_set(_: SetContext) RuntimeError!void { return error.InvalidOperation; } }; pub const CallContext = struct { env: *Environment, caller: *Object, callee: *Object, args: []const Value, }; pub const Caller = fn (context: CallContext) RuntimeError!Value; pub const DeinitializeContext = struct { env: *Environment, obj: *Object, }; pub const Deinitializer = fn (context: DeinitializeContext) void; pub const GetContext = struct { env: *Environment, obj: *const Object, index: Value, }; pub const Getter = fn (context: GetContext) RuntimeError!Value; pub const SetContext = struct { env: *Environment, obj: *Object, index: Value, value: Value, }; pub const Setter = fn (context: SetContext) RuntimeError!void; pub fn as_value(self: *Object) Value { return .{.object = self}; } pub fn is_string(self: Object) bool { // Userdata represents a string (of bytes) if it's identity is it's userdata. return self.identity == @ptrCast(*const anyopaque, self.userdata.ptr); } }; 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);