Refactor Kym runtime values to be objects
This commit is contained in:
parent
440d63447f
commit
1828eb3b45
|
@ -223,6 +223,21 @@ pub fn copy(target: []Byte, source: []const Byte) void {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn compare(this: []const Byte, that: []const Byte) isize {
|
||||
const range = math.min(this.len, that.len);
|
||||
var index: usize = 0;
|
||||
|
||||
while (index < range) : (index += 1) {
|
||||
const difference = @as(isize, @intCast(this[index])) - @as(isize, @intCast(that[index]));
|
||||
|
||||
if (difference != 0) {
|
||||
return difference;
|
||||
}
|
||||
}
|
||||
|
||||
return @as(isize, @intCast(this.len)) - @as(isize, @intCast(that.len));
|
||||
}
|
||||
|
||||
pub fn ends_with(target: []const Byte, match: []const Byte) bool {
|
||||
if (target.len < match.len) {
|
||||
return false;
|
||||
|
|
|
@ -16,6 +16,16 @@ pub fn Stack(comptime Value: type) type {
|
|||
self.values = self.values[0 .. 0];
|
||||
}
|
||||
|
||||
pub fn drop(self: *Self, amount: usize) bool {
|
||||
if (amount > self.values.len) {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.values = self.values[0 .. self.values.len - amount];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn free(self: *Self) void {
|
||||
if (self.capacity == 0) {
|
||||
return;
|
||||
|
@ -49,6 +59,28 @@ pub fn Stack(comptime Value: type) type {
|
|||
};
|
||||
}
|
||||
|
||||
pub fn pack(self: *Self) io.AllocationError!void {
|
||||
const packed_size = self.values.len;
|
||||
const buffer = try self.allocator.reallocate(null, @sizeOf(Value) * self.values.len);
|
||||
|
||||
io.copy(buffer, io.bytes_of(self.values));
|
||||
|
||||
if (self.capacity != 0) {
|
||||
self.allocator.deallocate(self.values.ptr[0 .. self.capacity]);
|
||||
}
|
||||
|
||||
self.values = @as([*]Value, @ptrCast(@alignCast(buffer)))[0 .. packed_size];
|
||||
self.capacity = packed_size;
|
||||
}
|
||||
|
||||
pub fn peek(self: *Self) ?Value {
|
||||
if (self.values.len == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return &self.values[self.values.len - 1];
|
||||
}
|
||||
|
||||
pub fn pop(self: *Self) ?Value {
|
||||
if (self.values.len == 0) {
|
||||
return null;
|
||||
|
|
|
@ -55,6 +55,23 @@ pub fn Slab(comptime Value: type) type {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn insert_at(self: *Self, key: usize, value: Value) bool {
|
||||
if (self.entries.values.len < key) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const entry = &self.entries.values[key - 1];
|
||||
|
||||
if (entry.* == .value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.next_index = entry.next_index;
|
||||
entry.* = .{.value = value};
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn make(allocator: io.Allocator) Self {
|
||||
return .{
|
||||
.next_index = 0,
|
||||
|
@ -143,6 +160,25 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra
|
|||
|
||||
const Self = @This();
|
||||
|
||||
pub fn remove(self: *Self, key: Key) ?Entry {
|
||||
const hash_max = math.min(math.max_int(@typeInfo(usize).Int), self.entries.len);
|
||||
var hashed_key = math.wrap(traits.hash(key), math.min_int(@typeInfo(usize).Int), hash_max);
|
||||
|
||||
while (true) {
|
||||
const entry = &(self.entries[hashed_key] orelse continue);
|
||||
|
||||
if (traits.match(entry.key, key)) {
|
||||
const original_entry = entry.*;
|
||||
|
||||
self.entries[hashed_key] = null;
|
||||
|
||||
return original_entry;
|
||||
}
|
||||
|
||||
hashed_key = (hashed_key +% 1) % hash_max;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn replace(self: *Self, key: Key, value: Value) io.AllocationError!?Entry {
|
||||
try self.rehash(load_max);
|
||||
|
||||
|
|
|
@ -12,39 +12,53 @@ pub const Manifest = struct {
|
|||
height: u16 = 480,
|
||||
tick_rate: f32 = 60.0,
|
||||
|
||||
pub fn load(self: *Manifest, env: *kym.RuntimeEnv, file_access: file.Access) kym.RuntimeError!void {
|
||||
const manifest = try env.execute_file(file_access, file.Path.from(&.{"app.ona"}));
|
||||
pub fn load(self: *Manifest, env: *kym.RuntimeEnv, global_ref: ?*const kym.RuntimeRef, file_access: file.Access) kym.RuntimeError!void {
|
||||
var manifest_ref = try env.execute_file(global_ref, file_access, file.Path.from(&.{"app.ona"}));
|
||||
|
||||
defer env.discard(manifest);
|
||||
defer env.discard(&manifest_ref);
|
||||
|
||||
const title = try env.get_field(manifest, "title");
|
||||
var title_ref = try kym.get_dynamic_field(env, manifest_ref, "title");
|
||||
|
||||
defer env.discard(title);
|
||||
defer env.discard(&title_ref);
|
||||
|
||||
const title_string = try env.get_string(title);
|
||||
const title_string = switch (env.unbox(title_ref)) {
|
||||
.string => |string| string,
|
||||
else => "",
|
||||
};
|
||||
|
||||
const width = @as(u16, get: {
|
||||
const ref = try env.get_field(manifest, "width");
|
||||
var ref = try kym.get_dynamic_field(env, manifest_ref, "width");
|
||||
|
||||
defer env.discard(ref);
|
||||
defer env.discard(&ref);
|
||||
|
||||
break: get @intFromFloat(env.get_float(ref) catch @as(f64, @floatFromInt(self.width)));
|
||||
// TODO: Add safety-checks to int cast.
|
||||
break: get switch (env.unbox(ref)) {
|
||||
.number => |number| @intFromFloat(number),
|
||||
else => self.width,
|
||||
};
|
||||
});
|
||||
|
||||
const height = @as(u16, get: {
|
||||
const ref = try env.get_field(manifest, "height");
|
||||
var ref = try kym.get_dynamic_field(env, manifest_ref, "height");
|
||||
|
||||
defer env.discard(ref);
|
||||
defer env.discard(&ref);
|
||||
|
||||
break: get @intFromFloat(env.get_float(ref) catch @as(f64, @floatFromInt(self.height)));
|
||||
// TODO: Add safety-checks to int cast.
|
||||
break: get switch (env.unbox(ref)) {
|
||||
.number => |number| @intFromFloat(number),
|
||||
else => self.height,
|
||||
};
|
||||
});
|
||||
|
||||
const tick_rate = @as(f32, get: {
|
||||
const ref = try env.get_field(manifest, "tick_rate");
|
||||
var ref = try kym.get_dynamic_field(env, manifest_ref, "tick_rate");
|
||||
|
||||
defer env.discard(ref);
|
||||
defer env.discard(&ref);
|
||||
|
||||
break: get @floatCast(env.get_float(ref) catch self.tick_rate);
|
||||
break: get switch (env.unbox(ref)) {
|
||||
.number => |number| @floatCast(number),
|
||||
else => self.tick_rate,
|
||||
};
|
||||
});
|
||||
|
||||
{
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
const Ast = @import("./kym/Ast.zig");
|
||||
|
||||
const State = @import("./kym/State.zig");
|
||||
const Chunk = @import("./kym/Chunk.zig");
|
||||
|
||||
const Table = @import("./kym/Table.zig");
|
||||
|
||||
|
@ -12,10 +12,9 @@ const tokens = @import("./kym/tokens.zig");
|
|||
|
||||
pub const CallContext = struct {
|
||||
env: *RuntimeEnv,
|
||||
caller: *const RuntimeRef,
|
||||
callable: *const RuntimeRef,
|
||||
caller: ?*const RuntimeRef,
|
||||
userdata: []u8,
|
||||
args: []const *const RuntimeRef = &.{},
|
||||
args: []const ?*const RuntimeRef = &.{},
|
||||
|
||||
pub fn arg_at(self: CallContext, index: u8) RuntimeError!*const RuntimeRef {
|
||||
if (!coral.math.is_clamped(index, 0, self.args.len - 1)) {
|
||||
|
@ -26,408 +25,106 @@ pub const CallContext = struct {
|
|||
}
|
||||
};
|
||||
|
||||
const Compiler = struct {
|
||||
state: *State,
|
||||
opcodes: OpcodeList,
|
||||
|
||||
locals: struct {
|
||||
buffer: [255][]const coral.io.Byte = [_][]const coral.io.Byte{""} ** 255,
|
||||
count: u8 = 0,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
fn declare(self: *Self, identifier: []const u8) CompileError!void {
|
||||
if (self.count == self.buffer.len) {
|
||||
return error.TooManyLocals;
|
||||
}
|
||||
|
||||
self.buffer[self.count] = identifier;
|
||||
self.count += 1;
|
||||
}
|
||||
|
||||
fn resolve(self: *Self, local_identifier: []const coral.io.Byte) ?u8 {
|
||||
if (self.count == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var index = @as(u8, self.count - 1);
|
||||
|
||||
while (true) : (index -= 1) {
|
||||
if (coral.io.equals(local_identifier, self.buffer[index])) {
|
||||
return index;
|
||||
}
|
||||
|
||||
if (index == 0) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
const CompileError = coral.io.AllocationError || error {
|
||||
UndefinedLocal,
|
||||
TooManyLocals,
|
||||
};
|
||||
|
||||
const LocalsList = coral.list.Stack([]const u8);
|
||||
|
||||
const OpcodeList = coral.list.Stack(Opcode);
|
||||
|
||||
fn compile_ast(self: *Compiler, ast: Ast) CompileError!void {
|
||||
for (ast.list_statements()) |statement| {
|
||||
switch (statement) {
|
||||
.return_expression => |return_expression| {
|
||||
try self.compile_expression(return_expression);
|
||||
},
|
||||
|
||||
.return_nothing => {
|
||||
try self.opcodes.push_one(.push_nil);
|
||||
},
|
||||
|
||||
.set_local => |local| {
|
||||
try self.compile_expression(local.expression);
|
||||
|
||||
if (self.locals.resolve(local.identifier)) |index| {
|
||||
try self.opcodes.push_one(.{.set_local = index});
|
||||
} else {
|
||||
try self.locals.declare(local.identifier);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn compile_expression(self: *Compiler, expression: Ast.Expression) CompileError!void {
|
||||
const is_zero = struct {
|
||||
fn is_zero(utf8: []const u8) bool {
|
||||
return coral.io.equals(utf8, "0") or coral.io.equals(utf8, "0.0");
|
||||
}
|
||||
}.is_zero;
|
||||
|
||||
const number_format = coral.utf8.DecimalFormat{
|
||||
.delimiter = "_",
|
||||
.positive_prefix = .none,
|
||||
};
|
||||
|
||||
switch (expression) {
|
||||
.nil_literal => try self.opcodes.push_one(.push_nil),
|
||||
.true_literal => try self.opcodes.push_one(.push_true),
|
||||
.false_literal => try self.opcodes.push_one(.push_false),
|
||||
|
||||
.number_literal => |literal| {
|
||||
const parsed_number = number_format.parse(literal, State.Float);
|
||||
|
||||
coral.debug.assert(parsed_number != null);
|
||||
|
||||
try self.opcodes.push_one(if (is_zero(literal)) .push_zero else .{.push_number = parsed_number.?});
|
||||
},
|
||||
|
||||
.string_literal => |literal| {
|
||||
try self.opcodes.push_one(.{
|
||||
.push_object = try self.state.acquire_interned(literal, &string_info),
|
||||
});
|
||||
},
|
||||
|
||||
.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(field.expression);
|
||||
|
||||
try self.opcodes.push_one(.{
|
||||
.push_object = try self.state.acquire_interned(field.identifier, &string_info),
|
||||
});
|
||||
}
|
||||
|
||||
try self.opcodes.push_one(.{.push_table = @intCast(fields.values.len)});
|
||||
},
|
||||
|
||||
.binary_operation => |operation| {
|
||||
try self.compile_expression(operation.lhs_expression.*);
|
||||
try self.compile_expression(operation.rhs_expression.*);
|
||||
|
||||
try self.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(operation.expression.*);
|
||||
|
||||
try self.opcodes.push_one(switch (operation.operator) {
|
||||
.boolean_negation => .not,
|
||||
.numeric_negation => .neg,
|
||||
});
|
||||
},
|
||||
|
||||
.grouped_expression => |grouped_expression| {
|
||||
try self.compile_expression(grouped_expression.*);
|
||||
},
|
||||
|
||||
.get_local => |local| {
|
||||
try self.opcodes.push_one(.{
|
||||
.get_local = self.locals.resolve(local) orelse return error.UndefinedLocal,
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn free(self: *Compiler) void {
|
||||
for (self.opcodes.values) |opcode| {
|
||||
if (opcode == .push_object) {
|
||||
self.state.release(opcode.push_object);
|
||||
}
|
||||
}
|
||||
|
||||
self.opcodes.free();
|
||||
}
|
||||
|
||||
fn list_opcodes(self: Compiler) []const Opcode {
|
||||
return self.opcodes.values;
|
||||
}
|
||||
|
||||
fn make(allocator: coral.io.Allocator, state: *State) Compiler {
|
||||
return .{
|
||||
.locals = .{},
|
||||
.opcodes = OpcodeList.make(allocator),
|
||||
.state = state,
|
||||
};
|
||||
}
|
||||
pub const DynamicObject = struct {
|
||||
userdata: []coral.io.Byte,
|
||||
typeinfo: *const Typeinfo,
|
||||
};
|
||||
|
||||
pub const Float = f64;
|
||||
|
||||
pub const IndexContext = struct {
|
||||
env: *RuntimeEnv,
|
||||
indexable: *const RuntimeRef,
|
||||
index: *const RuntimeRef,
|
||||
userdata: []u8,
|
||||
};
|
||||
|
||||
pub const ObjectInfo = struct {
|
||||
call: *const fn (context: CallContext) RuntimeError!*RuntimeRef = default_call,
|
||||
clean: *const fn (userdata: []u8) void = default_clean,
|
||||
get: *const fn (context: IndexContext) RuntimeError!*RuntimeRef = default_get,
|
||||
set: *const fn (context: IndexContext, value: *const RuntimeRef) RuntimeError!void = default_set,
|
||||
|
||||
fn cast(object_info: *const anyopaque) *const ObjectInfo {
|
||||
return @ptrCast(@alignCast(object_info));
|
||||
}
|
||||
|
||||
fn default_call(context: CallContext) RuntimeError!*RuntimeRef {
|
||||
return context.env.raise(error.BadOperation, "attempt to call non-callable");
|
||||
}
|
||||
|
||||
fn default_clean(_: []u8) void {
|
||||
// Nothing to clean up by default.
|
||||
}
|
||||
|
||||
fn default_get(context: IndexContext) RuntimeError!*RuntimeRef {
|
||||
return context.env.raise(error.BadOperation, "attempt to get non-indexable");
|
||||
}
|
||||
|
||||
fn default_set(context: IndexContext, _: *const RuntimeRef) RuntimeError!void {
|
||||
return context.env.raise(error.BadOperation, "attempt to set non-indexable");
|
||||
}
|
||||
};
|
||||
|
||||
pub const Opcode = union (enum) {
|
||||
push_nil,
|
||||
push_true,
|
||||
push_false,
|
||||
push_zero,
|
||||
push_number: State.Float,
|
||||
push_table: u32,
|
||||
push_object: *State.Object,
|
||||
|
||||
set_local: u8,
|
||||
get_local: u8,
|
||||
|
||||
not,
|
||||
neg,
|
||||
|
||||
add,
|
||||
sub,
|
||||
mul,
|
||||
div,
|
||||
|
||||
eql,
|
||||
cgt,
|
||||
clt,
|
||||
cge,
|
||||
cle,
|
||||
userdata: []coral.io.Byte,
|
||||
index_ref: ?*const RuntimeRef,
|
||||
};
|
||||
|
||||
pub const RuntimeEnv = struct {
|
||||
allocator: coral.io.Allocator,
|
||||
err_writer: coral.io.Writer,
|
||||
bound_refs: VariantSlab,
|
||||
state: State,
|
||||
local_refs: RefStack,
|
||||
frames: FrameStack,
|
||||
ref_values: RefSlab,
|
||||
|
||||
const FrameStack = coral.list.Stack(struct {
|
||||
locals_top: usize,
|
||||
});
|
||||
|
||||
const RefStack = coral.list.Stack(?*RuntimeRef);
|
||||
|
||||
pub const Options = struct {
|
||||
out_writer: coral.io.Writer = coral.io.null_writer,
|
||||
err_writer: coral.io.Writer = coral.io.null_writer,
|
||||
};
|
||||
|
||||
const RefSlab = coral.map.Slab(struct {
|
||||
ref_count: usize,
|
||||
|
||||
object: union (enum) {
|
||||
false,
|
||||
true,
|
||||
number: Float,
|
||||
string: []coral.io.Byte,
|
||||
dynamic: *DynamicObject,
|
||||
},
|
||||
});
|
||||
|
||||
pub const ScriptSource = struct {
|
||||
name: []const coral.io.Byte,
|
||||
data: []const coral.io.Byte,
|
||||
};
|
||||
|
||||
const VariantSlab = coral.map.Slab(State.Variant);
|
||||
pub fn acquire(self: *RuntimeEnv, ref: ?*const RuntimeRef) ?*RuntimeRef {
|
||||
const key = @intFromPtr(ref);
|
||||
var ref_data = self.ref_values.remove(key);
|
||||
|
||||
pub fn discard(self: *RuntimeEnv, ref: *RuntimeRef) void {
|
||||
coral.debug.assert(self.bound_refs.remove(@intFromPtr(ref)) != null);
|
||||
coral.debug.assert(ref_data != null);
|
||||
|
||||
ref_data.?.ref_count += 1;
|
||||
|
||||
coral.debug.assert(self.ref_values.insert_at(key, ref_data.?));
|
||||
|
||||
return @ptrFromInt(key);
|
||||
}
|
||||
|
||||
pub fn execute_chunk(self: *RuntimeEnv, name: []const coral.io.Byte, opcodes: []const Opcode) RuntimeError!*RuntimeRef {
|
||||
_ = name;
|
||||
pub fn call(
|
||||
self: *RuntimeEnv,
|
||||
caller_ref: *const RuntimeRef,
|
||||
callable_ref: *const RuntimeRef,
|
||||
arg_refs: []const *const RuntimeRef,
|
||||
) RuntimeError!*RuntimeRef {
|
||||
const callable = (try callable_ref.fetch(self)).to_object();
|
||||
|
||||
for (opcodes) |opcode| {
|
||||
switch (opcode) {
|
||||
.push_nil => try self.state.push_value(.nil),
|
||||
.push_true => try self.state.push_value(.true),
|
||||
.push_false => try self.state.push_value(.false),
|
||||
.push_zero => try self.state.push_value(.{.number = 0}),
|
||||
.push_number => |number| try self.state.push_value(.{.number = number}),
|
||||
|
||||
.push_table => |size| {
|
||||
var table = Table.make(self.allocator, &self.state);
|
||||
|
||||
errdefer table.free();
|
||||
|
||||
{
|
||||
var popped = @as(usize, 0);
|
||||
|
||||
while (popped < size) : (popped += 1) {
|
||||
try table.set_field(
|
||||
try to_object(self, try self.state.pop_value()),
|
||||
try self.state.pop_value());
|
||||
}
|
||||
}
|
||||
|
||||
const table_object = try self.state.acquire_new(coral.io.bytes_of(&table), &table_info);
|
||||
|
||||
errdefer self.state.release(table_object);
|
||||
|
||||
try self.state.push_value(.{.object = table_object});
|
||||
},
|
||||
|
||||
.push_object => |object| {
|
||||
const acquired_object = self.state.acquire_instance(object);
|
||||
|
||||
errdefer self.state.release(acquired_object);
|
||||
|
||||
try self.state.push_value(.{.object = acquired_object});
|
||||
},
|
||||
|
||||
.set_local => |local| {
|
||||
if (!self.state.set_value(local, try self.state.pop_value())) {
|
||||
return self.raise(error.BadOperation, "invalid local set");
|
||||
}
|
||||
},
|
||||
|
||||
.get_local => |local| {
|
||||
try self.state.push_value(self.state.get_value(local));
|
||||
},
|
||||
|
||||
.not => {
|
||||
try self.state.push_value(switch (try self.state.pop_value()) {
|
||||
.nil => return self.raise(error.BadOperation, "cannot convert nil to true or false"),
|
||||
.false => .true,
|
||||
.true => .false,
|
||||
.number => return self.raise(error.BadOperation, "cannot convert a number to true or false"),
|
||||
.object => return self.raise(error.BadOperation, "cannot convert an object to true or false"),
|
||||
return callable.userinfo.call(.{
|
||||
.env = self,
|
||||
.caller = caller_ref,
|
||||
.callable = callable_ref,
|
||||
.userdata = callable.userdata,
|
||||
.args = arg_refs,
|
||||
});
|
||||
},
|
||||
|
||||
.neg => {
|
||||
try self.state.push_value(.{.number = -(try to_number(self, try self.state.pop_value()))});
|
||||
},
|
||||
|
||||
.add => {
|
||||
const rhs_number = try to_number(self, try self.state.pop_value());
|
||||
const lhs_number = try to_number(self, try self.state.pop_value());
|
||||
|
||||
try self.state.push_value(.{.number = lhs_number + rhs_number});
|
||||
},
|
||||
|
||||
.sub => {
|
||||
const rhs_number = try to_number(self, try self.state.pop_value());
|
||||
const lhs_number = try to_number(self, try self.state.pop_value());
|
||||
|
||||
try self.state.push_value(.{.number = lhs_number - rhs_number});
|
||||
},
|
||||
|
||||
.mul => {
|
||||
const rhs_number = try to_number(self, try self.state.pop_value());
|
||||
const lhs_number = try to_number(self, try self.state.pop_value());
|
||||
|
||||
try self.state.push_value(.{.number = lhs_number * rhs_number});
|
||||
},
|
||||
|
||||
.div => {
|
||||
const rhs_number = try to_number(self, try self.state.pop_value());
|
||||
const lhs_number = try to_number(self, try self.state.pop_value());
|
||||
|
||||
try self.state.push_value(.{.number = lhs_number / rhs_number});
|
||||
},
|
||||
|
||||
.eql => {
|
||||
const rhs = try self.state.pop_value();
|
||||
const lhs = try self.state.pop_value();
|
||||
|
||||
try self.state.push_value(if (lhs.equals(rhs)) .true else .false);
|
||||
},
|
||||
|
||||
.cgt => {
|
||||
const rhs_number = try to_number(self, try self.state.pop_value());
|
||||
const lhs_number = try to_number(self, try self.state.pop_value());
|
||||
|
||||
try self.state.push_value(if (lhs_number > rhs_number) .true else .false);
|
||||
},
|
||||
|
||||
.clt => {
|
||||
const rhs_number = try to_number(self, try self.state.pop_value());
|
||||
const lhs_number = try to_number(self, try self.state.pop_value());
|
||||
|
||||
try self.state.push_value(if (lhs_number < rhs_number) .true else .false);
|
||||
},
|
||||
|
||||
.cge => {
|
||||
const rhs_number = try to_number(self, try self.state.pop_value());
|
||||
const lhs_number = try to_number(self, try self.state.pop_value());
|
||||
|
||||
try self.state.push_value(if (lhs_number >= rhs_number) .true else .false);
|
||||
},
|
||||
|
||||
.cle => {
|
||||
const rhs_number = try to_number(self, try self.state.pop_value());
|
||||
const lhs_number = try to_number(self, try self.state.pop_value());
|
||||
|
||||
try self.state.push_value(if (lhs_number <= rhs_number) .true else .false);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const return_value = try self.state.pop_value();
|
||||
pub fn discard(self: *RuntimeEnv, ref: *?*RuntimeRef) void {
|
||||
const key = @intFromPtr(ref.* orelse return);
|
||||
var ref_data = self.ref_values.remove(key) orelse unreachable;
|
||||
|
||||
errdefer if (return_value == .object) {
|
||||
self.state.release(return_value.object);
|
||||
};
|
||||
coral.debug.assert(ref_data.ref_count != 0);
|
||||
|
||||
return @ptrFromInt(try self.bound_refs.insert(return_value));
|
||||
ref_data.ref_count -= 1;
|
||||
|
||||
if (ref_data.ref_count == 0) {
|
||||
switch (ref_data.object) {
|
||||
.string => |string| self.allocator.deallocate(string),
|
||||
.dynamic => |virtual| self.allocator.deallocate(virtual),
|
||||
else => {},
|
||||
}
|
||||
} else {
|
||||
coral.debug.assert(self.ref_values.insert_at(key, ref_data));
|
||||
}
|
||||
|
||||
pub fn execute_file(self: *RuntimeEnv, file_access: file.Access, file_path: file.Path) RuntimeError!*RuntimeRef {
|
||||
ref.* = null;
|
||||
}
|
||||
|
||||
pub fn execute_file(self: *RuntimeEnv, global_ref: ?*const RuntimeRef, file_access: file.Access, file_path: file.Path) RuntimeError!?*RuntimeRef {
|
||||
const error_message = "failed to load file";
|
||||
|
||||
const file_data = (try file.allocate_and_load(self.allocator, file_access, file_path)) orelse {
|
||||
|
@ -436,13 +133,13 @@ pub const RuntimeEnv = struct {
|
|||
|
||||
defer self.allocator.deallocate(file_data);
|
||||
|
||||
return self.execute_script(.{
|
||||
return self.execute_script(global_ref, .{
|
||||
.name = file_path.to_string() orelse return self.raise(error.SystemFailure, error_message),
|
||||
.data = file_data,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn execute_script(self: *RuntimeEnv, source: ScriptSource) RuntimeError!*RuntimeRef {
|
||||
pub fn execute_script(self: *RuntimeEnv, global_ref: ?*const RuntimeRef, source: ScriptSource) RuntimeError!?*RuntimeRef {
|
||||
var ast = Ast.make(self.allocator);
|
||||
|
||||
defer ast.free();
|
||||
|
@ -456,198 +153,271 @@ pub const RuntimeEnv = struct {
|
|||
};
|
||||
}
|
||||
|
||||
var compiler = Compiler.make(self.allocator, &self.state);
|
||||
var exe = Chunk.make(self);
|
||||
|
||||
defer compiler.free();
|
||||
defer exe.free();
|
||||
|
||||
compiler.compile_ast(ast) catch |compile_error| return switch (compile_error) {
|
||||
error.OutOfMemory => error.OutOfMemory,
|
||||
error.UndefinedLocal => self.raise(error.BadOperation, "use of undefined local"),
|
||||
error.TooManyLocals => self.raise(error.OutOfMemory, "functions cannot contain more than 255 locals"),
|
||||
};
|
||||
try exe.compile_ast(ast);
|
||||
|
||||
return self.execute_chunk(source.name, compiler.list_opcodes());
|
||||
return exe.execute(global_ref, source.name);
|
||||
}
|
||||
|
||||
pub fn free(self: *RuntimeEnv) void {
|
||||
self.bound_refs.free();
|
||||
self.state.free();
|
||||
pub fn frame_pop(self: *RuntimeEnv) RuntimeError!void {
|
||||
const frame = self.frames.pop() orelse return self.raise(error.IllegalState, "stack underflow");
|
||||
|
||||
coral.debug.assert(self.local_refs.drop(self.local_refs.values.len - frame.locals_top));
|
||||
}
|
||||
|
||||
pub fn get_field(self: *RuntimeEnv, indexable: *const RuntimeRef, field: []const u8) RuntimeError!*RuntimeRef {
|
||||
const interned_field = try self.intern(field);
|
||||
|
||||
defer self.discard(interned_field);
|
||||
|
||||
const indexable_object = try to_object(self, try indexable.fetch(self));
|
||||
|
||||
return ObjectInfo.cast(indexable_object.userinfo).get(.{
|
||||
.env = self,
|
||||
.indexable = indexable,
|
||||
.index = interned_field,
|
||||
.userdata = indexable_object.userdata,
|
||||
pub fn frame_push(self: *RuntimeEnv) RuntimeError!void {
|
||||
return self.frames.push_one(.{
|
||||
.locals_top = self.local_refs.values.len,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn get_float(self: *RuntimeEnv, ref: *const RuntimeRef) RuntimeError!State.Float {
|
||||
return to_number(self, try ref.fetch(self));
|
||||
pub fn free(self: *RuntimeEnv) void {
|
||||
self.local_refs.free();
|
||||
self.ref_values.free();
|
||||
}
|
||||
|
||||
pub fn get_string(self: *RuntimeEnv, ref: *const RuntimeRef) RuntimeError![]const u8 {
|
||||
const object = try to_object(self, try ref.fetch(self));
|
||||
pub fn get_dynamic(self: *RuntimeEnv, indexable_ref: ?*const RuntimeRef, index_ref: ?*const RuntimeRef) RuntimeError!?*RuntimeRef {
|
||||
return switch (self.unbox(indexable_ref)) {
|
||||
.nil => self.raise(error.BadOperation, "nil is immutable"),
|
||||
.boolean => self.raise(error.BadOperation, "boolean has no such index"),
|
||||
.number => self.raise(error.BadOperation, "number has no such index"),
|
||||
.string => self.raise(error.BadOperation, "string has no such index"),
|
||||
|
||||
if (ObjectInfo.cast(object.userinfo) != &string_info) {
|
||||
return self.raise(error.BadOperation, "object is not a string");
|
||||
.dynamic => |dynamic| dynamic.typeinfo.get(.{
|
||||
.userdata = dynamic.userdata,
|
||||
.env = self,
|
||||
.index_ref = index_ref,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
return object.userdata;
|
||||
pub fn local_get(self: *RuntimeEnv, local: u8) RuntimeError!?*RuntimeRef {
|
||||
return self.local_refs.values[local];
|
||||
}
|
||||
|
||||
pub fn intern(self: *RuntimeEnv, data: []const u8) RuntimeError!*RuntimeRef {
|
||||
const data_object = try self.state.acquire_interned(data, &string_info);
|
||||
pub fn local_pop(self: *RuntimeEnv) RuntimeError!?*RuntimeRef {
|
||||
return self.local_refs.pop() orelse self.raise(error.IllegalState, "stack underflow");
|
||||
}
|
||||
|
||||
errdefer self.state.release(data_object);
|
||||
pub fn local_push_ref(self: *RuntimeEnv, ref: ?*RuntimeRef) RuntimeError!void {
|
||||
return self.local_refs.push_one(self.acquire(ref));
|
||||
}
|
||||
|
||||
return @ptrFromInt(try self.bound_refs.insert(.{.object = data_object}));
|
||||
pub fn local_push_boolean(self: *RuntimeEnv, boolean: bool) RuntimeError!void {
|
||||
return self.local_refs.push_one(try self.new_boolean(boolean));
|
||||
}
|
||||
|
||||
pub fn local_push_number(self: *RuntimeEnv, number: Float) RuntimeError!void {
|
||||
return self.local_refs.push_one(try self.new_number(number));
|
||||
}
|
||||
|
||||
pub fn local_set(self: *RuntimeEnv, local: u8, value: ?*RuntimeRef) RuntimeError!void {
|
||||
self.local_refs.values[local] = self.acquire(value);
|
||||
}
|
||||
|
||||
pub fn make(allocator: coral.io.Allocator, options: Options) RuntimeError!RuntimeEnv {
|
||||
var env = RuntimeEnv{
|
||||
.allocator = allocator,
|
||||
.bound_refs = VariantSlab.make(allocator),
|
||||
.state = State.make(allocator),
|
||||
return .{
|
||||
.local_refs = RefStack.make(allocator),
|
||||
.ref_values = RefSlab.make(allocator),
|
||||
.frames = FrameStack.make(allocator),
|
||||
.err_writer = options.err_writer,
|
||||
.allocator = allocator,
|
||||
};
|
||||
|
||||
return env;
|
||||
}
|
||||
|
||||
pub fn new_object(self: *RuntimeEnv, userdata: []const u8, info: *const ObjectInfo) RuntimeError!*RuntimeRef {
|
||||
const data_object = try self.state.acquire_new(userdata, info);
|
||||
|
||||
defer self.state.release(data_object);
|
||||
|
||||
return @ptrFromInt(try self.bound_refs.insert(.{.object = data_object}));
|
||||
pub fn new_boolean(self: *RuntimeEnv, value: bool) RuntimeError!?*RuntimeRef {
|
||||
return @ptrFromInt(try self.ref_values.insert(.{
|
||||
.ref_count = 1,
|
||||
.object = if (value) .true else .false,
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn raise(self: *RuntimeEnv, runtime_error: RuntimeError, error_message: []const u8) RuntimeError {
|
||||
pub fn new_dynamic(self: *RuntimeEnv, userdata: []const coral.io.Byte, typeinfo: *const Typeinfo) RuntimeError!*RuntimeRef {
|
||||
const userdata_copy = try coral.io.allocate_copy(self.allocator, userdata);
|
||||
|
||||
errdefer self.allocator.deallocate(userdata_copy);
|
||||
|
||||
const dynamic_object = try coral.io.allocate_one(self.allocator, DynamicObject{
|
||||
.typeinfo = typeinfo,
|
||||
.userdata = userdata_copy,
|
||||
});
|
||||
|
||||
errdefer self.allocator.deallocate(dynamic_object);
|
||||
|
||||
return @ptrFromInt(try self.ref_values.insert(.{
|
||||
.ref_count = 1,
|
||||
.object = .{.dynamic = dynamic_object},
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn new_number(self: *RuntimeEnv, value: Float) RuntimeError!?*RuntimeRef {
|
||||
return @ptrFromInt(try self.ref_values.insert(.{
|
||||
.ref_count = 1,
|
||||
.object = .{.number = value},
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn new_string(self: *RuntimeEnv, string_data: []const coral.io.Byte) RuntimeError!?*RuntimeRef {
|
||||
const string_copy = try coral.io.allocate_copy(self.allocator, string_data);
|
||||
|
||||
errdefer self.allocator.deallocate(string_copy);
|
||||
|
||||
return @ptrFromInt(try self.ref_values.insert(.{
|
||||
.ref_count = 1,
|
||||
.object = .{.string = string_copy},
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn raise(self: *RuntimeEnv, error_value: RuntimeError, error_message: []const u8) RuntimeError {
|
||||
// TODO: Print stack trace from state.
|
||||
coral.utf8.print_formatted(self.err_writer, "{name}@{line}: {message}", .{
|
||||
.name = "???",
|
||||
.line = @as(u64, 0),
|
||||
.message = error_message,
|
||||
}) catch return error.SystemFailure;
|
||||
}) catch {};
|
||||
|
||||
return runtime_error;
|
||||
return error_value;
|
||||
}
|
||||
|
||||
pub fn set_dynamic(self: *RuntimeEnv, indexable_ref: ?*const RuntimeRef, index_ref: ?*const RuntimeRef, value_ref: ?*const RuntimeRef) RuntimeError!void {
|
||||
return switch (self.unbox(indexable_ref)) {
|
||||
.nil => self.raise(error.BadOperation, "nil is immutable"),
|
||||
.boolean => self.raise(error.BadOperation, "boolean is immutable"),
|
||||
.number => self.raise(error.BadOperation, "number is immutable"),
|
||||
.string => self.raise(error.BadOperation, "string is immutable"),
|
||||
|
||||
.dynamic => |dynamic| dynamic.typeinfo.set(.{
|
||||
.userdata = dynamic.userdata,
|
||||
.env = self,
|
||||
.index_ref = index_ref,
|
||||
}, value_ref),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn unbox(self: *RuntimeEnv, ref: ?*const RuntimeRef) Unboxed {
|
||||
const ref_data = self.ref_values.lookup(@intFromPtr(ref orelse return .nil));
|
||||
|
||||
coral.debug.assert(ref_data != null);
|
||||
|
||||
return switch (ref_data.?.object) {
|
||||
.false => .{.boolean = false},
|
||||
.true => .{.boolean = true},
|
||||
.number => |number| .{.number = number},
|
||||
.string => |string| .{.string = string},
|
||||
.dynamic => |dynamic| .{.dynamic = dynamic},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const RuntimeError = coral.io.AllocationError || State.PopError || error {
|
||||
BadSyntax,
|
||||
BadOperation,
|
||||
pub const RuntimeError = coral.io.AllocationError || error {
|
||||
IllegalState,
|
||||
SystemFailure,
|
||||
TypeMismatch,
|
||||
BadOperation,
|
||||
BadSyntax,
|
||||
};
|
||||
|
||||
pub const RuntimeRef = opaque {
|
||||
fn fetch(self: *const RuntimeRef, env: *RuntimeEnv) RuntimeError!State.Variant {
|
||||
return env.bound_refs.lookup(@intFromPtr(self)) orelse env.raise(error.BadOperation, "stale ref");
|
||||
pub const RuntimeRef = opaque {};
|
||||
|
||||
pub const TestContext = struct {
|
||||
env: *RuntimeEnv,
|
||||
userdata: []const coral.io.Byte,
|
||||
other_ref: ?*const RuntimeRef,
|
||||
};
|
||||
|
||||
pub const Typeinfo = struct {
|
||||
call: *const fn (context: CallContext) RuntimeError!?*RuntimeRef,
|
||||
clean: *const fn (userdata: []u8) void,
|
||||
get: *const fn (context: IndexContext) RuntimeError!?*RuntimeRef,
|
||||
set: *const fn (context: IndexContext, value: ?*const RuntimeRef) RuntimeError!void,
|
||||
test_difference: *const fn (context: TestContext) RuntimeError!Float = default_test_difference,
|
||||
test_equality: *const fn (context: TestContext) RuntimeError!bool = default_test_equality,
|
||||
|
||||
fn default_test_difference(context: TestContext) RuntimeError!Float {
|
||||
return context.env.raise(error.TypeMismatch, "object is not comparable");
|
||||
}
|
||||
|
||||
fn default_test_equality(context: TestContext) RuntimeError!bool {
|
||||
return switch (context.env.unbox(context.other_ref)) {
|
||||
.dynamic => |dynamic| context.userdata.ptr == dynamic.userdata.ptr,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
fn table_clean(userdata: []u8) void {
|
||||
@as(*Table, @ptrCast(@alignCast(userdata.ptr))).free();
|
||||
}
|
||||
|
||||
fn table_get(context: IndexContext) RuntimeError!*RuntimeRef {
|
||||
const table = @as(*Table, @ptrCast(@alignCast(context.userdata.ptr)));
|
||||
|
||||
switch (try context.index.fetch(context.env)) {
|
||||
.nil => return context.env.raise(error.BadOperation, "cannot index a table with nil"),
|
||||
.true => return context.env.raise(error.BadOperation, "cannot index a table with true"),
|
||||
.false => return context.env.raise(error.BadOperation, "cannot index a table with false"),
|
||||
|
||||
.object => |index_object| {
|
||||
const value = table.get_field(index_object);
|
||||
|
||||
errdefer if (value == .object) {
|
||||
context.env.state.release(value.object);
|
||||
};
|
||||
|
||||
return @ptrFromInt(try context.env.bound_refs.insert(value));
|
||||
},
|
||||
|
||||
.number => |index_number| {
|
||||
const value = table.get_index(@intFromFloat(index_number));
|
||||
|
||||
errdefer if (value == .object) {
|
||||
context.env.state.release(value.object);
|
||||
};
|
||||
|
||||
return @ptrFromInt(try context.env.bound_refs.insert(value));
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const table_info = ObjectInfo{
|
||||
.clean = table_clean,
|
||||
.get = table_get,
|
||||
.set = table_set,
|
||||
pub const Unboxed = union (enum) {
|
||||
nil,
|
||||
boolean: bool,
|
||||
number: Float,
|
||||
string: []const u8,
|
||||
dynamic: *const DynamicObject,
|
||||
};
|
||||
|
||||
fn table_set(context: IndexContext, value: *const RuntimeRef) RuntimeError!void {
|
||||
const table = @as(*Table, @ptrCast(@alignCast(context.userdata.ptr)));
|
||||
pub fn get_dynamic_field(env: *RuntimeEnv, indexable: ?*const RuntimeRef, field: []const u8) RuntimeError!?*RuntimeRef {
|
||||
var interned_field = try env.new_string(field);
|
||||
|
||||
switch (try context.index.fetch(context.env)) {
|
||||
.nil => return context.env.raise(error.BadOperation, "cannot index a table with nil"),
|
||||
.true => return context.env.raise(error.BadOperation, "cannot index a table with true"),
|
||||
.false => return context.env.raise(error.BadOperation, "cannot index a table with false"),
|
||||
defer env.discard(&interned_field);
|
||||
|
||||
.object => |index_object| {
|
||||
const fetched_value = try value.fetch(context.env);
|
||||
|
||||
if (fetched_value == .object) {
|
||||
try table.set_field(index_object, .{
|
||||
.object = context.env.state.acquire_instance(fetched_value.object),
|
||||
});
|
||||
} else {
|
||||
try table.set_field(index_object, fetched_value);
|
||||
}
|
||||
},
|
||||
|
||||
.number => |index_number| {
|
||||
const fetched_value = try value.fetch(context.env);
|
||||
|
||||
if (fetched_value == .object) {
|
||||
try table.set_index(@intFromFloat(index_number), .{
|
||||
.object = context.env.state.acquire_instance(fetched_value.object),
|
||||
});
|
||||
} else {
|
||||
try table.set_index(@intFromFloat(index_number), fetched_value);
|
||||
}
|
||||
},
|
||||
}
|
||||
return env.get_dynamic(indexable, interned_field);
|
||||
}
|
||||
|
||||
fn to_number(env: *RuntimeEnv, variant: State.Variant) RuntimeError!State.Float {
|
||||
return switch (variant) {
|
||||
.nil => env.raise(error.BadOperation, "cannot convert nil to number"),
|
||||
.true => env.raise(error.BadOperation, "cannot convert true to number"),
|
||||
.false => env.raise(error.BadOperation, "cannot convert false to number"),
|
||||
.number => |number| number,
|
||||
.object => env.raise(error.BadOperation, "cannot convert object to number"),
|
||||
pub fn new_table(env: *RuntimeEnv) RuntimeError!?*RuntimeRef {
|
||||
var table = Table.make(env);
|
||||
|
||||
errdefer table.free();
|
||||
|
||||
return try env.new_dynamic(coral.io.bytes_of(&table), &Table.typeinfo);
|
||||
}
|
||||
|
||||
pub fn test_difference(env: *RuntimeEnv, lhs_ref: ?*const RuntimeRef, rhs_ref: ?*const RuntimeRef) RuntimeError!Float {
|
||||
return switch (env.unbox(lhs_ref)) {
|
||||
.nil => env.raise(error.TypeMismatch, "cannot compare nil objects"),
|
||||
.boolean => env.raise(error.TypeMismatch, "cannot compare boolean objects"),
|
||||
|
||||
.number => |lhs_number| switch (env.unbox(rhs_ref)) {
|
||||
.number => |rhs_number| rhs_number - lhs_number,
|
||||
else => env.raise(error.TypeMismatch, "right-hand object is not comparable with number objects"),
|
||||
},
|
||||
|
||||
.string => |lhs_string| switch (env.unbox(rhs_ref)) {
|
||||
.string => |rhs_string| @floatFromInt(coral.io.compare(lhs_string, rhs_string)),
|
||||
else => env.raise(error.TypeMismatch, "right-hand object is not comparable with string objects"),
|
||||
},
|
||||
|
||||
.dynamic => |lhs_dynamic| lhs_dynamic.typeinfo.test_difference(.{
|
||||
.env = env,
|
||||
.userdata = lhs_dynamic.userdata,
|
||||
.other_ref = rhs_ref,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
fn to_object(env: *RuntimeEnv, variant: State.Variant) RuntimeError!*State.Object {
|
||||
return switch (variant) {
|
||||
.nil => env.raise(error.BadOperation, "cannot convert nil to object"),
|
||||
.true => env.raise(error.BadOperation, "cannot convert true to object"),
|
||||
.false => env.raise(error.BadOperation, "cannot convert false to object"),
|
||||
.number => env.raise(error.BadOperation, "cannot convert number to object"),
|
||||
.object => |object| object,
|
||||
pub fn test_equality(env: *RuntimeEnv, lhs_ref: ?*const RuntimeRef, rhs_ref: ?*const RuntimeRef) RuntimeError!bool {
|
||||
return switch (env.unbox(lhs_ref)) {
|
||||
.nil => (rhs_ref == null),
|
||||
|
||||
.boolean => |lhs_boolean| switch (env.unbox(rhs_ref)) {
|
||||
.boolean => |rhs_boolean| rhs_boolean == lhs_boolean,
|
||||
else => false,
|
||||
},
|
||||
|
||||
.number => |lhs_number| switch (env.unbox(rhs_ref)) {
|
||||
.number => |rhs_number| lhs_number == rhs_number,
|
||||
else => false,
|
||||
},
|
||||
|
||||
.string => |lhs_string| switch (env.unbox(rhs_ref)) {
|
||||
.string => |rhs_string| coral.io.equals(lhs_string, rhs_string),
|
||||
else => false,
|
||||
},
|
||||
|
||||
.dynamic => |lhs_dynamic| try lhs_dynamic.typeinfo.test_equality(.{
|
||||
.env = env,
|
||||
.userdata = lhs_dynamic.userdata,
|
||||
.other_ref = rhs_ref,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
const string_info = ObjectInfo{
|
||||
|
||||
};
|
||||
|
|
|
@ -5,17 +5,22 @@ const tokens = @import("./tokens.zig");
|
|||
allocator: coral.io.Allocator,
|
||||
arena: coral.arena.Stacking,
|
||||
statements: Statement.List,
|
||||
error_message: []const u8,
|
||||
error_message: []const coral.io.Byte,
|
||||
|
||||
pub const Expression = union (enum) {
|
||||
nil_literal,
|
||||
true_literal,
|
||||
false_literal,
|
||||
number_literal: []const u8,
|
||||
string_literal: []const u8,
|
||||
number_literal: []const coral.io.Byte,
|
||||
string_literal: []const coral.io.Byte,
|
||||
table_literal: NamedList,
|
||||
grouped_expression: *Expression,
|
||||
get_local: []const u8,
|
||||
get_local: []const coral.io.Byte,
|
||||
|
||||
call_global: struct {
|
||||
identifier: []const coral.io.Byte,
|
||||
argument_expressions: List,
|
||||
},
|
||||
|
||||
binary_operation: struct {
|
||||
operator: BinaryOperator,
|
||||
|
@ -55,7 +60,7 @@ pub const Expression = union (enum) {
|
|||
};
|
||||
|
||||
pub const NamedList = coral.list.Stack(struct {
|
||||
identifier: []const u8,
|
||||
identifier: []const coral.io.Byte,
|
||||
expression: Expression,
|
||||
});
|
||||
|
||||
|
@ -80,6 +85,11 @@ pub const Statement = union (enum) {
|
|||
expression: Expression,
|
||||
},
|
||||
|
||||
call_global: struct {
|
||||
identifier: []const coral.io.Byte,
|
||||
argument_expressions: Expression.List,
|
||||
},
|
||||
|
||||
const List = coral.list.Stack(Statement);
|
||||
};
|
||||
|
||||
|
@ -88,13 +98,14 @@ const UnaryOperator = enum {
|
|||
numeric_negation,
|
||||
};
|
||||
|
||||
fn binary_operation_parser(comptime parse_next: ExpressionParser, comptime operators: []const Expression.BinaryOperator) ExpressionParser {
|
||||
return struct {
|
||||
fn parse(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression {
|
||||
var expression = try parse_next(self, tokenizer);
|
||||
fn binary_operation_parser(
|
||||
comptime parse_next: ExpressionParser,
|
||||
comptime operators: []const Expression.BinaryOperator) ExpressionParser {
|
||||
|
||||
{
|
||||
const BinaryOperationParser = struct {
|
||||
fn parse(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression {
|
||||
const allocator = self.arena.as_allocator();
|
||||
var expression = try parse_next(self, tokenizer);
|
||||
|
||||
inline for (operators) |operator| {
|
||||
const token = comptime operator.token();
|
||||
|
@ -103,8 +114,7 @@ fn binary_operation_parser(comptime parse_next: ExpressionParser, comptime opera
|
|||
tokenizer.step();
|
||||
|
||||
if (tokenizer.token == null) {
|
||||
return self.raise(
|
||||
"expected right-hand side of expression after `" ++ comptime token.text() ++ "`");
|
||||
return self.raise("expected other half of expression after `" ++ comptime token.text() ++ "`");
|
||||
}
|
||||
|
||||
expression = .{
|
||||
|
@ -116,11 +126,12 @@ fn binary_operation_parser(comptime parse_next: ExpressionParser, comptime opera
|
|||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return expression;
|
||||
}
|
||||
}.parse;
|
||||
};
|
||||
|
||||
return BinaryOperationParser.parse;
|
||||
}
|
||||
|
||||
pub fn free(self: *Self) void {
|
||||
|
@ -150,9 +161,12 @@ pub fn list_statements(self: Self) []const Statement {
|
|||
pub fn parse(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!void {
|
||||
self.free();
|
||||
|
||||
const allocator = self.arena.as_allocator();
|
||||
var has_returned = false;
|
||||
|
||||
while (true) {
|
||||
const no_effect_message = "statement has no effect";
|
||||
|
||||
tokenizer.skip(.newline);
|
||||
|
||||
switch (tokenizer.token orelse return) {
|
||||
|
@ -181,8 +195,6 @@ pub fn parse(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!void {
|
|||
.local => |identifier| {
|
||||
tokenizer.step();
|
||||
|
||||
const no_effect_message = "statement has no effect";
|
||||
|
||||
switch (tokenizer.token orelse return self.raise(no_effect_message)) {
|
||||
.newline => return self.raise(no_effect_message),
|
||||
|
||||
|
@ -209,6 +221,45 @@ pub fn parse(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!void {
|
|||
}
|
||||
},
|
||||
|
||||
.global => |identifier| {
|
||||
tokenizer.step();
|
||||
|
||||
switch (tokenizer.token orelse return self.raise(no_effect_message)) {
|
||||
.newline => return self.raise(no_effect_message),
|
||||
|
||||
.symbol_paren_left => {
|
||||
tokenizer.step();
|
||||
|
||||
var expressions_list = Expression.List.make(allocator);
|
||||
|
||||
while (true) {
|
||||
if (tokenizer.is_token(.symbol_paren_right)) {
|
||||
break;
|
||||
}
|
||||
|
||||
try expressions_list.push_one(try self.parse_expression(tokenizer));
|
||||
|
||||
switch (tokenizer.token orelse return self.raise("unexpected end after after `(`")) {
|
||||
.symbol_comma => continue,
|
||||
.symbol_paren_right => break,
|
||||
else => return self.raise("expected `)` or argument after `(`"),
|
||||
}
|
||||
}
|
||||
|
||||
tokenizer.step();
|
||||
|
||||
try self.statements.push_one(.{
|
||||
.call_global = .{
|
||||
.argument_expressions = expressions_list,
|
||||
.identifier = identifier,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
else => return self.raise("expected `=` after local"),
|
||||
}
|
||||
},
|
||||
|
||||
else => return self.raise("invalid statement"),
|
||||
}
|
||||
}
|
||||
|
@ -252,6 +303,24 @@ fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression
|
|||
return Expression{.grouped_expression = try coral.io.allocate_one(allocator, expression)};
|
||||
},
|
||||
|
||||
.keyword_nil => {
|
||||
tokenizer.step();
|
||||
|
||||
return .nil_literal;
|
||||
},
|
||||
|
||||
.keyword_true => {
|
||||
tokenizer.step();
|
||||
|
||||
return .true_literal;
|
||||
},
|
||||
|
||||
.keyword_false => {
|
||||
tokenizer.step();
|
||||
|
||||
return .false_literal;
|
||||
},
|
||||
|
||||
.number => |value| {
|
||||
tokenizer.step();
|
||||
|
||||
|
@ -264,6 +333,48 @@ fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression
|
|||
return Expression{.string_literal = value};
|
||||
},
|
||||
|
||||
.global => |identifier| {
|
||||
tokenizer.skip(.newline);
|
||||
|
||||
var expression_list = Expression.List.make(allocator);
|
||||
|
||||
while (true) {
|
||||
switch (tokenizer.token orelse return self.raise("expected expression or `)` after `(`")) {
|
||||
.symbol_paren_right => {
|
||||
tokenizer.step();
|
||||
|
||||
return Expression{
|
||||
.call_global = .{
|
||||
.identifier = identifier,
|
||||
.argument_expressions = expression_list,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
else => {
|
||||
try expression_list.push_one(try self.parse_expression(tokenizer));
|
||||
|
||||
switch (tokenizer.token orelse return self.raise("expected `,` or `)` after argument")) {
|
||||
.symbol_comma => continue,
|
||||
|
||||
.symbol_paren_right => {
|
||||
tokenizer.step();
|
||||
|
||||
return Expression{
|
||||
.call_global = .{
|
||||
.identifier = identifier,
|
||||
.argument_expressions = expression_list,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
else => return self.raise("expected `,` or `)` after argument"),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
.local => |identifier| {
|
||||
tokenizer.step();
|
||||
|
||||
|
|
|
@ -0,0 +1,504 @@
|
|||
const Ast = @import("./Ast.zig");
|
||||
|
||||
const coral = @import("coral");
|
||||
|
||||
const kym = @import("../kym.zig");
|
||||
|
||||
env: *kym.RuntimeEnv,
|
||||
constant_refs: RefList,
|
||||
opcodes: OpcodeList,
|
||||
|
||||
const AstCompiler = struct {
|
||||
chunk: *Self,
|
||||
local_identifiers_buffer: [255][]const coral.io.Byte = [_][]const coral.io.Byte{""} ** 255,
|
||||
local_identifiers_count: u8 = 0,
|
||||
|
||||
fn compile_expression(self: *AstCompiler, expression: Ast.Expression) kym.RuntimeError!void {
|
||||
const number_format = coral.utf8.DecimalFormat{
|
||||
.delimiter = "_",
|
||||
.positive_prefix = .none,
|
||||
};
|
||||
|
||||
switch (expression) {
|
||||
.nil_literal => try self.chunk.append_opcode(.push_nil),
|
||||
.true_literal => try self.chunk.append_opcode(.push_true),
|
||||
.false_literal => try self.chunk.append_opcode(.push_false),
|
||||
|
||||
.number_literal => |literal| {
|
||||
const parsed_number = number_format.parse(literal, kym.Float);
|
||||
|
||||
coral.debug.assert(parsed_number != null);
|
||||
|
||||
try self.chunk.append_opcode(.{
|
||||
.push_const = try self.chunk.declare_constant_number(parsed_number.?),
|
||||
});
|
||||
},
|
||||
|
||||
.string_literal => |literal| {
|
||||
try self.chunk.append_opcode(.{.push_const = try self.chunk.declare_constant_string(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(field.expression);
|
||||
|
||||
try self.chunk.append_opcode(.{
|
||||
.push_const = try self.chunk.declare_constant_string(field.identifier),
|
||||
});
|
||||
}
|
||||
|
||||
try self.chunk.append_opcode(.{
|
||||
.push_table = .{.field_count = @intCast(fields.values.len)}});
|
||||
},
|
||||
|
||||
.binary_operation => |operation| {
|
||||
try self.compile_expression(operation.lhs_expression.*);
|
||||
try self.compile_expression(operation.rhs_expression.*);
|
||||
|
||||
try self.chunk.append_opcode(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(operation.expression.*);
|
||||
|
||||
try self.chunk.append_opcode(switch (operation.operator) {
|
||||
.boolean_negation => .not,
|
||||
.numeric_negation => .neg,
|
||||
});
|
||||
},
|
||||
|
||||
.grouped_expression => |grouped_expression| {
|
||||
try self.compile_expression(grouped_expression.*);
|
||||
},
|
||||
|
||||
.get_local => |local| {
|
||||
try self.chunk.append_opcode(.{
|
||||
.local_push_ref = self.resolve_local(local) orelse return self.chunk.env.raise(error.OutOfMemory, "undefined local"),
|
||||
});
|
||||
},
|
||||
|
||||
.call_global => |call| {
|
||||
if (call.argument_expressions.values.len > coral.math.max_int(@typeInfo(u8).Int)) {
|
||||
return self.chunk.env.raise(error.OutOfMemory, "functions may receive a maximum of 255 locals");
|
||||
}
|
||||
|
||||
for (call.argument_expressions.values) |argument_expression| {
|
||||
try self.compile_expression(argument_expression);
|
||||
}
|
||||
|
||||
try self.chunk.append_opcode(.{.push_const = try self.chunk.declare_constant_string(call.identifier)});
|
||||
|
||||
try self.chunk.append_opcode(.{
|
||||
.global_call = .{
|
||||
.arg_count = @intCast(call.argument_expressions.values.len),
|
||||
},
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn compile_statement(self: *AstCompiler, statement: Ast.Statement) kym.RuntimeError!void {
|
||||
switch (statement) {
|
||||
.return_expression => |return_expression| try self.compile_expression(return_expression),
|
||||
.return_nothing => try self.chunk.append_opcode(.push_nil),
|
||||
|
||||
.set_local => |local| {
|
||||
try self.compile_expression(local.expression);
|
||||
|
||||
if (self.resolve_local(local.identifier)) |index| {
|
||||
try self.chunk.append_opcode(.{.set_local = index});
|
||||
} else {
|
||||
try self.declare_local(local.identifier);
|
||||
}
|
||||
},
|
||||
|
||||
.call_global => |call| {
|
||||
if (call.argument_expressions.values.len > coral.math.max_int(@typeInfo(u8).Int)) {
|
||||
return self.chunk.env.raise(error.OutOfMemory, "functions may receive a maximum of 255 locals");
|
||||
}
|
||||
|
||||
for (call.argument_expressions.values) |argument_expression| {
|
||||
try self.compile_expression(argument_expression);
|
||||
}
|
||||
|
||||
try self.chunk.append_opcode(.{.push_const = try self.chunk.declare_constant_string(call.identifier)});
|
||||
|
||||
try self.chunk.append_opcode(.{
|
||||
.global_call = .{
|
||||
.arg_count = @intCast(call.argument_expressions.values.len),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn declare_local(self: *AstCompiler, identifier: []const u8) kym.RuntimeError!void {
|
||||
if (self.local_identifiers_count == self.local_identifiers_buffer.len) {
|
||||
return self.chunk.env.raise(error.OutOfMemory, "functions may contain a maximum of 255 locals");
|
||||
}
|
||||
|
||||
self.local_identifiers_buffer[self.local_identifiers_count] = identifier;
|
||||
self.local_identifiers_count += 1;
|
||||
}
|
||||
|
||||
fn resolve_local(self: *AstCompiler, local_identifier: []const coral.io.Byte) ?u8 {
|
||||
if (self.local_identifiers_count == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var index = @as(u8, self.local_identifiers_count - 1);
|
||||
|
||||
while (true) : (index -= 1) {
|
||||
if (coral.io.equals(local_identifier, self.local_identifiers_buffer[index])) {
|
||||
return index;
|
||||
}
|
||||
|
||||
if (index == 0) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub const Constant = u16;
|
||||
|
||||
const RefList = coral.list.Stack(?*kym.RuntimeRef);
|
||||
|
||||
const LocalsList = coral.list.Stack([]const u8);
|
||||
|
||||
pub const Opcode = union (enum) {
|
||||
push_nil,
|
||||
push_true,
|
||||
push_false,
|
||||
push_const: Constant,
|
||||
local_push_ref: u8,
|
||||
set_local: u8,
|
||||
get_global,
|
||||
|
||||
push_table: struct {
|
||||
field_count: u32,
|
||||
},
|
||||
|
||||
global_call: struct {
|
||||
arg_count: u8,
|
||||
},
|
||||
|
||||
not,
|
||||
neg,
|
||||
|
||||
add,
|
||||
sub,
|
||||
mul,
|
||||
div,
|
||||
|
||||
eql,
|
||||
cgt,
|
||||
clt,
|
||||
cge,
|
||||
cle,
|
||||
};
|
||||
|
||||
const OpcodeList = coral.list.Stack(Opcode);
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn append_opcode(self: *Self, opcode: Opcode) kym.RuntimeError!void {
|
||||
return self.opcodes.push_one(opcode);
|
||||
}
|
||||
|
||||
pub fn compile_ast(self: *Self, ast: Ast) kym.RuntimeError!void {
|
||||
self.free();
|
||||
|
||||
try self.constant_refs.grow(coral.math.max_int(@typeInfo(u16).Int));
|
||||
|
||||
var compiler = AstCompiler{.chunk = self};
|
||||
|
||||
for (ast.list_statements()) |statement| {
|
||||
try compiler.compile_statement(statement);
|
||||
}
|
||||
|
||||
try self.constant_refs.pack();
|
||||
try self.opcodes.pack();
|
||||
}
|
||||
|
||||
pub fn declare_constant_number(self: *Self, constant: kym.Float) kym.RuntimeError!Constant {
|
||||
const tail = self.constant_refs.values.len;
|
||||
|
||||
if (tail == coral.math.max_int(@typeInfo(u16).Int)) {
|
||||
return self.env.raise(error.BadSyntax, "functions may contain a maximum of 65,535 constants");
|
||||
}
|
||||
|
||||
var constant_ref = try self.env.new_number(constant);
|
||||
|
||||
errdefer self.env.discard(&constant_ref);
|
||||
|
||||
try self.constant_refs.push_one(constant_ref);
|
||||
|
||||
return @intCast(tail);
|
||||
}
|
||||
|
||||
pub fn declare_constant_string(self: *Self, constant: []const coral.io.Byte) kym.RuntimeError!Constant {
|
||||
const tail = self.constant_refs.values.len;
|
||||
|
||||
if (tail == coral.math.max_int(@typeInfo(u16).Int)) {
|
||||
return self.env.raise(error.BadSyntax, "functions may contain a maximum of 65,535 constants");
|
||||
}
|
||||
|
||||
var constant_ref = try self.env.new_string(constant);
|
||||
|
||||
errdefer self.env.discard(&constant_ref);
|
||||
|
||||
try self.constant_refs.push_one(constant_ref);
|
||||
|
||||
return @intCast(tail);
|
||||
}
|
||||
|
||||
pub fn execute(self: *Self, global_ref: ?*const kym.RuntimeRef, name: []const coral.io.Byte) kym.RuntimeError!?*kym.RuntimeRef {
|
||||
_ = name;
|
||||
|
||||
try self.env.frame_push();
|
||||
|
||||
for (self.opcodes.values) |opcode| {
|
||||
switch (opcode) {
|
||||
.push_nil => try self.env.local_push_ref(null),
|
||||
.push_true => try self.env.local_push_boolean(true),
|
||||
.push_false => try self.env.local_push_boolean(false),
|
||||
.push_const => |constant| try self.env.local_push_ref(self.constant_refs.values[constant]),
|
||||
|
||||
.push_table => |push_table| {
|
||||
var table_ref = try kym.new_table(self.env);
|
||||
|
||||
defer self.env.discard(&table_ref);
|
||||
|
||||
{
|
||||
var popped = @as(usize, 0);
|
||||
|
||||
while (popped < push_table.field_count) : (popped += 1) {
|
||||
try self.env.set_dynamic(table_ref, try self.env.local_pop(), try self.env.local_pop());
|
||||
}
|
||||
}
|
||||
|
||||
try self.env.local_push_ref(table_ref);
|
||||
},
|
||||
|
||||
.local_push_ref => |local| {
|
||||
var ref = try self.env.local_get(local);
|
||||
|
||||
defer self.env.discard(&ref);
|
||||
|
||||
try self.env.local_push_ref(ref);
|
||||
},
|
||||
|
||||
.get_global => {
|
||||
var identifier_ref = try self.env.local_pop();
|
||||
|
||||
defer self.env.discard(&identifier_ref);
|
||||
|
||||
var ref = try self.env.get_dynamic(global_ref, identifier_ref);
|
||||
|
||||
defer self.env.discard(&ref);
|
||||
|
||||
try self.env.local_push_ref(ref);
|
||||
},
|
||||
|
||||
.set_local => |local| {
|
||||
var ref = try self.env.local_pop();
|
||||
|
||||
defer self.env.discard(&ref);
|
||||
|
||||
try self.env.local_set(local, ref);
|
||||
},
|
||||
|
||||
.global_call => |_| {
|
||||
|
||||
},
|
||||
|
||||
.neg => try self.env.local_push_number(switch (self.env.unbox(try self.env.local_pop())) {
|
||||
.number => |number| -number,
|
||||
else => return self.env.raise(error.TypeMismatch, "object is not scalar negatable"),
|
||||
}),
|
||||
|
||||
.not => try self.env.local_push_boolean(switch (self.env.unbox(try self.env.local_pop())) {
|
||||
.boolean => |boolean| !boolean,
|
||||
else => return self.env.raise(error.TypeMismatch, "object is not boolean negatable"),
|
||||
}),
|
||||
|
||||
.add => {
|
||||
var rhs_ref = try self.env.local_pop();
|
||||
|
||||
defer self.env.discard(&rhs_ref);
|
||||
|
||||
var lhs_ref = try self.env.local_pop();
|
||||
|
||||
defer self.env.discard(&lhs_ref);
|
||||
|
||||
try self.env.local_push_ref(switch (self.env.unbox(lhs_ref)) {
|
||||
.number => |lhs_number| switch (self.env.unbox(rhs_ref)) {
|
||||
.number => |rhs_number| try self.env.new_number(lhs_number + rhs_number),
|
||||
else => return self.env.raise(error.TypeMismatch, "right-hand object is not addable"),
|
||||
},
|
||||
|
||||
else => return self.env.raise(error.TypeMismatch, "left-hand object is not addable"),
|
||||
});
|
||||
},
|
||||
|
||||
.sub => {
|
||||
var rhs_ref = try self.env.local_pop();
|
||||
|
||||
defer self.env.discard(&rhs_ref);
|
||||
|
||||
var lhs_ref = try self.env.local_pop();
|
||||
|
||||
defer self.env.discard(&lhs_ref);
|
||||
|
||||
try self.env.local_push_ref(switch (self.env.unbox(lhs_ref)) {
|
||||
.number => |lhs_number| switch (self.env.unbox(rhs_ref)) {
|
||||
.number => |rhs_number| try self.env.new_number(lhs_number - rhs_number),
|
||||
else => return self.env.raise(error.TypeMismatch, "right-hand object is not subtractable"),
|
||||
},
|
||||
|
||||
else => return self.env.raise(error.TypeMismatch, "left-hand object is not subtractable"),
|
||||
});
|
||||
},
|
||||
|
||||
.mul => {
|
||||
var rhs_ref = try self.env.local_pop();
|
||||
|
||||
defer self.env.discard(&rhs_ref);
|
||||
|
||||
var lhs_ref = try self.env.local_pop();
|
||||
|
||||
defer self.env.discard(&lhs_ref);
|
||||
|
||||
try self.env.local_push_ref(switch (self.env.unbox(lhs_ref)) {
|
||||
.number => |lhs_number| switch (self.env.unbox(rhs_ref)) {
|
||||
.number => |rhs_number| try self.env.new_number(lhs_number * rhs_number),
|
||||
else => return self.env.raise(error.TypeMismatch, "right-hand object is not multiplyable"),
|
||||
},
|
||||
|
||||
else => return self.env.raise(error.TypeMismatch, "left-hand object is not multiplyable"),
|
||||
});
|
||||
},
|
||||
|
||||
.div => {
|
||||
var rhs_ref = try self.env.local_pop();
|
||||
|
||||
defer self.env.discard(&rhs_ref);
|
||||
|
||||
var lhs_ref = try self.env.local_pop();
|
||||
|
||||
defer self.env.discard(&lhs_ref);
|
||||
|
||||
try self.env.local_push_ref(switch (self.env.unbox(lhs_ref)) {
|
||||
.number => |lhs_number| switch (self.env.unbox(rhs_ref)) {
|
||||
.number => |rhs_number| try self.env.new_number(lhs_number / rhs_number),
|
||||
else => return self.env.raise(error.TypeMismatch, "right-hand object is not divisable"),
|
||||
},
|
||||
|
||||
else => return self.env.raise(error.TypeMismatch, "left-hand object is not divisable"),
|
||||
});
|
||||
},
|
||||
|
||||
.eql => {
|
||||
var rhs_ref = try self.env.local_pop();
|
||||
|
||||
defer self.env.discard(&rhs_ref);
|
||||
|
||||
var lhs_ref = try self.env.local_pop();
|
||||
|
||||
defer self.env.discard(&lhs_ref);
|
||||
|
||||
try self.env.local_push_boolean(try kym.test_equality(self.env, lhs_ref, rhs_ref));
|
||||
},
|
||||
|
||||
.cgt => {
|
||||
var rhs_ref = try self.env.local_pop();
|
||||
|
||||
defer self.env.discard(&rhs_ref);
|
||||
|
||||
var lhs_ref = try self.env.local_pop();
|
||||
|
||||
defer self.env.discard(&lhs_ref);
|
||||
|
||||
try self.env.local_push_boolean(try kym.test_difference(self.env, lhs_ref, rhs_ref) > 0);
|
||||
},
|
||||
|
||||
.clt => {
|
||||
var rhs_ref = try self.env.local_pop();
|
||||
|
||||
defer self.env.discard(&rhs_ref);
|
||||
|
||||
var lhs_ref = try self.env.local_pop();
|
||||
|
||||
defer self.env.discard(&lhs_ref);
|
||||
|
||||
try self.env.local_push_boolean(try kym.test_difference(self.env, lhs_ref, rhs_ref) < 0);
|
||||
},
|
||||
|
||||
.cge => {
|
||||
var rhs_ref = try self.env.local_pop();
|
||||
|
||||
defer self.env.discard(&rhs_ref);
|
||||
|
||||
var lhs_ref = try self.env.local_pop();
|
||||
|
||||
defer self.env.discard(&lhs_ref);
|
||||
|
||||
try self.env.local_push_boolean(try kym.test_difference(self.env, lhs_ref, rhs_ref) >= 0);
|
||||
},
|
||||
|
||||
.cle => {
|
||||
var rhs_ref = try self.env.local_pop();
|
||||
|
||||
defer self.env.discard(&rhs_ref);
|
||||
|
||||
var lhs_ref = try self.env.local_pop();
|
||||
|
||||
defer self.env.discard(&lhs_ref);
|
||||
|
||||
try self.env.local_push_boolean(try kym.test_difference(self.env, lhs_ref, rhs_ref) <= 0);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const result = try self.env.local_pop();
|
||||
|
||||
try self.env.frame_pop();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
pub fn free(self: *Self) void {
|
||||
for (self.constant_refs.values) |*constant| {
|
||||
self.env.discard(constant);
|
||||
}
|
||||
|
||||
self.opcodes.free();
|
||||
self.constant_refs.free();
|
||||
}
|
||||
|
||||
fn is_zero(utf8: []const u8) bool {
|
||||
return coral.io.equals(utf8, "0") or coral.io.equals(utf8, "0.0");
|
||||
}
|
||||
|
||||
pub fn make(env: *kym.RuntimeEnv) Self {
|
||||
return Self{
|
||||
.opcodes = OpcodeList.make(env.allocator),
|
||||
.constant_refs = RefList.make(env.allocator),
|
||||
.env = env,
|
||||
};
|
||||
}
|
|
@ -1,148 +0,0 @@
|
|||
const coral = @import("coral");
|
||||
|
||||
allocator: coral.io.Allocator,
|
||||
interned: SymbolTable,
|
||||
globals: Object,
|
||||
values: DataStack,
|
||||
frames: CallStack,
|
||||
|
||||
pub const Float = f64;
|
||||
|
||||
const CallStack = coral.list.Stack(struct {
|
||||
callable: *Object,
|
||||
opcode_index: usize,
|
||||
stack_index: usize,
|
||||
});
|
||||
|
||||
const DataStack = coral.list.Stack(Variant);
|
||||
|
||||
pub const Object = struct {
|
||||
ref_count: usize,
|
||||
userdata: []coral.io.Byte,
|
||||
userinfo: *const anyopaque,
|
||||
};
|
||||
|
||||
pub const PopError = error {
|
||||
StackOverflow,
|
||||
};
|
||||
|
||||
const Self = @This();
|
||||
|
||||
const SymbolTable = coral.map.Table([]const coral.io.Byte, *Object, coral.map.string_table_traits);
|
||||
|
||||
pub const Variant = union (enum) {
|
||||
nil,
|
||||
true,
|
||||
false,
|
||||
number: Float,
|
||||
object: *Object,
|
||||
|
||||
pub fn equals(self: Variant, other: Variant) bool {
|
||||
return switch (self) {
|
||||
.nil => other == .nil,
|
||||
.true => other == .true,
|
||||
.false => other == .false,
|
||||
|
||||
.number => |number| switch (other) {
|
||||
.number => |other_number| number == other_number,
|
||||
else => false,
|
||||
},
|
||||
|
||||
.object => |object| switch (other) {
|
||||
.object => |other_object| object == other_object,
|
||||
else => false,
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub fn acquire_instance(_: *Self, object: *Object) *Object {
|
||||
// TODO: safety-check object belongs to state.
|
||||
object.ref_count += 1;
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
pub fn acquire_interned(self: *Self, userdata: []const u8, userinfo: *const anyopaque) coral.io.AllocationError!*Object {
|
||||
// TODO: Include userinfo in matching lookup.
|
||||
if (self.interned.lookup(userdata)) |object| {
|
||||
return self.acquire_instance(object);
|
||||
} else {
|
||||
const data_object = try self.acquire_new(userdata, userinfo);
|
||||
|
||||
errdefer self.release(data_object);
|
||||
|
||||
coral.debug.assert(try self.interned.insert(data_object.userdata, data_object));
|
||||
|
||||
return data_object;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn acquire_new(self: *Self, userdata: []const u8, userinfo: *const anyopaque) coral.io.AllocationError!*Object {
|
||||
const allocated_userdata = try coral.io.allocate_copy(self.allocator, userdata);
|
||||
|
||||
errdefer self.allocator.deallocate(allocated_userdata);
|
||||
|
||||
const allocated_object = try coral.io.allocate_one(self.allocator, Object{
|
||||
.ref_count = 1,
|
||||
.userdata = allocated_userdata,
|
||||
.userinfo = userinfo,
|
||||
});
|
||||
|
||||
errdefer self.allocator.deallocate(allocated_object);
|
||||
|
||||
return allocated_object;
|
||||
}
|
||||
|
||||
pub fn free(self: *Self) void {
|
||||
self.values.free();
|
||||
self.frames.free();
|
||||
self.interned.free();
|
||||
}
|
||||
|
||||
pub fn get_value(self: *Self, index: u8) Variant {
|
||||
return if (index < self.values.values.len) self.values.values[index] else .nil;
|
||||
}
|
||||
|
||||
pub fn make(allocator: coral.io.Allocator) Self {
|
||||
return .{
|
||||
.values = DataStack.make(allocator),
|
||||
.frames = CallStack.make(allocator),
|
||||
.interned = SymbolTable.make(allocator),
|
||||
.allocator = allocator,
|
||||
|
||||
.globals = .{
|
||||
.ref_count = 0,
|
||||
.userdata = &.{},
|
||||
.userinfo = &.{},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn pop_value(self: *Self) PopError!Variant {
|
||||
return self.values.pop() orelse error.StackOverflow;
|
||||
}
|
||||
|
||||
pub fn push_value(self: *Self, value: Variant) coral.io.AllocationError!void {
|
||||
return self.values.push_one(value);
|
||||
}
|
||||
|
||||
pub fn release(self: *Self, object: *Object) void {
|
||||
coral.debug.assert(object.ref_count != 0);
|
||||
|
||||
object.ref_count -= 1;
|
||||
|
||||
if (object.ref_count == 0) {
|
||||
self.allocator.deallocate(object);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_value(self: *Self, index: u8, value: Variant) bool {
|
||||
if (index >= self.values.values.len) {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.values.values[index] = value;
|
||||
|
||||
return true;
|
||||
}
|
|
@ -1,76 +1,82 @@
|
|||
const State = @import("./State.zig");
|
||||
|
||||
const coral = @import("coral");
|
||||
|
||||
state: *State,
|
||||
const kym = @import("../kym.zig");
|
||||
|
||||
fields: FieldTable,
|
||||
array: ArrayList,
|
||||
|
||||
const ArrayList = coral.list.Stack(State.Variant);
|
||||
|
||||
const Field = struct {
|
||||
field: *State.Object,
|
||||
value: State.Variant,
|
||||
|
||||
fn release_objects(self: Field, state: *State) void {
|
||||
state.release(self.field);
|
||||
|
||||
if (self.value == .object) {
|
||||
state.release(self.value.object);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const FieldTable = coral.map.Table([]const coral.io.Byte, Field, coral.map.string_table_traits);
|
||||
const FieldTable = coral.map.Table([]const coral.io.Byte, struct {
|
||||
key_ref: ?*kym.RuntimeRef,
|
||||
value_ref: ?*kym.RuntimeRef,
|
||||
}, coral.map.string_table_traits);
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn free(self: *Self) void {
|
||||
{
|
||||
var field_iterator = FieldTable.Iterable{.table = &self.fields};
|
||||
|
||||
while (field_iterator.next()) |entry| {
|
||||
entry.value.release_objects(self.state);
|
||||
}
|
||||
}
|
||||
|
||||
self.fields.free();
|
||||
self.array.free();
|
||||
}
|
||||
|
||||
pub fn get_field(self: *Self, field_name: *State.Object) State.Variant {
|
||||
const field = self.fields.lookup(field_name.userdata) orelse return .nil;
|
||||
|
||||
if (field.value == .object) {
|
||||
return .{.object = self.state.acquire_instance(field.value.object)};
|
||||
}
|
||||
|
||||
return field.value;
|
||||
}
|
||||
|
||||
pub fn get_index(self: *Self, index: usize) State.Variant {
|
||||
return self.array.values[index];
|
||||
}
|
||||
|
||||
pub fn make(allocator: coral.io.Allocator, state: *State) Self {
|
||||
pub fn make(env: *kym.RuntimeEnv) Self {
|
||||
return .{
|
||||
.state = state,
|
||||
.fields = FieldTable.make(allocator),
|
||||
.array = ArrayList.make(allocator),
|
||||
.fields = FieldTable.make(env.allocator),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn set_field(self: *Self, field_name: *State.Object, value: State.Variant) coral.io.AllocationError!void {
|
||||
const previous_entry = try self.fields.replace(field_name.userdata, .{
|
||||
.field = field_name,
|
||||
.value = value,
|
||||
});
|
||||
pub const typeinfo = kym.Typeinfo{
|
||||
.call = typeinfo_call,
|
||||
.clean = typeinfo_clean,
|
||||
.get = typeinfo_get,
|
||||
.set = typeinfo_set,
|
||||
};
|
||||
|
||||
if (previous_entry) |entry| {
|
||||
entry.value.release_objects(self.state);
|
||||
fn typeinfo_call(context: kym.CallContext) kym.RuntimeError!?*kym.RuntimeRef {
|
||||
return context.env.raise(error.TypeMismatch, "cannot call a table");
|
||||
}
|
||||
|
||||
fn typeinfo_clean(userdata: []u8) void {
|
||||
@as(*Self, @ptrCast(@alignCast(userdata.ptr))).free();
|
||||
}
|
||||
|
||||
fn typeinfo_get(context: kym.IndexContext) kym.RuntimeError!?*kym.RuntimeRef {
|
||||
const table = @as(*Self, @ptrCast(@alignCast(context.userdata.ptr)));
|
||||
|
||||
return switch (context.env.unbox(context.index_ref)) {
|
||||
.string => |string| context.env.acquire((table.fields.lookup(string) orelse return null).value_ref),
|
||||
// TODO: Implement number indices in tables.
|
||||
.number => |_| unreachable,
|
||||
else => context.env.raise(error.TypeMismatch, "table objects may only be indexed with strings or numbers"),
|
||||
};
|
||||
}
|
||||
|
||||
fn typeinfo_set(context: kym.IndexContext, value_ref: ?*const kym.RuntimeRef) kym.RuntimeError!void {
|
||||
const table = @as(*Self, @ptrCast(@alignCast(context.userdata.ptr)));
|
||||
var acquired_value_ref = context.env.acquire(value_ref);
|
||||
|
||||
errdefer context.env.discard(&acquired_value_ref);
|
||||
|
||||
switch (context.env.unbox(context.index_ref)) {
|
||||
.string => |string| {
|
||||
var acquired_index_ref = context.env.acquire(context.index_ref);
|
||||
|
||||
errdefer context.env.discard(&acquired_index_ref);
|
||||
|
||||
var displaced_table_entry = if (acquired_value_ref) |ref| try table.fields.replace(string, .{
|
||||
.key_ref = acquired_index_ref,
|
||||
.value_ref = ref,
|
||||
}) else table.fields.remove(string);
|
||||
|
||||
if (displaced_table_entry) |*entry| {
|
||||
context.env.discard(&entry.value.key_ref);
|
||||
context.env.discard(&entry.value.value_ref);
|
||||
}
|
||||
},
|
||||
|
||||
.number => |_| {
|
||||
// TODO: Implement number indices in tables.
|
||||
unreachable;
|
||||
},
|
||||
|
||||
else => {
|
||||
return context.env.raise(error.TypeMismatch, "table objects may only be indexed with strings or numbers");
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_index(self: *Self, index: usize, value: State.Variant) coral.io.AllocationError!void {
|
||||
self.array.values[index] = value;
|
||||
}
|
||||
|
|
|
@ -48,9 +48,17 @@ pub fn run_app(file_access: file.Access) RuntimeError!void {
|
|||
|
||||
defer script_env.free();
|
||||
|
||||
var globals_ref = kym.new_table(&script_env) catch {
|
||||
try fail_log.write("failed to initialize script runtime");
|
||||
|
||||
return error.InitFailure;
|
||||
};
|
||||
|
||||
defer script_env.discard(&globals_ref);
|
||||
|
||||
var manifest = app.Manifest{};
|
||||
|
||||
manifest.load(&script_env, file_access) catch {
|
||||
manifest.load(&script_env, globals_ref, file_access) catch {
|
||||
fail_log.write("failed to load / execute app.ona manifest") catch {};
|
||||
|
||||
return error.BadManifest;
|
||||
|
|
Loading…
Reference in New Issue