1446 lines
41 KiB
Zig
1446 lines
41 KiB
Zig
const Ast = @import("./kym/Ast.zig");
|
|
|
|
const coral = @import("coral");
|
|
|
|
const file = @import("./file.zig");
|
|
|
|
pub const Caller = coral.io.Generator(RuntimeError!?*RuntimeRef, *RuntimeEnv);
|
|
|
|
pub const ErrorHandler = coral.io.Generator(void, ErrorInfo);
|
|
|
|
pub const ErrorInfo = struct {
|
|
message: []const coral.io.Byte,
|
|
frames: []const Frame,
|
|
};
|
|
|
|
pub const Fixed = i32;
|
|
|
|
pub const Float = f64;
|
|
|
|
pub const Frame = struct {
|
|
name: []const coral.io.Byte,
|
|
arg_count: u8,
|
|
locals_top: usize,
|
|
};
|
|
|
|
pub const RuntimeEnv = struct {
|
|
interned_symbols: SymbolSet,
|
|
allocator: coral.io.Allocator,
|
|
error_handler: ErrorHandler,
|
|
system_bindings: RefTable,
|
|
locals: RefList,
|
|
frames: FrameStack,
|
|
|
|
const Chunk = struct {
|
|
env: *RuntimeEnv,
|
|
name: []coral.io.Byte,
|
|
opcodes: OpcodeList,
|
|
constants: ConstList,
|
|
|
|
const Constant = union (enum) {
|
|
fixed: Fixed,
|
|
float: Float,
|
|
string: []const coral.io.Byte,
|
|
symbol: []const coral.io.Byte,
|
|
};
|
|
|
|
const Opcode = union (enum) {
|
|
push_nil,
|
|
push_true,
|
|
push_false,
|
|
push_const: u16,
|
|
push_local: u8,
|
|
push_table: u32,
|
|
push_system: u16,
|
|
local_set: u8,
|
|
object_get,
|
|
object_set,
|
|
object_call: u8,
|
|
|
|
not,
|
|
neg,
|
|
|
|
add,
|
|
sub,
|
|
mul,
|
|
div,
|
|
|
|
eql,
|
|
cgt,
|
|
clt,
|
|
cge,
|
|
cle,
|
|
};
|
|
|
|
const OpcodeList = coral.list.Stack(Opcode);
|
|
|
|
const CompilationUnit = struct {
|
|
local_identifiers_buffer: [255][]const coral.io.Byte = [_][]const coral.io.Byte{""} ** 255,
|
|
local_identifiers_count: u8 = 0,
|
|
|
|
fn compile_expression(self: *CompilationUnit, chunk: *Chunk, expression: Ast.Expression) RuntimeError!void {
|
|
const number_format = coral.utf8.DecimalFormat{
|
|
.delimiter = "_",
|
|
.positive_prefix = .none,
|
|
};
|
|
|
|
switch (expression) {
|
|
.nil_literal => try chunk.opcodes.push_one(.push_nil),
|
|
.true_literal => try chunk.opcodes.push_one(.push_true),
|
|
.false_literal => try chunk.opcodes.push_one(.push_false),
|
|
|
|
.number_literal => |literal| {
|
|
for (literal) |codepoint| {
|
|
if (codepoint == '.') {
|
|
return chunk.opcodes.push_one(.{
|
|
.push_const = try chunk.declare_constant(.{
|
|
.float = number_format.parse(literal, Float) orelse unreachable,
|
|
}),
|
|
});
|
|
}
|
|
}
|
|
|
|
try chunk.opcodes.push_one(.{
|
|
.push_const = try chunk.declare_constant(.{
|
|
.fixed = number_format.parse(literal, Fixed) orelse unreachable,
|
|
}),
|
|
});
|
|
},
|
|
|
|
.string_literal => |literal| {
|
|
try chunk.opcodes.push_one(.{
|
|
.push_const = try chunk.declare_constant(.{.string = literal}),
|
|
});
|
|
},
|
|
|
|
.symbol_literal => |literal| {
|
|
try chunk.opcodes.push_one(.{
|
|
.push_const = try chunk.declare_constant(.{.symbol = literal}),
|
|
});
|
|
},
|
|
|
|
.table_literal => |fields| {
|
|
if (fields.values.len > coral.math.max_int(@typeInfo(u32).Int)) {
|
|
return error.OutOfMemory;
|
|
}
|
|
|
|
for (fields.values) |field| {
|
|
try self.compile_expression(chunk, field.value_expression);
|
|
try self.compile_expression(chunk, field.key_expression);
|
|
}
|
|
|
|
try chunk.opcodes.push_one(.{.push_table = @intCast(fields.values.len)});
|
|
},
|
|
|
|
.binary_operation => |operation| {
|
|
try self.compile_expression(chunk, operation.lhs_expression.*);
|
|
try self.compile_expression(chunk, operation.rhs_expression.*);
|
|
|
|
try chunk.opcodes.push_one(switch (operation.operator) {
|
|
.addition => .add,
|
|
.subtraction => .sub,
|
|
.multiplication => .mul,
|
|
.divsion => .div,
|
|
.greater_equals_comparison => .eql,
|
|
.greater_than_comparison => .cgt,
|
|
.equals_comparison => .cge,
|
|
.less_than_comparison => .clt,
|
|
.less_equals_comparison => .cle,
|
|
});
|
|
},
|
|
|
|
.unary_operation => |operation| {
|
|
try self.compile_expression(chunk, operation.expression.*);
|
|
|
|
try chunk.opcodes.push_one(switch (operation.operator) {
|
|
.boolean_negation => .not,
|
|
.numeric_negation => .neg,
|
|
});
|
|
},
|
|
|
|
.invoke => |invoke| {
|
|
if (invoke.argument_expressions.values.len > coral.math.max_int(@typeInfo(u8).Int)) {
|
|
return chunk.env.raise(error.BadSyntax, "lambdas may contain a maximum of 255 arguments");
|
|
}
|
|
|
|
for (invoke.argument_expressions.values) |argument_expression| {
|
|
try self.compile_expression(chunk, argument_expression);
|
|
}
|
|
|
|
try self.compile_expression(chunk, invoke.object_expression.*);
|
|
try chunk.opcodes.push_one(.{.object_call = @intCast(invoke.argument_expressions.values.len)});
|
|
},
|
|
|
|
.grouped_expression => |grouped_expression| {
|
|
try self.compile_expression(chunk, grouped_expression.*);
|
|
},
|
|
|
|
.get_system => |get_system| {
|
|
try chunk.opcodes.push_one(.{
|
|
.push_system = try chunk.declare_constant(.{.symbol = get_system}),
|
|
});
|
|
},
|
|
|
|
.local_get => |local_get| {
|
|
try chunk.opcodes.push_one(.{
|
|
.push_local = self.resolve_local(local_get) orelse {
|
|
return chunk.env.raise(error.OutOfMemory, "undefined local");
|
|
},
|
|
});
|
|
},
|
|
|
|
.local_set => |local_set| {
|
|
if (self.resolve_local(local_set)) |index| {
|
|
try chunk.opcodes.push_one(.{.local_set = index});
|
|
} else {
|
|
if (self.local_identifiers_count == self.local_identifiers_buffer.len) {
|
|
return chunk.env.raise(error.BadSyntax, "chunks may have a maximum of 255 locals");
|
|
}
|
|
|
|
self.local_identifiers_buffer[self.local_identifiers_count] = local_set;
|
|
self.local_identifiers_count += 1;
|
|
}
|
|
},
|
|
|
|
.field_get => |field_get| {
|
|
try self.compile_expression(chunk, field_get.object_expression.*);
|
|
|
|
try chunk.opcodes.push_one(.{
|
|
.push_const = try chunk.declare_constant(.{.symbol = field_get.identifier}),
|
|
});
|
|
|
|
try chunk.opcodes.push_one(.object_get);
|
|
},
|
|
|
|
.field_set => |field_set| {
|
|
try self.compile_expression(chunk, field_set.object_expression.*);
|
|
|
|
try chunk.opcodes.push_one(.{
|
|
.push_const = try chunk.declare_constant(.{.symbol = field_set.identifier}),
|
|
});
|
|
|
|
try self.compile_expression(chunk, field_set.value_expression.*);
|
|
try chunk.opcodes.push_one(.object_set);
|
|
},
|
|
|
|
.subscript_get => |subscript_get| {
|
|
try self.compile_expression(chunk, subscript_get.object_expression.*);
|
|
try self.compile_expression(chunk, subscript_get.subscript_expression.*);
|
|
try chunk.opcodes.push_one(.object_get);
|
|
},
|
|
|
|
.subscript_set => |subscript_set| {
|
|
try self.compile_expression(chunk, subscript_set.object_expression.*);
|
|
try self.compile_expression(chunk, subscript_set.subscript_expression.*);
|
|
try self.compile_expression(chunk, subscript_set.value_expression.*);
|
|
try chunk.opcodes.push_one(.object_set);
|
|
},
|
|
}
|
|
}
|
|
|
|
fn compile_statement(self: *CompilationUnit, chunk: *Chunk, statement: Ast.Statement) RuntimeError!void {
|
|
switch (statement) {
|
|
.@"return" => |@"return"| {
|
|
if (@"return".expression) |expression| {
|
|
try self.compile_expression(chunk, expression);
|
|
} else {
|
|
try chunk.opcodes.push_one(.push_nil);
|
|
}
|
|
},
|
|
|
|
.expression => |expression| try self.compile_expression(chunk, expression),
|
|
}
|
|
}
|
|
|
|
fn resolve_local(self: *CompilationUnit, local_identifier: []const coral.io.Byte) ?u8 {
|
|
if (self.local_identifiers_count == 0) {
|
|
return null;
|
|
}
|
|
|
|
var index = @as(u8, self.local_identifiers_count - 1);
|
|
|
|
while (true) : (index -= 1) {
|
|
if (coral.io.equals(local_identifier, self.local_identifiers_buffer[index])) {
|
|
return index;
|
|
}
|
|
|
|
if (index == 0) {
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
fn compile(self: *Chunk, ast: Ast) RuntimeError!void {
|
|
var unit = CompilationUnit{};
|
|
|
|
for (ast.list_statements()) |statement| {
|
|
try unit.compile_statement(self, statement);
|
|
}
|
|
}
|
|
|
|
fn declare_constant(self: *Chunk, constant: Constant) RuntimeError!u16 {
|
|
if (self.constants.values.len == coral.math.max_int(@typeInfo(u16).Int)) {
|
|
return self.env.raise(error.BadSyntax, "chunks cannot contain more than 65,535 constants");
|
|
}
|
|
|
|
const constant_index = self.constants.values.len;
|
|
|
|
try self.constants.push_one(try switch (constant) {
|
|
.fixed => |fixed| self.env.new_fixed(fixed),
|
|
.float => |float| self.env.new_float(float),
|
|
.string => |string| self.env.new_string(string),
|
|
.symbol => |symbol| self.env.new_symbol(symbol),
|
|
});
|
|
|
|
return @intCast(constant_index);
|
|
}
|
|
|
|
fn execute(self: *Chunk) RuntimeError!?*RuntimeRef {
|
|
try self.env.frames.push_one(.{
|
|
.arg_count = 0,
|
|
.locals_top = self.env.locals.values.len,
|
|
.name = self.name,
|
|
});
|
|
|
|
defer coral.debug.assert(self.env.frames.pop() != null);
|
|
|
|
for (self.opcodes.values) |opcode| {
|
|
switch (opcode) {
|
|
.push_nil => try self.env.locals.push_one(null),
|
|
.push_true => try self.env.locals.push_one(try self.env.new_boolean(true)),
|
|
.push_false => try self.env.locals.push_one(try self.env.new_boolean(false)),
|
|
|
|
.push_const => |push_const| {
|
|
if (push_const >= self.constants.values.len) {
|
|
return self.env.raise(error.IllegalState, "invalid const");
|
|
}
|
|
|
|
try self.env.locals.push_one(try self.env.acquire(self.constants.values[push_const]));
|
|
},
|
|
|
|
.push_local => |push_local| {
|
|
if (self.env.locals.values[push_local]) |local| {
|
|
try self.env.locals.push_one(try self.env.acquire(local));
|
|
} else {
|
|
try self.env.locals.push_one(null);
|
|
}
|
|
},
|
|
|
|
.push_table => |push_table| {
|
|
const table = try self.env.new_table();
|
|
|
|
errdefer self.env.discard(table);
|
|
|
|
{
|
|
const dynamic = table.object().payload.dynamic;
|
|
const userdata = dynamic.userdata();
|
|
var popped = @as(usize, 0);
|
|
|
|
while (popped < push_table) : (popped += 1) {
|
|
const index = try self.env.expect(try self.pop_local());
|
|
|
|
defer self.env.discard(index);
|
|
|
|
const maybe_value = try self.pop_local();
|
|
|
|
defer {
|
|
if (maybe_value) |value| {
|
|
self.env.discard(value);
|
|
}
|
|
}
|
|
|
|
try dynamic.typeinfo().set(.{
|
|
.userdata = userdata,
|
|
.env = self.env,
|
|
}, index, maybe_value);
|
|
}
|
|
}
|
|
|
|
try self.env.locals.push_one(table);
|
|
},
|
|
|
|
.push_system => |push_system| {
|
|
if (self.env.system_bindings.lookup(self.constants.values[push_system])) |syscallable| {
|
|
try self.env.locals.push_one(try self.env.acquire(syscallable));
|
|
} else {
|
|
try self.env.locals.push_one(null);
|
|
}
|
|
},
|
|
|
|
.local_set => |local_set| {
|
|
const local = &self.env.locals.values[local_set];
|
|
|
|
if (local.*) |previous_local| {
|
|
self.env.discard(previous_local);
|
|
}
|
|
|
|
local.* = try self.pop_local();
|
|
},
|
|
|
|
.object_get => {
|
|
const index = (try self.pop_local()) orelse {
|
|
return self.env.raise(error.TypeMismatch, "nil is not a valid index");
|
|
};
|
|
|
|
defer self.env.discard(index);
|
|
|
|
const indexable = (try self.pop_local()) orelse {
|
|
return self.env.raise(error.TypeMismatch, "nil is not a valid indexable");
|
|
};
|
|
|
|
defer self.env.discard(indexable);
|
|
|
|
const value = try self.env.get(indexable, index);
|
|
|
|
errdefer {
|
|
if (value) |ref| {
|
|
self.env.discard(ref);
|
|
}
|
|
}
|
|
|
|
try self.env.locals.push_one(value);
|
|
},
|
|
|
|
.object_set => {
|
|
const value = try self.pop_local();
|
|
|
|
defer {
|
|
if (value) |ref| {
|
|
self.env.discard(ref);
|
|
}
|
|
}
|
|
|
|
const index = try self.pop_local() orelse {
|
|
return self.env.raise(error.TypeMismatch, "nil is not a valid index");
|
|
};
|
|
|
|
defer self.env.discard(index);
|
|
|
|
const indexable = try self.pop_local() orelse {
|
|
return self.env.raise(error.TypeMismatch, "nil is not a valid indexable");
|
|
};
|
|
|
|
defer self.env.discard(indexable);
|
|
|
|
try self.env.set(indexable, index, value);
|
|
},
|
|
|
|
.object_call => |object_call| {
|
|
const result = call: {
|
|
const callable = try self.env.expect(try self.pop_local());
|
|
|
|
defer self.env.discard(callable);
|
|
|
|
try self.env.frames.push_one(.{
|
|
.name = "<lambda>",
|
|
.arg_count = object_call,
|
|
.locals_top = self.env.locals.values.len,
|
|
});
|
|
|
|
defer coral.debug.assert(self.env.frames.pop() != null);
|
|
|
|
break: call try switch (callable.object().payload) {
|
|
.dynamic => |dynamic| dynamic.typeinfo().call(.{
|
|
.userdata = dynamic.userdata(),
|
|
.env = self.env,
|
|
}),
|
|
|
|
else => self.env.raise(error.TypeMismatch, "object is not callable"),
|
|
};
|
|
};
|
|
|
|
for (0 .. object_call) |_| {
|
|
if (try self.pop_local()) |popped_arg| {
|
|
self.env.discard(popped_arg);
|
|
}
|
|
}
|
|
|
|
errdefer {
|
|
if (result) |ref| {
|
|
self.env.discard(ref);
|
|
}
|
|
}
|
|
|
|
try self.env.locals.push_one(result);
|
|
},
|
|
|
|
.not => {
|
|
if (try self.pop_local()) |value| {
|
|
defer self.env.discard(value);
|
|
|
|
try self.env.locals.push_one(try self.env.new_boolean(!value.is_truthy()));
|
|
} else {
|
|
try self.env.locals.push_one(try self.env.new_boolean(true));
|
|
}
|
|
},
|
|
|
|
.neg => {
|
|
const value = try self.env.expect(try self.pop_local());
|
|
|
|
defer self.env.discard(value);
|
|
|
|
try self.env.locals.push_one(try switch (value.object().payload) {
|
|
.fixed => |fixed| self.env.new_fixed(-fixed),
|
|
.float => |float| self.env.new_float(-float),
|
|
else => self.env.raise(error.TypeMismatch, "object is not negatable"),
|
|
});
|
|
},
|
|
|
|
.add => {
|
|
const rhs = try self.env.expect(try self.pop_local());
|
|
|
|
defer self.env.discard(rhs);
|
|
|
|
const lhs = try self.env.expect(try self.pop_local());
|
|
|
|
defer self.env.discard(lhs);
|
|
|
|
try self.env.locals.push_one(try 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.env.new_fixed(fixed);
|
|
}
|
|
|
|
break: add self.env.new_float(@as(Float,
|
|
@floatFromInt(lhs_fixed)) +
|
|
@as(Float, @floatFromInt(rhs_fixed)));
|
|
},
|
|
|
|
.float => |rhs_float| self.env.new_float(
|
|
@as(Float, @floatFromInt(lhs_fixed)) + rhs_float),
|
|
|
|
else => self.env.raise(error.TypeMismatch, "right-hand object is not addable"),
|
|
},
|
|
|
|
.float => |lhs_float| switch (rhs.object().payload) {
|
|
.float => |rhs_float| self.env.new_float(lhs_float + rhs_float),
|
|
|
|
.fixed => |rhs_fixed| self.env.new_float(
|
|
lhs_float + @as(Float, @floatFromInt(rhs_fixed))),
|
|
|
|
else => self.env.raise(error.TypeMismatch, "right-hand object is not addable"),
|
|
},
|
|
|
|
else => self.env.raise(error.TypeMismatch, "left-hand object is not addable"),
|
|
});
|
|
},
|
|
|
|
.sub => {
|
|
const rhs = try self.env.expect(try self.pop_local());
|
|
|
|
defer self.env.discard(rhs);
|
|
|
|
const lhs = try self.env.expect(try self.pop_local());
|
|
|
|
defer self.env.discard(lhs);
|
|
|
|
try self.env.locals.push_one(try switch (lhs.object().payload) {
|
|
.fixed => |lhs_fixed| switch (rhs.object().payload) {
|
|
.fixed => |rhs_fixed| sub: {
|
|
if (coral.math.checked_sub(lhs_fixed, rhs_fixed)) |fixed| {
|
|
break: sub self.env.new_fixed(fixed);
|
|
}
|
|
|
|
break: sub self.env.new_float(@as(Float,
|
|
@floatFromInt(lhs_fixed)) -
|
|
@as(Float, @floatFromInt(rhs_fixed)));
|
|
},
|
|
|
|
.float => |rhs_float| self.env.new_float(
|
|
@as(Float, @floatFromInt(lhs_fixed)) - rhs_float),
|
|
|
|
else => self.env.raise(error.TypeMismatch, "right-hand object is not subtractable"),
|
|
},
|
|
|
|
.float => |lhs_float| switch (rhs.object().payload) {
|
|
.float => |rhs_float| self.env.new_float(lhs_float - rhs_float),
|
|
|
|
.fixed => |rhs_fixed| self.env.new_float(
|
|
lhs_float - @as(Float, @floatFromInt(rhs_fixed))),
|
|
|
|
else => self.env.raise(error.TypeMismatch, "right-hand object is not subtractable"),
|
|
},
|
|
|
|
else => self.env.raise(error.TypeMismatch, "left-hand object is not subtractable"),
|
|
});
|
|
},
|
|
|
|
.mul => {
|
|
const rhs = try self.env.expect(try self.pop_local());
|
|
|
|
defer self.env.discard(rhs);
|
|
|
|
const lhs = try self.env.expect(try self.pop_local());
|
|
|
|
defer self.env.discard(lhs);
|
|
|
|
try self.env.locals.push_one(try switch (lhs.object().payload) {
|
|
.fixed => |lhs_fixed| switch (rhs.object().payload) {
|
|
.fixed => |rhs_fixed| mul: {
|
|
if (coral.math.checked_mul(lhs_fixed, rhs_fixed)) |fixed| {
|
|
break: mul self.env.new_fixed(fixed);
|
|
}
|
|
|
|
break: mul self.env.new_float(@as(Float,
|
|
@floatFromInt(lhs_fixed)) *
|
|
@as(Float, @floatFromInt(rhs_fixed)));
|
|
},
|
|
|
|
.float => |rhs_float| self.env.new_float(
|
|
@as(Float, @floatFromInt(lhs_fixed)) * rhs_float),
|
|
|
|
else => self.env.raise(error.TypeMismatch, "right-hand object is not multiplicable"),
|
|
},
|
|
|
|
.float => |lhs_float| switch (rhs.object().payload) {
|
|
.float => |rhs_float| self.env.new_float(lhs_float * rhs_float),
|
|
|
|
.fixed => |rhs_fixed| self.env.new_float(
|
|
lhs_float * @as(Float, @floatFromInt(rhs_fixed))),
|
|
|
|
else => self.env.raise(error.TypeMismatch, "right-hand object is not multiplicable"),
|
|
},
|
|
|
|
else => self.env.raise(error.TypeMismatch, "left-hand object is not multiplicable"),
|
|
});
|
|
},
|
|
|
|
.div => {
|
|
const rhs = try self.env.expect(try self.pop_local());
|
|
|
|
defer self.env.discard(rhs);
|
|
|
|
const lhs = try self.env.expect(try self.pop_local());
|
|
|
|
defer self.env.discard(lhs);
|
|
|
|
try self.env.locals.push_one(try switch (lhs.object().payload) {
|
|
.fixed => |lhs_fixed| switch (rhs.object().payload) {
|
|
.fixed => |rhs_fixed| self.env.new_float(@as(Float,
|
|
@floatFromInt(lhs_fixed)) /
|
|
@as(Float, @floatFromInt(rhs_fixed))),
|
|
|
|
.float => |rhs_float| self.env.new_float(
|
|
@as(Float, @floatFromInt(lhs_fixed)) / rhs_float),
|
|
|
|
else => self.env.raise(error.TypeMismatch, "right-hand object is not divisible"),
|
|
},
|
|
|
|
.float => |lhs_float| switch (rhs.object().payload) {
|
|
.float => |rhs_float| self.env.new_float(lhs_float / rhs_float),
|
|
|
|
.fixed => |rhs_fixed| self.env.new_float(
|
|
lhs_float / @as(Float, @floatFromInt(rhs_fixed))),
|
|
|
|
else => self.env.raise(error.TypeMismatch, "right-hand object is not divisible"),
|
|
},
|
|
|
|
else => self.env.raise(error.TypeMismatch, "left-hand object is not divisible"),
|
|
});
|
|
},
|
|
|
|
.eql => {
|
|
if (try self.pop_local()) |rhs| {
|
|
self.env.discard(rhs);
|
|
|
|
if (try self.pop_local()) |lhs| {
|
|
self.env.discard(lhs);
|
|
|
|
try self.env.locals.push_one(try self.env.new_boolean(lhs.equals(rhs)));
|
|
} else {
|
|
try self.env.locals.push_one(try self.env.new_boolean(false));
|
|
}
|
|
} else {
|
|
if (try self.pop_local()) |lhs| {
|
|
self.env.discard(lhs);
|
|
|
|
try self.env.locals.push_one(try self.env.new_boolean(false));
|
|
} else {
|
|
try self.env.locals.push_one(try self.env.new_boolean(true));
|
|
}
|
|
}
|
|
},
|
|
|
|
.cgt => {
|
|
const rhs = try self.env.expect(try self.pop_local());
|
|
|
|
defer self.env.discard(rhs);
|
|
|
|
const lhs = try self.env.expect(try self.pop_local());
|
|
|
|
defer self.env.discard(lhs);
|
|
|
|
try self.env.locals.push_one(try self.env.new_boolean(switch (lhs.object().payload) {
|
|
.fixed => |lhs_fixed| switch (rhs.object().payload) {
|
|
.fixed => |rhs_fixed| lhs_fixed > rhs_fixed,
|
|
.float => |rhs_float| @as(Float, @floatFromInt(lhs_fixed)) > rhs_float,
|
|
else => return self.env.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.env.raise(error.TypeMismatch, "right-hand object is not comparable"),
|
|
},
|
|
|
|
else => return self.env.raise(error.TypeMismatch, "left-hand object is not comparable"),
|
|
}));
|
|
},
|
|
|
|
.clt => {
|
|
const rhs = try self.env.expect(try self.pop_local());
|
|
|
|
defer self.env.discard(rhs);
|
|
|
|
const lhs = try self.env.expect(try self.pop_local());
|
|
|
|
defer self.env.discard(lhs);
|
|
|
|
try self.env.locals.push_one(try self.env.new_boolean(switch (lhs.object().payload) {
|
|
.fixed => |lhs_fixed| switch (rhs.object().payload) {
|
|
.fixed => |rhs_fixed| lhs_fixed < rhs_fixed,
|
|
.float => |rhs_float| @as(Float, @floatFromInt(lhs_fixed)) < rhs_float,
|
|
else => return self.env.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.env.raise(error.TypeMismatch, "right-hand object is not comparable"),
|
|
},
|
|
|
|
else => return self.env.raise(error.TypeMismatch, "left-hand object is not comparable"),
|
|
}));
|
|
},
|
|
|
|
.cge => {
|
|
const rhs = try self.env.expect(try self.pop_local());
|
|
|
|
defer self.env.discard(rhs);
|
|
|
|
const lhs = try self.env.expect(try self.pop_local());
|
|
|
|
defer self.env.discard(lhs);
|
|
|
|
try self.env.locals.push_one(try self.env.new_boolean(switch (lhs.object().payload) {
|
|
.fixed => |lhs_fixed| switch (rhs.object().payload) {
|
|
.fixed => |rhs_fixed| lhs_fixed >= rhs_fixed,
|
|
.float => |rhs_float| @as(Float, @floatFromInt(lhs_fixed)) >= rhs_float,
|
|
else => return self.env.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.env.raise(error.TypeMismatch, "right-hand object is not comparable"),
|
|
},
|
|
|
|
else => return self.env.raise(error.TypeMismatch, "left-hand object is not comparable"),
|
|
}));
|
|
},
|
|
|
|
.cle => {
|
|
const rhs = try self.env.expect(try self.pop_local());
|
|
|
|
defer self.env.discard(rhs);
|
|
|
|
const lhs = try self.env.expect(try self.pop_local());
|
|
|
|
defer self.env.discard(lhs);
|
|
|
|
try self.env.locals.push_one(try self.env.new_boolean(switch (lhs.object().payload) {
|
|
.fixed => |lhs_fixed| switch (rhs.object().payload) {
|
|
.fixed => |rhs_fixed| lhs_fixed <= rhs_fixed,
|
|
.float => |rhs_float| @as(Float, @floatFromInt(lhs_fixed)) <= rhs_float,
|
|
else => return self.env.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.env.raise(error.TypeMismatch, "right-hand object is not comparable"),
|
|
},
|
|
|
|
else => return self.env.raise(error.TypeMismatch, "left-hand object is not comparable"),
|
|
}));
|
|
},
|
|
|
|
}
|
|
}
|
|
|
|
return self.pop_local();
|
|
}
|
|
|
|
fn free(self: *Chunk) void {
|
|
while (self.constants.pop()) |constant| {
|
|
self.env.discard(constant);
|
|
}
|
|
|
|
self.constants.free();
|
|
self.opcodes.free();
|
|
self.env.allocator.deallocate(self.name);
|
|
}
|
|
|
|
fn make(env: *RuntimeEnv, name: []const coral.io.Byte) coral.io.AllocationError!Chunk {
|
|
return .{
|
|
.name = try coral.io.allocate_copy(env.allocator, name),
|
|
.opcodes = OpcodeList.make(env.allocator),
|
|
.constants = ConstList.make(env.allocator),
|
|
.env = env,
|
|
};
|
|
}
|
|
|
|
fn pop_local(self: *Chunk) RuntimeError!?*RuntimeRef {
|
|
return self.env.locals.pop() orelse self.env.raise(error.IllegalState, "stack underflow");
|
|
}
|
|
};
|
|
|
|
const ConstList = coral.list.Stack(*RuntimeRef);
|
|
|
|
const FrameStack = coral.list.Stack(Frame);
|
|
|
|
const RefList = coral.list.Stack(?*RuntimeRef);
|
|
|
|
const RefTable = coral.map.Table(*RuntimeRef, *RuntimeRef, struct {
|
|
pub const hash = RuntimeRef.hash;
|
|
|
|
pub const equals = RuntimeRef.equals;
|
|
});
|
|
|
|
const SymbolSet = coral.map.StringTable([:0]coral.io.Byte);
|
|
|
|
const Table = struct {
|
|
associative: RefTable,
|
|
contiguous: RefList,
|
|
|
|
fn typeinfo_destruct(method: Typeinfo.Method) void {
|
|
const table = @as(*Table, @ptrCast(@alignCast(method.userdata.ptr)));
|
|
|
|
{
|
|
var field_iterable = table.associative.as_iterable();
|
|
|
|
while (field_iterable.next()) |entry| {
|
|
method.env.discard(entry.key);
|
|
method.env.discard(entry.value);
|
|
}
|
|
}
|
|
|
|
table.associative.free();
|
|
|
|
while (table.contiguous.pop()) |value| {
|
|
if (value) |ref| {
|
|
method.env.discard(ref);
|
|
}
|
|
}
|
|
|
|
table.contiguous.free();
|
|
}
|
|
|
|
fn typeinfo_get(method: Typeinfo.Method, index: *const RuntimeRef) RuntimeError!?*RuntimeRef {
|
|
const table = @as(*Table, @ptrCast(@alignCast(method.userdata.ptr)));
|
|
const acquired_index = try method.env.acquire(index);
|
|
|
|
defer method.env.discard(acquired_index);
|
|
|
|
if (acquired_index.is_fixed()) |fixed| {
|
|
if (fixed < 0) {
|
|
// TODO: Negative indexing.
|
|
unreachable;
|
|
}
|
|
|
|
if (fixed < table.contiguous.values.len) {
|
|
return method.env.acquire(table.contiguous.values[@intCast(fixed)] orelse return null);
|
|
}
|
|
}
|
|
|
|
if (table.associative.lookup(acquired_index)) |value_ref| {
|
|
return method.env.acquire(value_ref);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
fn typeinfo_set(method: Typeinfo.Method, index: *const RuntimeRef, value: ?*const RuntimeRef) RuntimeError!void {
|
|
const table = @as(*Table, @ptrCast(@alignCast(method.userdata.ptr)));
|
|
const acquired_index = try method.env.acquire(index);
|
|
|
|
errdefer method.env.discard(acquired_index);
|
|
|
|
if (acquired_index.is_fixed()) |fixed| {
|
|
if (fixed < 0) {
|
|
// TODO: Negative indexing.
|
|
unreachable;
|
|
}
|
|
|
|
if (fixed < table.contiguous.values.len) {
|
|
const maybe_replacing = &table.contiguous.values[@intCast(fixed)];
|
|
|
|
if (maybe_replacing.*) |replacing| {
|
|
method.env.discard(replacing);
|
|
}
|
|
|
|
maybe_replacing.* = if (value) |ref| try method.env.acquire(ref) else null;
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
const acquired_value = try method.env.acquire(value orelse {
|
|
if (table.associative.remove(acquired_index)) |removed| {
|
|
method.env.discard(removed.key);
|
|
method.env.discard(removed.value);
|
|
}
|
|
|
|
return;
|
|
});
|
|
|
|
errdefer method.env.discard(acquired_value);
|
|
|
|
if (try table.associative.replace(acquired_index, acquired_value)) |replaced| {
|
|
method.env.discard(replaced.key);
|
|
method.env.discard(replaced.value);
|
|
}
|
|
}
|
|
|
|
};
|
|
|
|
pub fn acquire(self: *RuntimeEnv, value: *const RuntimeRef) RuntimeError!*RuntimeRef {
|
|
const object = value.object();
|
|
|
|
object.ref_count = coral.math.checked_add(object.ref_count, 1) orelse {
|
|
return self.raise(error.IllegalState, "reference overflow");
|
|
};
|
|
|
|
return @ptrCast(object);
|
|
}
|
|
|
|
pub fn arg(self: *RuntimeEnv, index: usize) RuntimeError!?*RuntimeRef {
|
|
const frame = self.frames.peek() orelse return self.raise(error.IllegalState, "stack underflow");
|
|
|
|
if (index < frame.arg_count) {
|
|
if (self.locals.values[frame.locals_top - (1 + index)]) |local| {
|
|
return self.acquire(local);
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
pub fn bind_system(self: *RuntimeEnv, name: []const coral.io.Byte, value: *const RuntimeRef) RuntimeError!void {
|
|
const name_symbol = try self.new_symbol(name);
|
|
|
|
errdefer self.discard(name_symbol);
|
|
|
|
const acquired_value = try self.acquire(value);
|
|
|
|
errdefer self.discard(acquired_value);
|
|
|
|
if (try self.system_bindings.replace(name_symbol, acquired_value)) |replaced| {
|
|
self.discard(replaced.key);
|
|
self.discard(replaced.value);
|
|
}
|
|
}
|
|
|
|
pub fn call(self: *RuntimeEnv, callable: *RuntimeRef, args: []const *RuntimeRef) RuntimeError!?*RuntimeRef {
|
|
try self.locals.push_all(args);
|
|
|
|
defer coral.io.assert(self.locals.drop(args.len));
|
|
|
|
try self.frames.push_one(.{
|
|
.name = "<native>",
|
|
.arg_count = args.len,
|
|
.locals_top = self.locals.values.len,
|
|
});
|
|
|
|
defer coral.io.assert(self.frames.pop() != null);
|
|
|
|
return switch (callable.object().payload) {
|
|
.dynamic => |dynamic| dynamic.typeinfo.call(.{
|
|
.userdata = dynamic.userdata(),
|
|
.env = self,
|
|
}),
|
|
|
|
else => self.raise(error.TypeMismatch, "object is not callable"),
|
|
};
|
|
}
|
|
|
|
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 => {},
|
|
|
|
.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(.{
|
|
.userdata = dynamic.userdata(),
|
|
.env = self,
|
|
});
|
|
}
|
|
|
|
self.allocator.deallocate(dynamic.unpack());
|
|
},
|
|
}
|
|
|
|
self.allocator.deallocate(object);
|
|
}
|
|
}
|
|
|
|
pub fn execute_file(self: *RuntimeEnv, file_access: file.Access, file_path: file.Path) RuntimeError!?*RuntimeRef {
|
|
const file_data = (try file.allocate_and_load(self.allocator, file_access, file_path)) orelse {
|
|
return self.raise(error.BadOperation, "failed to open or read file specified");
|
|
};
|
|
|
|
defer self.allocator.deallocate(file_data);
|
|
|
|
const file_name = file_path.to_string() orelse "<script>";
|
|
var ast = Ast.make(self.allocator, file_name);
|
|
|
|
defer ast.free();
|
|
|
|
ast.parse(file_data) catch |parse_error| return switch (parse_error) {
|
|
error.BadSyntax => self.raise(error.BadSyntax, ast.error_message()),
|
|
error.OutOfMemory => error.OutOfMemory,
|
|
};
|
|
|
|
var chunk = try Chunk.make(self, file_name);
|
|
|
|
defer chunk.free();
|
|
|
|
try chunk.compile(ast);
|
|
|
|
return chunk.execute();
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
{
|
|
var iterable = self.system_bindings.as_iterable();
|
|
|
|
while (iterable.next()) |entry| {
|
|
self.discard(entry.key);
|
|
self.discard(entry.value);
|
|
}
|
|
}
|
|
|
|
self.frames.free();
|
|
self.locals.free();
|
|
self.system_bindings.free();
|
|
self.interned_symbols.free();
|
|
}
|
|
|
|
pub fn get(self: *RuntimeEnv, indexable: *RuntimeRef, index: *const RuntimeRef) RuntimeError!?*RuntimeRef {
|
|
return switch (indexable.object().payload) {
|
|
.dynamic => |dynamic| dynamic.typeinfo().get(.{
|
|
.userdata = dynamic.userdata(),
|
|
.env = self,
|
|
}, index),
|
|
|
|
else => self.raise(error.TypeMismatch, "object is not get-indexable"),
|
|
};
|
|
}
|
|
|
|
pub fn make(allocator: coral.io.Allocator, error_handler: ErrorHandler) coral.io.AllocationError!RuntimeEnv {
|
|
return RuntimeEnv{
|
|
.locals = RefList.make(allocator),
|
|
.frames = FrameStack.make(allocator),
|
|
.system_bindings = RefTable.make(allocator, .{}),
|
|
.interned_symbols = SymbolSet.make(allocator, .{}),
|
|
.error_handler = error_handler,
|
|
.allocator = allocator,
|
|
};
|
|
}
|
|
|
|
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(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_table(self: *RuntimeEnv) RuntimeError!*RuntimeRef {
|
|
var table = Table{
|
|
.associative = RefTable.make(self.allocator, .{}),
|
|
.contiguous = RefList.make(self.allocator),
|
|
};
|
|
|
|
errdefer {
|
|
table.associative.free();
|
|
table.contiguous.free();
|
|
}
|
|
|
|
return try self.new_dynamic(coral.io.bytes_of(&table).ptr, &.{
|
|
.name = "table",
|
|
.size = @sizeOf(Table),
|
|
.destruct = Table.typeinfo_destruct,
|
|
.get = Table.typeinfo_get,
|
|
.set = Table.typeinfo_set,
|
|
});
|
|
}
|
|
|
|
pub fn raise(self: *RuntimeEnv, error_value: RuntimeError, message: []const coral.io.Byte) RuntimeError {
|
|
self.error_handler.invoke(.{
|
|
.message = message,
|
|
.frames = self.frames.values,
|
|
});
|
|
|
|
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(.{
|
|
.userdata = dynamic.userdata(),
|
|
.env = self,
|
|
}, index, value),
|
|
|
|
else => self.raise(error.TypeMismatch, "object is not set-indexable"),
|
|
};
|
|
}
|
|
|
|
pub fn unbox_dynamic(env: *RuntimeEnv, value: *const RuntimeRef) RuntimeError![]coral.io.Byte {
|
|
return switch (value.object().payload) {
|
|
.dynamic => |dynamic| dynamic.userdata(),
|
|
else => env.raise(error.TypeMismatch, "expected fixed object")
|
|
};
|
|
}
|
|
|
|
pub fn unbox_float(env: *RuntimeEnv, value: *const RuntimeRef) RuntimeError!Float {
|
|
return switch (value.object().payload) {
|
|
.fixed => |fixed| @floatFromInt(fixed),
|
|
.float => |float| float,
|
|
else => env.raise(error.TypeMismatch, "expected float object")
|
|
};
|
|
}
|
|
|
|
pub fn unbox_fixed(env: *RuntimeEnv, value: *const RuntimeRef) RuntimeError!Fixed {
|
|
return value.is_fixed() orelse env.raise(error.TypeMismatch, "expected fixed object");
|
|
}
|
|
|
|
pub fn unbox_string(env: *RuntimeEnv, value: *const RuntimeRef) RuntimeError![]coral.io.Byte {
|
|
return switch (value.object().payload) {
|
|
.string => |string| extract: {
|
|
coral.debug.assert(string.len >= 0);
|
|
|
|
break: extract string.ptr[0 .. @intCast(string.len)];
|
|
},
|
|
|
|
else => env.raise(error.TypeMismatch, "expected string object")
|
|
};
|
|
}
|
|
|
|
pub fn unbox_symbol(env: *RuntimeEnv, value: *RuntimeRef) RuntimeError![*:0]const coral.io.Byte {
|
|
return switch (value.object().payload) {
|
|
.symbol => |symbol| symbol,
|
|
else => env.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,
|
|
|
|
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 ..];
|
|
}
|
|
},
|
|
},
|
|
};
|
|
|
|
fn allocate(allocator: coral.io.Allocator, data: Object) coral.io.AllocationError!*RuntimeRef {
|
|
return @ptrCast(try coral.io.allocate_one(allocator, data));
|
|
}
|
|
|
|
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,
|
|
},
|
|
|
|
.string => |self_string| switch (other.object().payload) {
|
|
.string => |other_string| coral.io.equals(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| @bitCast(@intFromPtr(symbol)),
|
|
.string => |string| coral.io.djb2_hash(@typeInfo(usize).Int, string.unpack()),
|
|
.dynamic => |dynamic| @intFromPtr(dynamic.typeinfo()) ^ @intFromPtr(dynamic.userdata().ptr),
|
|
};
|
|
}
|
|
|
|
pub fn is_fixed(self: *const RuntimeRef) ?Fixed {
|
|
return switch (self.object().payload) {
|
|
.fixed => |fixed| @intCast(@as(u32, @bitCast(fixed))),
|
|
else => null,
|
|
};
|
|
}
|
|
|
|
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,
|
|
.string => |string| string.len != 0,
|
|
.dynamic => true,
|
|
};
|
|
}
|
|
};
|
|
|
|
pub const Typeinfo = struct {
|
|
name: []const coral.io.Byte,
|
|
size: usize,
|
|
destruct: ?*const fn (method: Method) void = null,
|
|
call: *const fn (method: Method) RuntimeError!?*RuntimeRef = default_call,
|
|
get: *const fn (method: Method, index: *const RuntimeRef) RuntimeError!?*RuntimeRef = default_get,
|
|
set: *const fn (method: Method, value: *const RuntimeRef, value: ?*const RuntimeRef) RuntimeError!void = default_set,
|
|
|
|
pub const Method = struct {
|
|
env: *RuntimeEnv,
|
|
userdata: []coral.io.Byte,
|
|
};
|
|
|
|
fn default_call(method: Method) RuntimeError!?*RuntimeRef {
|
|
return method.env.raise(error.TypeMismatch, "object is not callable");
|
|
}
|
|
|
|
fn default_get(method: Method, _: *const RuntimeRef) RuntimeError!?*RuntimeRef {
|
|
return method.env.raise(error.TypeMismatch, "object is not index-gettable");
|
|
}
|
|
|
|
fn default_set(method: Method, _: *const RuntimeRef, _: ?*const RuntimeRef) RuntimeError!void {
|
|
return method.env.raise(error.TypeMismatch, "object is not index-settable");
|
|
}
|
|
};
|
|
|
|
pub fn bind_syscaller(env: *RuntimeEnv, name: []const coral.io.Byte, caller: Caller) RuntimeError!void {
|
|
const callable = try new_caller(env, caller);
|
|
|
|
defer env.discard(callable);
|
|
|
|
try env.bind_system(name, callable);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
pub fn new_caller(env: *RuntimeEnv, value: Caller) RuntimeError!*RuntimeRef {
|
|
const Callable = struct {
|
|
fn call(method: Typeinfo.Method) RuntimeError!?*RuntimeRef {
|
|
coral.debug.assert(method.userdata.len == @sizeOf(Caller));
|
|
|
|
return @as(*Caller, @ptrCast(@alignCast(method.userdata))).invoke(method.env);
|
|
}
|
|
};
|
|
|
|
return env.new_dynamic(coral.io.bytes_of(&value).ptr, &.{
|
|
.name = "<native>",
|
|
.size = @sizeOf(Caller),
|
|
.call = Callable.call,
|
|
});
|
|
}
|