882 lines
27 KiB
Zig
882 lines
27 KiB
Zig
const Chunk = @import("./kym/Chunk.zig");
|
|
|
|
const Table = @import("./kym/Table.zig");
|
|
|
|
const app = @import("./app.zig");
|
|
|
|
const builtin = @import("builtin");
|
|
|
|
const coral = @import("coral");
|
|
|
|
const file = @import("./file.zig");
|
|
|
|
const tokens = @import("./kym/tokens.zig");
|
|
|
|
const tree = @import("./kym/tree.zig");
|
|
|
|
pub const Frame = struct {
|
|
name: []const coral.io.Byte = "",
|
|
arg_count: u8,
|
|
locals_top: usize,
|
|
|
|
pub fn args(self: *const Frame, env: *RuntimeEnv) []const ?*const RuntimeRef {
|
|
return env.locals.values[self.locals_top .. (self.locals_top + self.arg_count)];
|
|
}
|
|
|
|
pub fn get_arg(self: *const Frame, env: *RuntimeEnv, arg_index: u8) RuntimeError!*const RuntimeRef {
|
|
return self.has_arg(env, arg_index) orelse env.raise(error.BadOperation, "nil reference");
|
|
}
|
|
|
|
pub fn has_arg(self: *const Frame, env: *RuntimeEnv, arg_index: u8) ?*const RuntimeRef {
|
|
return if (arg_index >= self.arg_count) null else env.locals.values[self.locals_top + arg_index];
|
|
}
|
|
};
|
|
|
|
pub const Fixed = i32;
|
|
|
|
pub const Float = f64;
|
|
|
|
pub const RuntimeEnv = struct {
|
|
options: Options,
|
|
interned_symbols: SymbolSet,
|
|
allocator: coral.io.Allocator,
|
|
locals: LocalList,
|
|
frames: FrameStack,
|
|
|
|
const FrameStack = coral.list.Stack(Frame);
|
|
|
|
const LocalList = coral.list.Stack(?*RuntimeRef);
|
|
|
|
pub const Options = struct {
|
|
import_access: file.Access = .null,
|
|
print: ?*const Printer = null,
|
|
print_error: ?*const Printer = null,
|
|
};
|
|
|
|
pub const Printer = fn (buffer: []const coral.io.Byte) void;
|
|
|
|
const SymbolSet = coral.map.StringTable([:0]coral.io.Byte);
|
|
|
|
pub fn add(self: *RuntimeEnv, lhs: *const RuntimeRef, rhs: *const RuntimeRef) RuntimeError!*RuntimeRef {
|
|
return switch (lhs.object().payload) {
|
|
.fixed => |lhs_fixed| switch (rhs.object().payload) {
|
|
.fixed => |rhs_fixed| add: {
|
|
if (coral.math.checked_add(lhs_fixed, rhs_fixed)) |fixed| {
|
|
break: add self.new_fixed(fixed);
|
|
}
|
|
|
|
break: add self.new_float(@as(Float, @floatFromInt(lhs_fixed)) + @as(Float, @floatFromInt(rhs_fixed)));
|
|
},
|
|
|
|
.float => |rhs_float| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) + rhs_float),
|
|
else => self.raise(error.TypeMismatch, "right-hand object is not addable"),
|
|
},
|
|
|
|
.float => |lhs_float| switch (rhs.object().payload) {
|
|
.float => |rhs_float| self.new_float(lhs_float + rhs_float),
|
|
.fixed => |rhs_fixed| self.new_float(lhs_float + @as(Float, @floatFromInt(rhs_fixed))),
|
|
else => self.raise(error.TypeMismatch, "right-hand object is not addable"),
|
|
},
|
|
|
|
else => self.raise(error.TypeMismatch, "left-hand object is not addable"),
|
|
};
|
|
}
|
|
|
|
pub fn call(self: *RuntimeEnv, callable: *const RuntimeRef, args: []const *RuntimeRef) RuntimeError!?*RuntimeRef {
|
|
// TODO: Handle errors.
|
|
for (args) |arg| {
|
|
try self.locals.push_one(try self.acquire(arg));
|
|
}
|
|
|
|
const frame = try self.push_frame(args.len);
|
|
|
|
defer self.pop_frame();
|
|
|
|
return self.call_frame(callable, frame);
|
|
}
|
|
|
|
pub fn call_frame(self: *RuntimeEnv, callable: *const RuntimeRef, frame: Frame) RuntimeError!?*RuntimeRef {
|
|
return switch (callable.object().payload) {
|
|
.syscall => |syscall| syscall(self, frame),
|
|
.dynamic => |dynamic| dynamic.typeinfo().call(self, dynamic.userdata(), frame),
|
|
else => self.raise(error.TypeMismatch, "object is not callable"),
|
|
};
|
|
}
|
|
|
|
pub fn compare(self: *RuntimeEnv, lhs: *const RuntimeRef, rhs: *const RuntimeRef) RuntimeError!Float {
|
|
return switch (lhs.object().payload) {
|
|
.fixed => |lhs_fixed| switch (rhs.object().payload) {
|
|
.fixed => |rhs_fixed| @as(Float, @floatFromInt(lhs_fixed)) - @as(Float, @floatFromInt(rhs_fixed)),
|
|
.float => |rhs_float| @as(Float, @floatFromInt(lhs_fixed)) - rhs_float,
|
|
else => return self.raise(error.TypeMismatch, "right-hand object is not comparable"),
|
|
},
|
|
|
|
.float => |lhs_float| switch (rhs.object().payload) {
|
|
.float => |rhs_float| lhs_float - rhs_float,
|
|
.fixed => |rhs_fixed| lhs_float - @as(Float, @floatFromInt(rhs_fixed)),
|
|
else => return self.raise(error.TypeMismatch, "right-hand object is not comparable"),
|
|
},
|
|
|
|
else => return self.raise(error.TypeMismatch, "left-hand object is not comparable"),
|
|
};
|
|
}
|
|
|
|
pub fn discard(self: *RuntimeEnv, value: *RuntimeRef) void {
|
|
var object = value.object();
|
|
|
|
coral.debug.assert(object.ref_count != 0);
|
|
|
|
object.ref_count -= 1;
|
|
|
|
if (object.ref_count == 0) {
|
|
switch (object.payload) {
|
|
.false, .true, .float, .fixed, .symbol, .vector2, .vector3, .syscall => {},
|
|
|
|
.string => |string| {
|
|
coral.debug.assert(string.len >= 0);
|
|
self.allocator.deallocate(string.ptr[0 .. @intCast(string.len)]);
|
|
},
|
|
|
|
.dynamic => |dynamic| {
|
|
if (dynamic.typeinfo().destruct) |destruct| {
|
|
destruct(self, dynamic.userdata());
|
|
}
|
|
|
|
self.allocator.deallocate(dynamic.unpack());
|
|
},
|
|
}
|
|
|
|
self.allocator.deallocate(object);
|
|
}
|
|
}
|
|
|
|
pub fn div(self: *RuntimeEnv, lhs: *const RuntimeRef, rhs: *const RuntimeRef) RuntimeError!*RuntimeRef {
|
|
return switch (lhs.object().payload) {
|
|
.fixed => |lhs_fixed| switch (rhs.object().payload) {
|
|
.fixed => |rhs_fixed| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) / @as(Float, @floatFromInt(rhs_fixed))),
|
|
.float => |rhs_float| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) / rhs_float),
|
|
else => self.raise(error.TypeMismatch, "right-hand object is not divisible"),
|
|
},
|
|
|
|
.float => |lhs_float| switch (rhs.object().payload) {
|
|
.float => |rhs_float| self.new_float(lhs_float / rhs_float),
|
|
.fixed => |rhs_fixed| self.new_float(lhs_float / @as(Float, @floatFromInt(rhs_fixed))),
|
|
else => self.raise(error.TypeMismatch, "right-hand object is not divisible"),
|
|
},
|
|
|
|
else => self.raise(error.TypeMismatch, "left-hand object is not divisible"),
|
|
};
|
|
}
|
|
|
|
pub fn expect(self: *RuntimeEnv, value: ?*RuntimeRef) RuntimeError!*RuntimeRef {
|
|
return value orelse self.raise(error.TypeMismatch, "nil reference");
|
|
}
|
|
|
|
pub fn free(self: *RuntimeEnv) void {
|
|
while (self.locals.pop()) |local| {
|
|
if (local) |ref| {
|
|
self.discard(ref);
|
|
}
|
|
}
|
|
|
|
{
|
|
var iterable = self.interned_symbols.as_iterable();
|
|
|
|
while (iterable.next()) |entry| {
|
|
self.allocator.deallocate(entry.value);
|
|
}
|
|
}
|
|
|
|
self.frames.free();
|
|
self.locals.free();
|
|
self.interned_symbols.free();
|
|
}
|
|
|
|
pub fn get(self: *RuntimeEnv, indexable: *RuntimeRef, index: *const RuntimeRef) RuntimeError!?*RuntimeRef {
|
|
return switch (indexable.object().payload) {
|
|
.false => self.raise(error.TypeMismatch, "false is not get-indexable"),
|
|
.true => self.raise(error.TypeMismatch, "true is not get-indexable"),
|
|
.fixed => self.raise(error.TypeMismatch, "fixed is not get-indexable"),
|
|
.float => self.raise(error.TypeMismatch, "float is not get-indexable"),
|
|
.string => self.raise(error.TypeMismatch, "string is not get-indexable"),
|
|
.symbol => self.raise(error.TypeMismatch, "symbol is not get-indexable"),
|
|
.syscall => self.raise(error.TypeMismatch, "syscall is not get-indexable"),
|
|
|
|
.vector2 => |vector2| swizzle: {
|
|
const swizzle_symbol = try self.unbox_symbol(index);
|
|
var swizzle_buffer = [_]f32{0} ** 3;
|
|
var swizzle_count = @as(usize, 0);
|
|
|
|
while (true) : (swizzle_count += 1) {
|
|
if (swizzle_count > swizzle_buffer.len) {
|
|
return null;
|
|
}
|
|
|
|
swizzle_buffer[swizzle_count] = switch (swizzle_symbol[swizzle_count]) {
|
|
0 => break: swizzle switch (swizzle_count) {
|
|
1 => self.new_float(swizzle_buffer[0]),
|
|
2 => self.new_vector2(swizzle_buffer[0], swizzle_buffer[1]),
|
|
3 => self.new_vector3(swizzle_buffer[0], swizzle_buffer[1], swizzle_buffer[2]),
|
|
else => unreachable,
|
|
},
|
|
|
|
'x' => vector2[0],
|
|
'y' => vector2[1],
|
|
else => return null,
|
|
};
|
|
}
|
|
},
|
|
|
|
.vector3 => |vector3| swizzle: {
|
|
const swizzle_symbol = try self.unbox_symbol(index);
|
|
var swizzle_buffer = [_]f32{0} ** 3;
|
|
var swizzle_count = @as(usize, 0);
|
|
|
|
while (true) : (swizzle_count += 1) {
|
|
if (swizzle_count > swizzle_buffer.len) {
|
|
return null;
|
|
}
|
|
|
|
swizzle_buffer[swizzle_count] = switch (swizzle_symbol[swizzle_count]) {
|
|
0 => break: swizzle switch (swizzle_count) {
|
|
1 => self.new_float(swizzle_buffer[0]),
|
|
2 => self.new_vector2(swizzle_buffer[0], swizzle_buffer[1]),
|
|
3 => self.new_vector3(swizzle_buffer[0], swizzle_buffer[1], swizzle_buffer[2]),
|
|
else => unreachable,
|
|
},
|
|
|
|
'x' => vector3[0],
|
|
'y' => vector3[1],
|
|
'z' => vector3[2],
|
|
else => return null,
|
|
};
|
|
}
|
|
},
|
|
|
|
.dynamic => |dynamic| dynamic.typeinfo().get(self, dynamic.userdata(), index),
|
|
};
|
|
}
|
|
|
|
pub fn import(self: *RuntimeEnv, file_path: file.Path) RuntimeError!?*RuntimeRef {
|
|
var chunk = make_chunk: {
|
|
const file_data =
|
|
(try file.allocate_and_load(self.allocator, self.options.import_access, file_path)) orelse {
|
|
return self.raise(error.BadOperation, "failed to open or read file specified");
|
|
};
|
|
|
|
defer self.allocator.deallocate(file_data);
|
|
|
|
var root = try tree.Root.make(self.allocator);
|
|
|
|
defer root.free();
|
|
|
|
{
|
|
var stream = tokens.Stream{.source = file_data};
|
|
|
|
root.parse(&stream) catch |parse_error| {
|
|
for (root.error_messages.values) |error_message| {
|
|
self.print(error_message);
|
|
}
|
|
|
|
return parse_error;
|
|
};
|
|
}
|
|
|
|
break: make_chunk try Chunk.make(self, file_path.to_string() orelse "<script>", &root.environment);
|
|
};
|
|
|
|
if (builtin.mode == .Debug) {
|
|
const string_ref = try chunk.dump(self);
|
|
|
|
defer self.discard(string_ref);
|
|
|
|
const string = string_ref.as_string();
|
|
|
|
coral.debug.assert(string != null);
|
|
app.log_info(string.?);
|
|
}
|
|
|
|
defer chunk.free(self);
|
|
|
|
return execute_chunk: {
|
|
const frame = try self.push_frame(0);
|
|
|
|
defer self.pop_frame();
|
|
|
|
break: execute_chunk chunk.execute(self, frame);
|
|
};
|
|
}
|
|
|
|
pub fn make(allocator: coral.io.Allocator, options: Options) coral.io.AllocationError!RuntimeEnv {
|
|
return RuntimeEnv{
|
|
.locals = LocalList.make(allocator),
|
|
.frames = FrameStack.make(allocator),
|
|
.interned_symbols = SymbolSet.make(allocator, .{}),
|
|
.options = options,
|
|
.allocator = allocator,
|
|
};
|
|
}
|
|
|
|
pub fn mul(self: *RuntimeEnv, lhs: *const RuntimeRef, rhs: *const RuntimeRef) RuntimeError!*RuntimeRef {
|
|
return switch (lhs.object().payload) {
|
|
.fixed => |lhs_fixed| switch (rhs.object().payload) {
|
|
.fixed => |rhs_fixed| mul_fixed: {
|
|
if (coral.math.checked_mul(lhs_fixed, rhs_fixed)) |fixed| {
|
|
break: mul_fixed self.new_fixed(fixed);
|
|
}
|
|
|
|
break: mul_fixed self.new_float(@as(Float, @floatFromInt(lhs_fixed)) * @as(Float, @floatFromInt(rhs_fixed)));
|
|
},
|
|
|
|
.float => |rhs_float| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) * rhs_float),
|
|
else => self.raise(error.TypeMismatch, "right-hand object is not multiplicable"),
|
|
},
|
|
|
|
.float => |lhs_float| switch (rhs.object().payload) {
|
|
.float => |rhs_float| self.new_float(lhs_float * rhs_float),
|
|
.fixed => |rhs_fixed| self.new_float(lhs_float * @as(Float, @floatFromInt(rhs_fixed))),
|
|
else => self.raise(error.TypeMismatch, "right-hand object is not multiplicable"),
|
|
},
|
|
|
|
else => self.raise(error.TypeMismatch, "left-hand object is not multiplicable"),
|
|
};
|
|
}
|
|
|
|
pub fn neg(self: *RuntimeEnv, value: *const RuntimeRef) RuntimeError!*RuntimeRef {
|
|
return switch (value.object().payload) {
|
|
.fixed => |fixed| self.new_fixed(-fixed),
|
|
.float => |float| self.new_float(-float),
|
|
else => self.raise(error.TypeMismatch, "object is not negatable"),
|
|
};
|
|
}
|
|
|
|
pub fn new_boolean(self: *RuntimeEnv, value: bool) RuntimeError!*RuntimeRef {
|
|
return RuntimeRef.allocate(self.allocator, .{
|
|
.ref_count = 1,
|
|
.payload = if (value) .true else .false,
|
|
});
|
|
}
|
|
|
|
pub fn new_dynamic(
|
|
self: *RuntimeEnv,
|
|
userdata: [*]const coral.io.Byte,
|
|
typeinfo: *const Typeinfo,
|
|
) RuntimeError!*RuntimeRef {
|
|
const dynamic = try self.allocator.reallocate(null, @sizeOf(usize) + typeinfo.size);
|
|
|
|
errdefer self.allocator.deallocate(dynamic);
|
|
|
|
coral.io.copy(dynamic, coral.io.bytes_of(&typeinfo));
|
|
coral.io.copy(dynamic[@sizeOf(usize) ..], userdata[0 .. typeinfo.size]);
|
|
|
|
return RuntimeRef.allocate(self.allocator, .{
|
|
.ref_count = 1,
|
|
.payload = .{.dynamic = .{.ptr = dynamic.ptr}},
|
|
});
|
|
}
|
|
|
|
pub fn new_fixed(self: *RuntimeEnv, value: Fixed) RuntimeError!*RuntimeRef {
|
|
return RuntimeRef.allocate(self.allocator, .{
|
|
.ref_count = 1,
|
|
.payload = .{.fixed = value},
|
|
});
|
|
}
|
|
|
|
pub fn new_float(self: *RuntimeEnv, value: Float) RuntimeError!*RuntimeRef {
|
|
return RuntimeRef.allocate(self.allocator, .{
|
|
.ref_count = 1,
|
|
.payload = .{.float = value},
|
|
});
|
|
}
|
|
|
|
pub fn new_string(self: *RuntimeEnv, value: []const coral.io.Byte) RuntimeError!*RuntimeRef {
|
|
if (value.len > coral.math.max_int(@typeInfo(Fixed).Int)) {
|
|
return error.OutOfMemory;
|
|
}
|
|
|
|
const string = try coral.io.allocate_copy(coral.io.Byte, self.allocator, value);
|
|
|
|
errdefer self.allocator.deallocate(string);
|
|
|
|
return RuntimeRef.allocate(self.allocator, .{
|
|
.ref_count = 1,
|
|
|
|
.payload = .{
|
|
.string = .{
|
|
.ptr = string.ptr,
|
|
.len = @intCast(string.len),
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
pub fn new_symbol(self: *RuntimeEnv, value: []const coral.io.Byte) RuntimeError!*RuntimeRef {
|
|
return RuntimeRef.allocate(self.allocator, .{
|
|
.ref_count = 1,
|
|
|
|
.payload = .{
|
|
.symbol = self.interned_symbols.lookup(value) orelse create: {
|
|
const symbol_string = try coral.io.allocate_string(self.allocator, value);
|
|
|
|
errdefer self.allocator.deallocate(symbol_string);
|
|
|
|
coral.debug.assert(try self.interned_symbols.insert(symbol_string[0 .. value.len], symbol_string));
|
|
|
|
break: create symbol_string;
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
pub fn new_syscall(self: *RuntimeEnv, value: *const Syscall) RuntimeError!*RuntimeRef {
|
|
return RuntimeRef.allocate(self.allocator, .{
|
|
.ref_count = 1,
|
|
.payload = .{.syscall = value},
|
|
});
|
|
}
|
|
|
|
pub fn new_table(self: *RuntimeEnv) RuntimeError!*RuntimeRef {
|
|
var table = Table.make(self);
|
|
|
|
errdefer table.free(self);
|
|
|
|
return try self.new_dynamic(coral.io.bytes_of(&table).ptr, Table.typeinfo);
|
|
}
|
|
|
|
pub fn new_vector2(self: *RuntimeEnv, x: f32, y: f32) RuntimeError!*RuntimeRef {
|
|
return RuntimeRef.allocate(self.allocator, .{
|
|
.ref_count = 1,
|
|
.payload = .{.vector2 = .{x, y}},
|
|
});
|
|
}
|
|
|
|
pub fn new_vector3(self: *RuntimeEnv, x: f32, y: f32, z: f32) RuntimeError!*RuntimeRef {
|
|
return RuntimeRef.allocate(self.allocator, .{
|
|
.ref_count = 1,
|
|
.payload = .{.vector3 = .{x, y, z}},
|
|
});
|
|
}
|
|
|
|
pub fn print(self: *RuntimeEnv, buffer: []const coral.io.Byte) void {
|
|
if (self.options.print) |bound_print| {
|
|
bound_print(buffer);
|
|
}
|
|
}
|
|
|
|
pub fn print_error(self: *RuntimeEnv, buffer: []const coral.io.Byte) void {
|
|
if (self.options.print_error) |bound_print_error| {
|
|
bound_print_error(buffer);
|
|
}
|
|
}
|
|
|
|
pub fn pop_frame(self: *RuntimeEnv) void {
|
|
var to_pop = self.locals.values.len - (self.frames.pop() orelse unreachable).locals_top;
|
|
|
|
while (to_pop != 0) {
|
|
if (self.locals.pop() orelse unreachable) |local| {
|
|
self.discard(local);
|
|
}
|
|
|
|
to_pop -= 1;
|
|
}
|
|
}
|
|
|
|
pub fn pop_local(self: *RuntimeEnv) RuntimeError!?*RuntimeRef {
|
|
return self.locals.pop() orelse self.raise(error.IllegalState, "stack underflow");
|
|
}
|
|
|
|
pub fn push_frame(self: *RuntimeEnv, arg_count: u8) RuntimeError!Frame {
|
|
const frame = Frame{
|
|
.arg_count = arg_count,
|
|
.locals_top = self.locals.values.len - arg_count,
|
|
};
|
|
|
|
try self.frames.push_one(frame);
|
|
|
|
return frame;
|
|
}
|
|
|
|
pub fn raise(self: *RuntimeEnv, error_value: RuntimeError, message: []const coral.io.Byte) RuntimeError {
|
|
self.print_error(message);
|
|
|
|
if (!self.frames.is_empty()) {
|
|
self.print_error("stack trace:");
|
|
|
|
var remaining_frames = self.frames.values.len;
|
|
|
|
while (remaining_frames != 0) {
|
|
remaining_frames -= 1;
|
|
|
|
self.print_error(self.frames.values[remaining_frames].name);
|
|
}
|
|
}
|
|
|
|
return error_value;
|
|
}
|
|
|
|
pub fn set(self: *RuntimeEnv, indexable: *RuntimeRef, index: *const RuntimeRef, value: ?*const RuntimeRef) RuntimeError!void {
|
|
return switch (indexable.object().payload) {
|
|
.dynamic => |dynamic| dynamic.typeinfo().set(self, dynamic.userdata(), index, value),
|
|
else => self.raise(error.TypeMismatch, "object is not set-indexable"),
|
|
};
|
|
}
|
|
|
|
pub fn sub(self: *RuntimeEnv, lhs: *const RuntimeRef, rhs: *const RuntimeRef) RuntimeError!*RuntimeRef {
|
|
return switch (lhs.object().payload) {
|
|
.fixed => |lhs_fixed| switch (rhs.object().payload) {
|
|
.fixed => |rhs_fixed| sub_fixed: {
|
|
if (coral.math.checked_sub(lhs_fixed, rhs_fixed)) |fixed| {
|
|
break: sub_fixed self.new_fixed(fixed);
|
|
}
|
|
|
|
break: sub_fixed self.new_float(@as(Float, @floatFromInt(lhs_fixed)) - @as(Float, @floatFromInt(rhs_fixed)));
|
|
},
|
|
|
|
.float => |rhs_float| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) - rhs_float),
|
|
else => self.raise(error.TypeMismatch, "right-hand object is not subtractable"),
|
|
},
|
|
|
|
.float => |lhs_float| switch (rhs.object().payload) {
|
|
.float => |rhs_float| self.new_float(lhs_float - rhs_float),
|
|
.fixed => |rhs_fixed| self.new_float(lhs_float - @as(Float, @floatFromInt(rhs_fixed))),
|
|
else => self.raise(error.TypeMismatch, "right-hand object is not subtractable"),
|
|
},
|
|
|
|
else => self.raise(error.TypeMismatch, "left-hand object is not subtractable"),
|
|
};
|
|
}
|
|
|
|
pub fn to_string(self: *RuntimeEnv, value: *const RuntimeRef) RuntimeError!*RuntimeRef {
|
|
const decimal_format = coral.utf8.DecimalFormat.default;
|
|
|
|
return switch (value.object().payload) {
|
|
.false => self.new_string("false"),
|
|
.true => self.new_string("true"),
|
|
|
|
.fixed => |fixed| convert: {
|
|
var string = [_:0]coral.io.Byte{0} ** 32;
|
|
var buffer = coral.io.FixedBuffer{.bytes = &string};
|
|
const length = decimal_format.print(buffer.as_writer(), fixed);
|
|
|
|
coral.debug.assert(length != null);
|
|
|
|
break: convert self.new_string(string[0 .. length.?]);
|
|
},
|
|
|
|
.float => |float| convert: {
|
|
var string = [_:0]coral.io.Byte{0} ** 32;
|
|
var buffer = coral.io.FixedBuffer{.bytes = &string};
|
|
const length = decimal_format.print(buffer.as_writer(), float);
|
|
|
|
coral.debug.assert(length != null);
|
|
|
|
break: convert self.new_string(string[0 .. length.?]);
|
|
},
|
|
|
|
.symbol => |symbol| self.new_string(coral.io.slice_sentineled(@as(coral.io.Byte, 0), symbol)),
|
|
.string => value.acquire(),
|
|
|
|
.vector2 => |vector2| convert: {
|
|
var string = [_:0]coral.io.Byte{0} ** 64;
|
|
var buffer = coral.io.FixedBuffer{.bytes = &string};
|
|
|
|
const length = coral.utf8.print_formatted(buffer.as_writer(), "vec3({x}, {y})", .{
|
|
.x = vector2[0],
|
|
.y = vector2[1],
|
|
});
|
|
|
|
coral.debug.assert(length != null);
|
|
|
|
break: convert self.new_string(string[0 .. length.?]);
|
|
},
|
|
|
|
.vector3 => |vector3| convert: {
|
|
var string = [_:0]coral.io.Byte{0} ** 96;
|
|
var buffer = coral.io.FixedBuffer{.bytes = &string};
|
|
|
|
const length = coral.utf8.print_formatted(buffer.as_writer(), "vec3({x}, {y}, {z})", .{
|
|
.x = vector3[0],
|
|
.y = vector3[1],
|
|
.z = vector3[2],
|
|
});
|
|
|
|
coral.debug.assert(length != null);
|
|
|
|
break: convert self.new_string(string[0 .. length.?]);
|
|
},
|
|
|
|
.syscall => self.new_string("<syscall>"),
|
|
.dynamic => self.new_string("<dynamic>"),
|
|
};
|
|
}
|
|
|
|
pub fn unbox_dynamic(self: *RuntimeEnv, value: *const RuntimeRef) RuntimeError![]coral.io.Byte {
|
|
return switch (value.object().payload) {
|
|
.dynamic => |dynamic| dynamic.userdata(),
|
|
else => self.raise(error.TypeMismatch, "expected fixed object")
|
|
};
|
|
}
|
|
|
|
pub fn unbox_float(self: *RuntimeEnv, value: *const RuntimeRef) RuntimeError!Float {
|
|
return switch (value.object().payload) {
|
|
.fixed => |fixed| @floatFromInt(fixed),
|
|
.float => |float| float,
|
|
else => self.raise(error.TypeMismatch, "expected float object")
|
|
};
|
|
}
|
|
|
|
pub fn unbox_fixed(self: *RuntimeEnv, value: *const RuntimeRef) RuntimeError!Fixed {
|
|
return value.as_fixed() orelse self.raise(error.TypeMismatch, "expected fixed object");
|
|
}
|
|
|
|
pub fn unbox_string(self: *RuntimeEnv, value: *const RuntimeRef) RuntimeError![]const coral.io.Byte {
|
|
return value.as_string() orelse self.raise(error.TypeMismatch, "expected string object");
|
|
}
|
|
|
|
pub fn unbox_symbol(self: *RuntimeEnv, value: *const RuntimeRef) RuntimeError![*:0]const coral.io.Byte {
|
|
return switch (value.object().payload) {
|
|
.symbol => |symbol| symbol,
|
|
else => self.raise(error.TypeMismatch, "expected symbol object")
|
|
};
|
|
}
|
|
};
|
|
|
|
pub const RuntimeError = coral.io.AllocationError || error {
|
|
IllegalState,
|
|
TypeMismatch,
|
|
BadOperation,
|
|
BadSyntax,
|
|
};
|
|
|
|
pub const RuntimeRef = opaque {
|
|
const Object = struct {
|
|
ref_count: u16,
|
|
|
|
payload: union (enum) {
|
|
false,
|
|
true,
|
|
float: Float,
|
|
fixed: Fixed,
|
|
symbol: [*:0]const coral.io.Byte,
|
|
vector2: [2]f32,
|
|
vector3: [3]f32,
|
|
syscall: *const Syscall,
|
|
|
|
string: struct {
|
|
ptr: [*]coral.io.Byte,
|
|
len: Fixed,
|
|
|
|
const Self = @This();
|
|
|
|
fn unpack(self: Self) []coral.io.Byte {
|
|
coral.debug.assert(self.len >= 0);
|
|
|
|
return self.ptr[0 .. @intCast(self.len)];
|
|
}
|
|
},
|
|
|
|
dynamic: struct {
|
|
ptr: [*]coral.io.Byte,
|
|
|
|
const Self = @This();
|
|
|
|
fn typeinfo(self: Self) *const Typeinfo {
|
|
return @as(**const Typeinfo, @ptrCast(@alignCast(self.ptr))).*;
|
|
}
|
|
|
|
fn unpack(self: Self) []coral.io.Byte {
|
|
return self.ptr[0 .. (@sizeOf(usize) + self.typeinfo().size)];
|
|
}
|
|
|
|
fn userdata(self: Self) []coral.io.Byte {
|
|
const unpacked = self.unpack();
|
|
const address_size = @sizeOf(usize);
|
|
|
|
coral.debug.assert(unpacked.len >= address_size);
|
|
|
|
return unpacked[address_size ..];
|
|
}
|
|
},
|
|
},
|
|
};
|
|
|
|
pub fn acquire(self: *const RuntimeRef) *RuntimeRef {
|
|
const self_object = self.object();
|
|
|
|
self_object.ref_count += 1;
|
|
|
|
return @ptrCast(self_object);
|
|
}
|
|
|
|
fn allocate(allocator: coral.io.Allocator, data: Object) coral.io.AllocationError!*RuntimeRef {
|
|
return @ptrCast(try coral.io.allocate_one(allocator, data));
|
|
}
|
|
|
|
pub fn as_fixed(self: *const RuntimeRef) ?Fixed {
|
|
return switch (self.object().payload) {
|
|
.fixed => |fixed| @intCast(@as(u32, @bitCast(fixed))),
|
|
else => null,
|
|
};
|
|
}
|
|
|
|
pub fn as_string(self: *const RuntimeRef) ?[]const coral.io.Byte {
|
|
return switch (self.object().payload) {
|
|
.string => |string| get: {
|
|
coral.debug.assert(string.len > -1);
|
|
|
|
break: get string.ptr[0 .. @intCast(string.len)];
|
|
},
|
|
|
|
else => null,
|
|
};
|
|
}
|
|
|
|
fn object(self: *const RuntimeRef) *Object {
|
|
return @constCast(@ptrCast(@alignCast(self)));
|
|
}
|
|
|
|
pub fn equals(self: *const RuntimeRef, other: *const RuntimeRef) bool {
|
|
return switch (self.object().payload) {
|
|
.false => other.object().payload == .false,
|
|
.true => other.object().payload == .true,
|
|
|
|
.fixed => |self_fixed| switch (other.object().payload) {
|
|
.fixed => |other_fixed| other_fixed == self_fixed,
|
|
.float => |other_float| other_float == @as(Float, @floatFromInt(self_fixed)),
|
|
else => false,
|
|
},
|
|
|
|
.float => |self_float| switch (other.object().payload) {
|
|
.float => |other_float| other_float == self_float,
|
|
.fixed => |other_fixed| @as(Float, @floatFromInt(other_fixed)) == self_float,
|
|
else => false,
|
|
},
|
|
|
|
.symbol => |self_symbol| switch (other.object().payload) {
|
|
.symbol => |other_symbol| self_symbol == other_symbol,
|
|
else => false,
|
|
},
|
|
|
|
.vector2 => |self_vector| switch (other.object().payload) {
|
|
.vector2 => |other_vector| coral.io.are_equal(coral.io.bytes_of(&self_vector), coral.io.bytes_of(&other_vector)),
|
|
else => false,
|
|
},
|
|
|
|
.vector3 => |self_vector| switch (other.object().payload) {
|
|
.vector3 => |other_vector| coral.io.are_equal(coral.io.bytes_of(&self_vector), coral.io.bytes_of(&other_vector)),
|
|
else => false,
|
|
},
|
|
|
|
.syscall => |self_syscall| switch (other.object().payload) {
|
|
.syscall => |other_syscall| self_syscall == other_syscall,
|
|
else => false,
|
|
},
|
|
|
|
.string => |self_string| switch (other.object().payload) {
|
|
.string => |other_string| coral.io.are_equal(self_string.unpack(), other_string.unpack()),
|
|
else => false,
|
|
},
|
|
|
|
.dynamic => |self_dynamic| switch (other.object().payload) {
|
|
.dynamic => |other_dynamic|
|
|
self_dynamic.typeinfo() == other_dynamic.typeinfo() and
|
|
self_dynamic.userdata().ptr == other_dynamic.userdata().ptr,
|
|
|
|
else => false,
|
|
},
|
|
};
|
|
}
|
|
|
|
pub fn hash(self: *const RuntimeRef) usize {
|
|
return switch (self.object().payload) {
|
|
.false => 1237,
|
|
.true => 1231,
|
|
.float => |float| @bitCast(float),
|
|
.fixed => |fixed| @intCast(@as(u32, @bitCast(fixed))),
|
|
.symbol => |symbol| @intFromPtr(symbol),
|
|
.vector2 => |vector| @bitCast(vector),
|
|
.vector3 => |vector| coral.io.jenkins_hash(@typeInfo(usize).Int, coral.io.bytes_of(&vector)),
|
|
.syscall => |syscall| @intFromPtr(syscall),
|
|
.string => |string| coral.io.djb2_hash(@typeInfo(usize).Int, string.unpack()),
|
|
.dynamic => |dynamic| @intFromPtr(dynamic.typeinfo()) ^ @intFromPtr(dynamic.userdata().ptr),
|
|
};
|
|
}
|
|
|
|
pub fn is_truthy(self: *const RuntimeRef) bool {
|
|
return switch (self.object().payload) {
|
|
.false => false,
|
|
.true => true,
|
|
.float => |float| float != 0,
|
|
.fixed => |fixed| fixed != 0,
|
|
.symbol => true,
|
|
.vector2 => |vector| coral.io.all_equals(coral.io.bytes_of(&vector), 0),
|
|
.vector3 => |vector| coral.io.all_equals(coral.io.bytes_of(&vector), 0),
|
|
.syscall => true,
|
|
.string => |string| string.len != 0,
|
|
.dynamic => true,
|
|
};
|
|
}
|
|
|
|
pub fn typename(self: *const RuntimeRef) []const coral.io.Byte {
|
|
return switch (self.object().payload) {
|
|
.false => "false",
|
|
.true => "true",
|
|
.float => "float",
|
|
.fixed => "fixed",
|
|
.symbol => "symbol",
|
|
.vector2 => "vector2",
|
|
.vector3 => "vector3",
|
|
.syscall => "syscall",
|
|
.string => "string",
|
|
.dynamic => "dynamic",
|
|
};
|
|
}
|
|
};
|
|
|
|
pub const Syscall = fn (env: *RuntimeEnv, frame: Frame) RuntimeError!?*RuntimeRef;
|
|
|
|
pub const Typeinfo = struct {
|
|
name: []const coral.io.Byte,
|
|
size: usize,
|
|
destruct: ?*const fn (env: *RuntimeEnv, userdata: []coral.io.Byte) void = null,
|
|
call: *const fn (env: *RuntimeEnv, userdata: []coral.io.Byte, frame: Frame) RuntimeError!?*RuntimeRef = default_call,
|
|
get: *const fn (env: *RuntimeEnv, userdata: []coral.io.Byte, index: *const RuntimeRef) RuntimeError!?*RuntimeRef = default_get,
|
|
set: *const fn (env: *RuntimeEnv, userdata: []coral.io.Byte, value: *const RuntimeRef, value: ?*const RuntimeRef) RuntimeError!void = default_set,
|
|
|
|
fn default_call(env: *RuntimeEnv, _: []coral.io.Byte, _: Frame) RuntimeError!?*RuntimeRef {
|
|
return env.raise(error.BadOperation, "object is not callable");
|
|
}
|
|
|
|
fn default_get(env: *RuntimeEnv, _: []coral.io.Byte, _: *const RuntimeRef) RuntimeError!?*RuntimeRef {
|
|
return env.raise(error.BadOperation, "object is not get-indexable");
|
|
}
|
|
|
|
fn default_set(env: *RuntimeEnv, _: []coral.io.Byte, _: *const RuntimeRef, _: ?*const RuntimeRef) RuntimeError!void {
|
|
return env.raise(error.BadOperation, "object is not set-indexable");
|
|
}
|
|
};
|
|
|
|
pub fn get_field(env: *RuntimeEnv, indexable: *RuntimeRef, field: []const coral.io.Byte) RuntimeError!?*RuntimeRef {
|
|
const field_symbol = try env.new_symbol(field);
|
|
|
|
defer env.discard(field_symbol);
|
|
|
|
return env.get(indexable, field_symbol);
|
|
}
|
|
|
|
pub fn get_index(env: *RuntimeEnv, indexable: *RuntimeRef, index: Fixed) RuntimeError!?*RuntimeRef {
|
|
const index_number = try env.new_fixed(index);
|
|
|
|
defer env.discard(index_number);
|
|
|
|
return env.get(indexable, index_number);
|
|
}
|
|
|
|
pub fn get_key(env: *RuntimeEnv, indexable: *RuntimeRef, key: []const coral.io.Byte) RuntimeError!?*RuntimeRef {
|
|
const key_string = try env.new_string(key);
|
|
|
|
defer env.discard(key_string);
|
|
|
|
return env.get(indexable, key_string);
|
|
}
|