1740 lines
49 KiB
Zig
1740 lines
49 KiB
Zig
const ast = @import("./kym/ast.zig");
|
|
|
|
const coral = @import("coral");
|
|
|
|
const file = @import("./file.zig");
|
|
|
|
pub const Fixed = i32;
|
|
|
|
pub const Float = f64;
|
|
|
|
pub const RuntimeEnv = struct {
|
|
options: Options,
|
|
interned_symbols: SymbolSet,
|
|
allocator: coral.io.Allocator,
|
|
locals: RefList,
|
|
frames: FrameStack,
|
|
|
|
const Chunk = struct {
|
|
env: *RuntimeEnv,
|
|
name: []coral.io.Byte,
|
|
opcodes: OpcodeList,
|
|
constants: ConstList,
|
|
|
|
const Builtin = enum {
|
|
import,
|
|
print,
|
|
vec2,
|
|
vec3,
|
|
};
|
|
|
|
const Constant = union (enum) {
|
|
fixed: Fixed,
|
|
float: Float,
|
|
string: []const coral.io.Byte,
|
|
symbol: []const coral.io.Byte,
|
|
};
|
|
|
|
const Opcode = union (enum) {
|
|
pop,
|
|
push_nil,
|
|
push_true,
|
|
push_false,
|
|
push_const: u16,
|
|
push_local: u8,
|
|
push_table: u32,
|
|
push_builtin: Builtin,
|
|
local_set: u8,
|
|
object_get,
|
|
object_set,
|
|
object_call: u8,
|
|
|
|
not,
|
|
neg,
|
|
|
|
add,
|
|
sub,
|
|
mul,
|
|
div,
|
|
|
|
eql,
|
|
cgt,
|
|
clt,
|
|
cge,
|
|
cle,
|
|
|
|
jt: u32,
|
|
jf: u32,
|
|
};
|
|
|
|
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.*);
|
|
},
|
|
|
|
.builtin => |builtin| {
|
|
coral.debug.assert(builtin.len != 0);
|
|
|
|
switch (builtin[0]) {
|
|
'i' => {
|
|
if (coral.io.ends_with(builtin, "mport")) {
|
|
return chunk.opcodes.push_one(.{.push_builtin = .import});
|
|
}
|
|
},
|
|
|
|
'p' => {
|
|
if (coral.io.ends_with(builtin, "rint")) {
|
|
return chunk.opcodes.push_one(.{.push_builtin = .print});
|
|
}
|
|
},
|
|
|
|
'v' => {
|
|
if (coral.io.ends_with(builtin, "ec2")) {
|
|
return chunk.opcodes.push_one(.{.push_builtin = .vec2});
|
|
}
|
|
|
|
if (coral.io.ends_with(builtin, "ec3")) {
|
|
return chunk.opcodes.push_one(.{.push_builtin = .vec3});
|
|
}
|
|
},
|
|
|
|
else => {},
|
|
}
|
|
|
|
return chunk.env.raise(error.BadSyntax, "unknown builtin");
|
|
},
|
|
|
|
.local_get => |local_get| {
|
|
try chunk.opcodes.push_one(.{
|
|
.push_local = self.resolve_local(local_get.identifier) orelse {
|
|
return chunk.env.raise(error.OutOfMemory, "undefined local");
|
|
},
|
|
});
|
|
},
|
|
|
|
.local_set => |local_set| {
|
|
try self.compile_expression(chunk, local_set.value_expression.*);
|
|
|
|
if (self.resolve_local(local_set.identifier)) |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.identifier;
|
|
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| {
|
|
try self.compile_expression(chunk, expression);
|
|
} else {
|
|
try chunk.opcodes.push_one(.push_nil);
|
|
}
|
|
},
|
|
|
|
.block => |block| {
|
|
for (block.values) |block_statement| {
|
|
try self.compile_statement(chunk, block_statement);
|
|
}
|
|
},
|
|
|
|
.@"while" => |@"while"| {
|
|
try self.compile_expression(chunk, @"while".condition_expression);
|
|
try chunk.opcodes.push_one(.{.jf = 0});
|
|
|
|
const origin_index = @as(u32, @intCast(chunk.opcodes.values.len - 1));
|
|
|
|
for (@"while".block_statements.values) |block_statement| {
|
|
try self.compile_statement(chunk, block_statement);
|
|
}
|
|
|
|
chunk.opcodes.values[origin_index].jf = @intCast(chunk.opcodes.values.len - 1);
|
|
|
|
try self.compile_expression(chunk, @"while".condition_expression);
|
|
try chunk.opcodes.push_one(.{.jt = origin_index});
|
|
},
|
|
|
|
.@"if" => |@"if"| {
|
|
try self.compile_expression(chunk, @"if".then_block.condition_expression);
|
|
try chunk.opcodes.push_one(.{.jf = 0});
|
|
|
|
const then_origin_index = @as(u32, @intCast(chunk.opcodes.values.len - 1));
|
|
|
|
for (@"if".then_block.block_statements.values) |block_statement| {
|
|
try self.compile_statement(chunk, block_statement);
|
|
}
|
|
|
|
chunk.opcodes.values[then_origin_index].jf = @intCast(chunk.opcodes.values.len - 1);
|
|
|
|
if (@"if".else_block) |else_block| {
|
|
if (else_block.condition_expression) |condition_expression| {
|
|
try self.compile_expression(chunk, condition_expression);
|
|
try chunk.opcodes.push_one(.{.jf = 0});
|
|
}
|
|
|
|
const else_origin_index = @as(u32, @intCast(chunk.opcodes.values.len - 1));
|
|
|
|
for (else_block.block_statements.values) |block_statement| {
|
|
try self.compile_statement(chunk, block_statement);
|
|
}
|
|
|
|
if (else_block.condition_expression != null) {
|
|
chunk.opcodes.values[else_origin_index].jf = @intCast(chunk.opcodes.values.len - 1);
|
|
}
|
|
}
|
|
},
|
|
|
|
.expression => |expression| {
|
|
try self.compile_expression(chunk, expression);
|
|
|
|
if (expression == .invoke) {
|
|
try chunk.opcodes.push_one(.pop);
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
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.are_equal(local_identifier, self.local_identifiers_buffer[index])) {
|
|
return index;
|
|
}
|
|
|
|
if (index == 0) {
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
fn compile(self: *Chunk, statements: *const ast.StatementList) RuntimeError!void {
|
|
var unit = CompilationUnit{};
|
|
|
|
for (statements.values) |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);
|
|
|
|
var opcode_cursor = @as(u32, 0);
|
|
|
|
while (opcode_cursor < self.opcodes.values.len) : (opcode_cursor += 1) {
|
|
switch (self.opcodes.values[opcode_cursor]) {
|
|
.pop => {
|
|
if (try self.pop_local()) |ref| {
|
|
self.env.discard(ref);
|
|
}
|
|
},
|
|
|
|
.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 (push_local >= self.env.locals.values.len) {
|
|
return self.env.raise(error.IllegalState, "invalid 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_builtin => |push_builtin| {
|
|
const builtin_syscall = try self.env.new_syscall(switch (push_builtin) {
|
|
.import => syscall_import,
|
|
.print => syscall_print,
|
|
.vec2 => syscall_vec2,
|
|
.vec3 => syscall_vec3,
|
|
});
|
|
|
|
errdefer self.env.discard(builtin_syscall);
|
|
|
|
try self.env.locals.push_one(builtin_syscall);
|
|
},
|
|
|
|
.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.env.expect(try self.pop_local());
|
|
|
|
defer self.env.discard(index);
|
|
|
|
const indexable = try self.env.expect(try self.pop_local());
|
|
|
|
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) {
|
|
.syscall => |syscall| syscall(self.env),
|
|
|
|
.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"),
|
|
}));
|
|
},
|
|
|
|
.jf => |jf| {
|
|
const condition = (try self.pop_local()) orelse {
|
|
opcode_cursor = jf;
|
|
|
|
continue;
|
|
};
|
|
|
|
self.env.discard(condition);
|
|
|
|
if (!condition.is_truthy()) {
|
|
opcode_cursor = jf;
|
|
}
|
|
},
|
|
|
|
.jt => |jt| {
|
|
const condition = (try self.pop_local()) orelse continue;
|
|
|
|
self.env.discard(condition);
|
|
|
|
if (condition.is_truthy()) {
|
|
opcode_cursor = jt;
|
|
}
|
|
},
|
|
}
|
|
|
|
// for (self.env.locals.values) |local| {
|
|
// self.env.print(if (local) |ref| ref.typename() else "nil");
|
|
// }
|
|
|
|
// self.env.print("------------");
|
|
}
|
|
|
|
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(struct {
|
|
name: []const coral.io.Byte,
|
|
arg_count: u8,
|
|
locals_top: usize,
|
|
});
|
|
|
|
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 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 acquire_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 - frame.arg_count) + 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) {
|
|
.syscall => |syscall| syscall(self.env),
|
|
|
|
.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, .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(.{
|
|
.userdata = dynamic.userdata(),
|
|
.env = self,
|
|
});
|
|
}
|
|
|
|
self.allocator.deallocate(dynamic.unpack());
|
|
},
|
|
}
|
|
|
|
self.allocator.deallocate(object);
|
|
}
|
|
}
|
|
|
|
pub fn import(self: *RuntimeEnv, file_path: file.Path) RuntimeError!?*RuntimeRef {
|
|
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);
|
|
|
|
const file_name = file_path.to_string() orelse "<script>";
|
|
var chunk = try Chunk.make(self, file_name);
|
|
|
|
defer chunk.free();
|
|
|
|
var ast_tree = ast.Tree.make(self.allocator, file_name);
|
|
|
|
defer ast_tree.free();
|
|
|
|
try chunk.compile(ast_tree.parse(file_data) catch |parse_error| return switch (parse_error) {
|
|
error.BadSyntax => self.raise(error.BadSyntax, ast_tree.error_message()),
|
|
error.OutOfMemory => error.OutOfMemory,
|
|
});
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
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.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(.{
|
|
.userdata = dynamic.userdata(),
|
|
.env = self,
|
|
}, index),
|
|
|
|
else => self.raise(error.TypeMismatch, "object is not get-indexable"),
|
|
};
|
|
}
|
|
|
|
pub fn make(allocator: coral.io.Allocator, options: Options) coral.io.AllocationError!RuntimeEnv {
|
|
return RuntimeEnv{
|
|
.locals = RefList.make(allocator),
|
|
.frames = FrameStack.make(allocator),
|
|
.interned_symbols = SymbolSet.make(allocator, .{}),
|
|
.options = options,
|
|
.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_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{
|
|
.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 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 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(.{
|
|
.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: *const 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,
|
|
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 ..];
|
|
}
|
|
},
|
|
},
|
|
};
|
|
|
|
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,
|
|
},
|
|
|
|
.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_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,
|
|
.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) RuntimeError!?*RuntimeRef;
|
|
|
|
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 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);
|
|
}
|
|
|
|
fn syscall_import(env: *RuntimeEnv) RuntimeError!?*RuntimeRef {
|
|
const arg = try env.expect(try env.acquire_arg(0));
|
|
|
|
defer env.discard(arg);
|
|
|
|
return env.import(file.Path.from(&.{try env.unbox_string(arg)}));
|
|
}
|
|
|
|
fn syscall_print(env: *RuntimeEnv) RuntimeError!?*RuntimeRef {
|
|
const arg = try env.expect(try env.acquire_arg(0));
|
|
|
|
defer env.discard(arg);
|
|
|
|
env.print(try env.unbox_string(arg));
|
|
|
|
return null;
|
|
}
|
|
|
|
fn syscall_vec2(env: *RuntimeEnv) RuntimeError!?*RuntimeRef {
|
|
const x = decode: {
|
|
const value = try env.expect(try env.acquire_arg(0));
|
|
|
|
defer env.discard(value);
|
|
|
|
break: decode @as(f32, @floatCast(try env.unbox_float(value)));
|
|
};
|
|
|
|
if (try env.acquire_arg(1)) |y| {
|
|
defer env.discard(y);
|
|
|
|
return env.new_vector2(x, @floatCast(try env.unbox_float(y)));
|
|
}
|
|
|
|
return env.new_vector2(x, x);
|
|
}
|
|
|
|
fn syscall_vec3(env: *RuntimeEnv) RuntimeError!?*RuntimeRef {
|
|
const x = decode: {
|
|
const value = try env.expect(try env.acquire_arg(0));
|
|
|
|
defer env.discard(value);
|
|
|
|
break: decode @as(f32, @floatCast(try env.unbox_float(value)));
|
|
};
|
|
|
|
if (try env.acquire_arg(1)) |y| {
|
|
defer env.discard(y);
|
|
|
|
const z = try env.expect(try env.acquire_arg(2));
|
|
|
|
defer env.discard(z);
|
|
|
|
return env.new_vector3(x, @floatCast(try env.unbox_float(y)), @floatCast(try env.unbox_float(z)));
|
|
}
|
|
|
|
return env.new_vector3(x, x, x);
|
|
}
|