ona/source/ona/kym.zig
kayomn 816051f066
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
Amend code review
2023-11-05 15:51:25 +00:00

1006 lines
30 KiB
Zig

const Chunk = @import("./kym/Chunk.zig");
const Table = @import("./kym/Table.zig");
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_stringable: *RuntimeRef,
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 expect_arg(self: *const Frame, env: *RuntimeEnv, arg_index: u8) RuntimeError!*const RuntimeRef {
return self.has_arg(env, arg_index) orelse env.raise(error.TypeMismatch, "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 {typename} is not addable", .{
.typename = rhs.typename(),
}),
},
.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 {typename} is not addable", .{
.typename = rhs.typename(),
}),
},
else => self.raise(error.TypeMismatch, "left-hand {typename} is not addable", .{
.typename = lhs.typename(),
}),
};
}
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(callable, args.len);
defer self.pop_frame();
return self.call_frame(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, "{typename} is not callable", .{.typename = callable.typename()}),
};
}
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 {typename} is not comparable", .{
.typename = rhs.typename(),
}),
},
.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 {typename} is not comparable", .{
.typename = rhs.typename(),
}),
},
else => return self.raise(error.TypeMismatch, "left-hand {typename} is not comparable", .{
.typename = lhs.typename(),
}),
};
}
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 => {},
.boxed => |*boxed| {
if (boxed.*) |boxed_value| {
self.discard(boxed_value);
}
},
.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 {typename} is not divisible", .{
.typename = rhs.typename(),
}),
},
.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 {typename} is not divisible", .{
.typename = rhs.typename(),
}),
},
else => self.raise(error.TypeMismatch, "left-hand {typename} is not divisible", .{
.typename = lhs.typename(),
}),
};
}
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) {
.vector2 => |vector2| swizzle: {
const swizzle_symbol = try self.unwrap_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.unwrap_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),
else => self.raise(error.TypeMismatch, "{typename} is not get-indexable", .{
.typename = indexable.typename(),
}),
};
}
pub fn import(self: *RuntimeEnv, file_path: file.Path) RuntimeError!?*RuntimeRef {
const file_name = file_path.get_string();
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 `{name}`", .{
.name = file_name,
});
};
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;
};
}
const chunk_name = try self.new_string(file_name);
defer self.discard(chunk_name);
break: make_chunk try Chunk.make(self, chunk_name, &root.environment);
};
defer chunk.free(self);
return execute_chunk: {
const name = try self.new_string(file_name);
defer self.discard(name);
const frame = try self.push_frame(name, 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 {typename} is not multiplicable", .{
.typename = rhs.typename(),
}),
},
.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 {typename} is not multiplicable", .{
.typename = rhs.typename(),
}),
},
else => self.raise(error.TypeMismatch, "left-hand {typename} is not multiplicable", .{
.typename = lhs.typename(),
}),
};
}
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, "{typename} is not negatable", .{.typename = value.typename()}),
};
}
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_boxed(self: *RuntimeEnv, value: ?*const RuntimeRef) RuntimeError!*RuntimeRef {
return RuntimeRef.allocate(self.allocator, .{
.ref_count = 1,
.payload = .{.boxed = if (value) |ref| ref.acquire() else null},
});
}
pub fn new_dynamic(
self: *RuntimeEnv,
userdata: []const coral.io.Byte,
typeinfo: *const Typeinfo,
) RuntimeError!*RuntimeRef {
coral.debug.assert(userdata.len == typeinfo.size);
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), 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 {
const popped_frame = self.frames.pop();
coral.debug.assert(popped_frame != null);
self.discard(popped_frame.?.name_stringable);
var locals_to_pop = self.locals.values.len - popped_frame.?.locals_top;
while (locals_to_pop != 0) {
const popped_local = self.locals.pop();
coral.debug.assert(popped_local != null);
if (popped_local.?) |local| {
self.discard(local);
}
locals_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, name_stringable: *RuntimeRef, arg_count: u8) RuntimeError!Frame {
const frame = Frame{
.name_stringable = name_stringable.acquire(),
.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, comptime format: []const coral.io.Byte, args: anytype) RuntimeError {
{
const formatted_message = try coral.utf8.alloc_formatted(self.allocator, format, args);
defer self.allocator.deallocate(formatted_message);
self.print_error(formatted_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;
const callable_string = try self.to_string(self.frames.values[remaining_frames].name_stringable);
defer self.discard(callable_string);
self.print_error(get_name: {
const string = callable_string.as_string();
coral.debug.assert(string != null);
break: get_name string.?;
});
}
}
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, "{typename} is not set-indexable", .{
.typename = indexable.typename(),
}),
};
}
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 {typename} is not subtractable", .{
.typename = rhs.typename(),
}),
},
.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 {typename} is not subtractable", .{
.typename = rhs.typename(),
}),
},
else => self.raise(error.TypeMismatch, "left-hand {typename} is not subtractable", .{
.typename = lhs.typename(),
}),
};
}
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.?]);
},
.boxed => |boxed| if (boxed) |boxed_value| self.to_string(boxed_value) else self.new_string("nil"),
.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(), "@vec2({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 unwrap_dynamic(self: *RuntimeEnv, value: *const RuntimeRef, typeinfo: *const Typeinfo) RuntimeError![]coral.io.Byte {
return value.as_dynamic(typeinfo) orelse self.raise(error.TypeMismatch, "expected dynamic object, not {typename}", .{
.typename = value.typename(),
});
}
pub fn unwrap_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, not {typename}", .{.typename = value.typename()}),
};
}
pub fn unwrap_fixed(self: *RuntimeEnv, value: *const RuntimeRef) RuntimeError!Fixed {
return value.as_fixed() orelse self.raise(error.TypeMismatch, "expected fixed, not {typename}", .{
.typename = value.typename(),
});
}
pub fn unwrap_string(self: *RuntimeEnv, value: *const RuntimeRef) RuntimeError![]const coral.io.Byte {
return value.as_string() orelse self.raise(error.TypeMismatch, "expected string, not {typename}", .{
.typename = value.typename(),
});
}
pub fn unwrap_symbol(self: *RuntimeEnv, value: *const RuntimeRef) RuntimeError![*:0]const coral.io.Byte {
return value.as_symbol() orelse self.raise(error.TypeMismatch, "expected symbol, not {typename}", .{
.typename = value.typename(),
});
}
};
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,
boxed: ?*RuntimeRef,
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_boxed(self: *const RuntimeRef) ?*?*RuntimeRef {
return switch (self.object().payload) {
.boxed => |*boxed| boxed,
else => null,
};
}
pub fn as_dynamic(self: *const RuntimeRef, typeinfo: *const Typeinfo) ?[]u8 {
return switch (self.object().payload) {
.dynamic => |dynamic| if (dynamic.typeinfo() == typeinfo) dynamic.userdata() else null,
else => null,
};
}
pub fn as_fixed(self: *const RuntimeRef) ?Fixed {
return switch (self.object().payload) {
.fixed => |fixed| 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)];
},
.symbol => |symbol| coral.io.slice_sentineled(@as(u8, 0), symbol),
else => null,
};
}
pub fn as_symbol(self: *const RuntimeRef) ?[*:0]const coral.io.Byte {
return switch (self.object().payload) {
.symbol => |symbol| symbol,
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,
},
.boxed => |boxed| unbox: {
if (boxed) |boxed_value| {
break: unbox boxed_value.equals(other);
}
if (other.as_boxed()) |boxed_value| {
break: unbox boxed_value.* == null;
}
break: unbox 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),
.boxed => |boxed| @intFromPtr(boxed),
.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,
.boxed => |boxed| boxed != null,
.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",
.boxed => "boxed",
.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, "this dynamic object is not callable", .{});
}
fn default_get(env: *RuntimeEnv, _: []coral.io.Byte, _: *const RuntimeRef) RuntimeError!?*RuntimeRef {
return env.raise(error.BadOperation, "this dynamic object is not get-indexable", .{});
}
fn default_set(env: *RuntimeEnv, _: []coral.io.Byte, _: *const RuntimeRef, _: ?*const RuntimeRef) RuntimeError!void {
return env.raise(error.BadOperation, "this dynamic object is not set-indexable", .{});
}
};
pub fn assert(env: *RuntimeEnv, condition: bool) RuntimeError!void {
if (!condition) {
return env.raise(error.IllegalState, "assertion", .{});
}
}
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);
}