ona/source/ona/kym.zig
2023-08-24 23:45:29 +01:00

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);
}