2023-05-06 03:47:52 +02:00
|
|
|
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 {
|
2023-05-10 00:57:19 +02:00
|
|
|
userdata: []const u8 = &.{},
|
|
|
|
identity: ?*const anyopaque = null,
|
2023-05-06 03:47:52 +02:00
|
|
|
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,
|
2023-05-10 00:57:19 +02:00
|
|
|
.identity = &self.globals,
|
2023-05-06 03:47:52 +02:00
|
|
|
.userdata = &.{},
|
|
|
|
.fields = self.globals,
|
|
|
|
.behavior = &.{},
|
|
|
|
};
|
|
|
|
|
|
|
|
return object.behavior.caller(.{
|
2023-05-10 00:57:19 +02:00
|
|
|
.env = self,
|
|
|
|
.args = arguments,
|
|
|
|
.caller = &global_object,
|
|
|
|
.callee = object,
|
2023-05-06 03:47:52 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-05-10 00:57:19 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2023-05-06 03:47:52 +02:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2023-05-10 00:57:19 +02:00
|
|
|
pub fn init(allocator: coral.io.Allocator, options: InitOptions) !Environment {
|
2023-05-06 03:47:52 +02:00
|
|
|
var environment = Environment{
|
|
|
|
.allocator = allocator,
|
|
|
|
.globals = .{},
|
|
|
|
.stack = .{},
|
|
|
|
.calls = .{},
|
|
|
|
};
|
|
|
|
|
|
|
|
errdefer {
|
|
|
|
environment.stack.deinit(allocator);
|
|
|
|
environment.calls.deinit(allocator);
|
|
|
|
}
|
|
|
|
|
2023-05-10 00:57:19 +02:00
|
|
|
try environment.stack.grow(allocator, options.stack_max);
|
|
|
|
try environment.calls.grow(allocator, options.calls_max);
|
2023-05-06 03:47:52 +02:00
|
|
|
|
|
|
|
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);
|
|
|
|
|
2023-05-10 00:57:19 +02:00
|
|
|
const userdata = try coral.io.allocate_many(u8, options.userdata.len, self.allocator);
|
2023-05-06 03:47:52 +02:00
|
|
|
|
|
|
|
errdefer coral.io.deallocate(self.allocator, userdata);
|
|
|
|
|
2023-05-10 00:57:19 +02:00
|
|
|
coral.io.copy(userdata, options.userdata);
|
|
|
|
|
2023-05-06 03:47:52 +02:00
|
|
|
object.* = .{
|
|
|
|
.userdata = userdata,
|
|
|
|
.ref_count = 1,
|
2023-05-10 00:57:19 +02:00
|
|
|
.identity = options.identity orelse userdata.ptr,
|
2023-05-06 03:47:52 +02:00
|
|
|
.behavior = options.behavior,
|
|
|
|
.fields = .{},
|
|
|
|
};
|
|
|
|
|
|
|
|
return object;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn new_array(self: *Environment) coral.io.AllocationError!*Object {
|
|
|
|
// TODO: Implement.
|
|
|
|
return self.new(.none, .{});
|
|
|
|
}
|
|
|
|
|
2023-05-10 00:57:19 +02:00
|
|
|
pub fn new_script(self: *Environment, options: NewScriptOptions) coral.io.AllocationError!*Object {
|
2023-05-06 03:47:52 +02:00
|
|
|
// TODO: Implement.
|
|
|
|
_ = self;
|
|
|
|
_ = options;
|
|
|
|
|
|
|
|
return error.OutOfMemory;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn new_string(self: *Environment, string_data: []const u8) coral.io.AllocationError!*Object {
|
2023-05-10 00:57:19 +02:00
|
|
|
const object = try self.new(.{
|
|
|
|
.userdata = string_data,
|
|
|
|
.behavior = &.{},
|
2023-05-06 03:47:52 +02:00
|
|
|
});
|
|
|
|
|
2023-05-10 00:57:19 +02:00
|
|
|
errdefer self.release(object);
|
2023-05-06 03:47:52 +02:00
|
|
|
|
2023-05-10 00:57:19 +02:00
|
|
|
return object;
|
2023-05-06 03:47:52 +02:00
|
|
|
}
|
|
|
|
|
2023-05-10 00:57:19 +02:00
|
|
|
pub fn new_string_from(self: *Environment, formats: []const coral.format.Value) coral.io.AllocationError!*Object {
|
2023-05-06 03:47:52 +02:00
|
|
|
// TODO: Implement.
|
2023-05-10 00:57:19 +02:00
|
|
|
coral.format.print(coral.io.null_writer, formats);
|
2023-05-06 03:47:52 +02:00
|
|
|
|
2023-05-10 00:57:19 +02:00
|
|
|
return self.new_string("");
|
2023-05-06 03:47:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-10 00:57:19 +02:00
|
|
|
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));
|
|
|
|
}
|
|
|
|
|
2023-05-06 03:47:52 +02:00
|
|
|
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,
|
2023-05-10 00:57:19 +02:00
|
|
|
identity: *const anyopaque,
|
2023-05-06 03:47:52 +02:00
|
|
|
userdata: []u8,
|
|
|
|
behavior: *const Behavior,
|
|
|
|
fields: ValueTable,
|
|
|
|
|
|
|
|
pub const Behavior = struct {
|
|
|
|
caller: *const Caller = default_call,
|
2023-05-10 00:57:19 +02:00
|
|
|
deinitialize: *const Deinitializer = default_deinitialize,
|
2023-05-06 03:47:52 +02:00
|
|
|
getter: *const Getter = default_get,
|
|
|
|
setter: *const Setter = default_set,
|
|
|
|
|
|
|
|
fn default_call(_: CallContext) RuntimeError!Value {
|
|
|
|
return error.InvalidOperation;
|
|
|
|
}
|
|
|
|
|
2023-05-10 00:57:19 +02:00
|
|
|
fn default_deinitialize(_: DeinitializeContext) void {
|
|
|
|
// Nothing to deinitialize by default.
|
2023-05-06 03:47:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
fn default_get(context: GetContext) RuntimeError!Value {
|
2023-05-10 00:57:19 +02:00
|
|
|
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}});
|
2023-05-06 03:47:52 +02:00
|
|
|
|
2023-05-10 00:57:19 +02:00
|
|
|
defer context.env.release(index);
|
2023-05-06 03:47:52 +02:00
|
|
|
|
2023-05-10 00:57:19 +02:00
|
|
|
return context.obj.fields.lookup(index.userdata) orelse .nil;
|
|
|
|
},
|
2023-05-06 03:47:52 +02:00
|
|
|
|
2023-05-10 00:57:19 +02:00
|
|
|
else => return error.InvalidOperation,
|
|
|
|
}
|
2023-05-06 03:47:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
fn default_set(_: SetContext) RuntimeError!void {
|
|
|
|
return error.InvalidOperation;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
pub const CallContext = struct {
|
2023-05-10 00:57:19 +02:00
|
|
|
env: *Environment,
|
|
|
|
caller: *Object,
|
|
|
|
callee: *Object,
|
|
|
|
args: []const Value,
|
2023-05-06 03:47:52 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
pub const Caller = fn (context: CallContext) RuntimeError!Value;
|
|
|
|
|
2023-05-10 00:57:19 +02:00
|
|
|
pub const DeinitializeContext = struct {
|
|
|
|
env: *Environment,
|
|
|
|
obj: *Object,
|
2023-05-06 03:47:52 +02:00
|
|
|
};
|
|
|
|
|
2023-05-10 00:57:19 +02:00
|
|
|
pub const Deinitializer = fn (context: DeinitializeContext) void;
|
2023-05-06 03:47:52 +02:00
|
|
|
|
|
|
|
pub const GetContext = struct {
|
2023-05-10 00:57:19 +02:00
|
|
|
env: *Environment,
|
|
|
|
obj: *const Object,
|
2023-05-06 03:47:52 +02:00
|
|
|
index: Value,
|
|
|
|
};
|
|
|
|
|
|
|
|
pub const Getter = fn (context: GetContext) RuntimeError!Value;
|
|
|
|
|
|
|
|
pub const SetContext = struct {
|
2023-05-10 00:57:19 +02:00
|
|
|
env: *Environment,
|
|
|
|
obj: *Object,
|
2023-05-06 03:47:52 +02:00
|
|
|
index: Value,
|
|
|
|
value: Value,
|
|
|
|
};
|
|
|
|
|
|
|
|
pub const Setter = fn (context: SetContext) RuntimeError!void;
|
|
|
|
|
|
|
|
pub fn as_value(self: *Object) Value {
|
|
|
|
return .{.object = self};
|
|
|
|
}
|
2023-05-10 00:57:19 +02:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
2023-05-06 03:47:52 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
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);
|