From e5c18e4a14730f7de228a68d3a080ee9d4bcaded Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 12 Nov 2023 13:09:30 +0000 Subject: [PATCH 1/4] Refactor Kym --- source/coral/arena.zig | 20 +- source/coral/list.zig | 34 +- source/coral/map.zig | 26 +- source/ona/app.zig | 20 +- source/ona/kym.zig | 1727 +++++++++++++++++++++++++------------- source/ona/kym/Chunk.zig | 612 ++++++-------- source/ona/kym/Table.zig | 52 +- source/ona/kym/tree.zig | 12 +- source/ona/ona.zig | 4 +- 9 files changed, 1474 insertions(+), 1033 deletions(-) diff --git a/source/coral/arena.zig b/source/coral/arena.zig index 2408007..cd5a88a 100644 --- a/source/coral/arena.zig +++ b/source/coral/arena.zig @@ -57,7 +57,7 @@ pub const Stacking = struct { }); } - pub fn free(self: *Stacking) void { + pub fn deinit(self: *Stacking) void { while (self.head_region) |region| { const next_region = region.next; @@ -75,6 +75,15 @@ pub const Stacking = struct { unreachable; } + pub fn init(allocator: io.Allocator, min_region_size: usize) Stacking { + return Stacking{ + .allocator = allocator, + .min_region_size = min_region_size, + .head_region = null, + .tail_region = null, + }; + } + fn reallocate(self: *Stacking, _: usize, allocation: ?[]io.Byte, byte_size: usize) io.AllocationError![]io.Byte { if (allocation) |buffer| { if (byte_size < buffer.len) { @@ -104,13 +113,4 @@ pub const Stacking = struct { return @as([*]io.Byte, @ptrCast(tail_region.allocate(region_size)))[0 .. byte_size]; } - - pub fn make(allocator: io.Allocator, min_region_size: usize) Stacking { - return Stacking{ - .allocator = allocator, - .min_region_size = min_region_size, - .head_region = null, - .tail_region = null, - }; - } }; diff --git a/source/coral/list.zig b/source/coral/list.zig index 5fa5040..ac90828 100644 --- a/source/coral/list.zig +++ b/source/coral/list.zig @@ -16,6 +16,16 @@ pub fn Stack(comptime Value: type) type { self.values = self.values[0 .. 0]; } + pub fn deinit(self: *Self) void { + if (self.capacity == 0) { + return; + } + + self.allocator.deallocate(self.values.ptr[0 .. self.capacity]); + + self.values = &.{}; + } + pub fn drop(self: *Self, count: usize) bool { if (math.checked_sub(self.values.len, count)) |updated_count| { self.values = self.values[0 .. updated_count]; @@ -26,16 +36,6 @@ pub fn Stack(comptime Value: type) type { return false; } - pub fn free(self: *Self) void { - if (self.capacity == 0) { - return; - } - - self.allocator.deallocate(self.values.ptr[0 .. self.capacity]); - - self.values = &.{}; - } - pub fn grow(self: *Self, growth_amount: usize) io.AllocationError!void { const grown_capacity = self.capacity + growth_amount; const buffer = try self.allocator.reallocate(null, @sizeOf(Value) * grown_capacity); @@ -51,11 +51,7 @@ pub fn Stack(comptime Value: type) type { self.capacity = grown_capacity; } - pub fn is_empty(self: Self) bool { - return self.values.len == 0; - } - - pub fn make(allocator: io.Allocator) Self { + pub fn init(allocator: io.Allocator) Self { return .{ .allocator = allocator, .capacity = 0, @@ -63,7 +59,11 @@ pub fn Stack(comptime Value: type) type { }; } - pub fn pack(self: *Self) io.AllocationError!void { + pub fn is_empty(self: Self) bool { + return self.values.len == 0; + } + + pub fn pack(self: *Self) io.AllocationError![]Value { const packed_size = self.values.len; const buffer = try self.allocator.reallocate(null, @sizeOf(Value) * self.values.len); @@ -75,6 +75,8 @@ pub fn Stack(comptime Value: type) type { self.values = @as([*]Value, @ptrCast(@alignCast(buffer)))[0 .. packed_size]; self.capacity = packed_size; + + return self.values; } pub fn peek(self: Self) ?Value { diff --git a/source/coral/map.zig b/source/coral/map.zig index f19cd27..b16f239 100644 --- a/source/coral/map.zig +++ b/source/coral/map.zig @@ -174,7 +174,7 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime Traits: type) ty self.count = 0; } - pub fn free(self: *Self) void { + pub fn deinit(self: *Self) void { if (self.entries.len == 0) { return; } @@ -185,6 +185,15 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime Traits: type) ty self.count = 0; } + pub fn init(allocator: io.Allocator, traits: Traits) Self { + return .{ + .allocator = allocator, + .count = 0, + .entries = &.{}, + .traits = traits, + }; + } + pub fn insert(self: *Self, key: Key, value: Value) io.AllocationError!bool { try self.rehash(load_max); @@ -235,23 +244,14 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime Traits: type) ty return null; } - pub fn make(allocator: io.Allocator, traits: Traits) Self { - return .{ - .allocator = allocator, - .count = 0, - .entries = &.{}, - .traits = traits, - }; - } - pub fn rehash(self: *Self, max_load: f32) io.AllocationError!void { if (self.calculate_load_factor() <= max_load) { return; } - var table = make(self.allocator, self.traits); + var table = init(self.allocator, self.traits); - errdefer table.free(); + errdefer table.deinit(); table.entries = allocate: { const min_count = @max(1, self.count); @@ -266,7 +266,7 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime Traits: type) ty } } - self.free(); + self.deinit(); self.* = table; } diff --git a/source/ona/app.zig b/source/ona/app.zig index 0ef5952..be3044e 100644 --- a/source/ona/app.zig +++ b/source/ona/app.zig @@ -13,15 +13,15 @@ pub const Manifest = struct { tick_rate: f32 = 60.0, pub fn load(self: *Manifest, env: *kym.RuntimeEnv) kym.RuntimeError!void { - const manifest = try env.import(file.Path.from(&.{"app.ona"})) orelse return; + const manifest = (try env.import(file.Path.from(&.{"app.ona"}))).pop() orelse return; - defer env.discard(manifest); + defer env.release(manifest); const width = @as(u16, get: { if (try kym.get_field(env, manifest, "width")) |ref| { - defer env.discard(ref); + defer env.release(ref); - const fixed = try env.unwrap_fixed(ref); + const fixed = try env.expect_fixed(ref); if (fixed > 0 and fixed < coral.math.max_int(@typeInfo(@TypeOf(self.width)).Int)) { break: get @intCast(fixed); @@ -33,9 +33,9 @@ pub const Manifest = struct { const height = @as(u16, get: { if (try kym.get_field(env, manifest, "height")) |ref| { - defer env.discard(ref); + defer env.release(ref); - const fixed = try env.unwrap_fixed(ref); + const fixed = try env.expect_fixed(ref); if (fixed > 0 and fixed < coral.math.max_int(@typeInfo(@TypeOf(self.height)).Int)) { break: get @intCast(fixed); @@ -47,18 +47,18 @@ pub const Manifest = struct { const tick_rate = @as(f32, get: { if (try kym.get_field(env, manifest, "tick_rate")) |ref| { - defer env.discard(ref); + defer env.release(ref); - break: get @floatCast(try env.unwrap_float(ref)); + break: get @floatCast(try env.expect_float(ref)); } break: get self.tick_rate; }); if (try kym.get_field(env, manifest, "title")) |ref| { - defer env.discard(ref); + defer env.release(ref); - const title_string = try env.unwrap_string(ref); + const title_string = try env.expect_string(ref); const limited_title_len = @min(title_string.len, self.title.len); coral.io.copy(&self.title, title_string[0 .. limited_title_len]); diff --git a/source/ona/kym.zig b/source/ona/kym.zig index bbebc34..b1ceea9 100644 --- a/source/ona/kym.zig +++ b/source/ona/kym.zig @@ -10,272 +10,383 @@ const tokens = @import("./kym/tokens.zig"); const tree = @import("./kym/tree.zig"); -pub const Frame = struct { - callable: *RuntimeRef, - arg_count: u8, - locals_top: usize, - - pub fn args(self: *const Frame, env: *RuntimeEnv) []const ?*const RuntimeRef { - return env.locals.values[self.locals_top .. (self.locals_top + self.arg_count)]; - } - - pub fn expect_arg(self: *const Frame, env: *RuntimeEnv, arg_index: u8) RuntimeError!*const RuntimeRef { - return self.has_arg(env, arg_index) orelse env.raise(error.TypeMismatch, "nil reference", .{}); - } - - pub fn has_arg(self: *const Frame, env: *RuntimeEnv, arg_index: u8) ?*const RuntimeRef { - return if (arg_index >= self.arg_count) null else env.locals.values[self.locals_top + arg_index]; - } -}; - +/// +/// Fixed-length integer number type. +/// pub const Fixed = i32; +/// +/// Floating point real number type. +/// pub const Float = f64; +/// +/// Supertype for all numeric types. +/// +pub const Numeric = union (enum) { + fixed: Fixed, + float: Float, + vector2: Vector2, + vector3: Vector3, +}; + +/// +/// Runtime environment virtual machine state. +/// pub const RuntimeEnv = struct { + allocator: coral.io.Allocator, options: Options, interned_symbols: SymbolSet, - allocator: coral.io.Allocator, locals: LocalList, - frames: FrameStack, + frames: FrameList, - const FrameStack = coral.list.Stack(Frame); + const FrameList = coral.list.Stack(struct { + callable: *RuntimeObj, + locals_top: usize, + arg_count: u8, + }); - const LocalList = coral.list.Stack(?*RuntimeRef); + const LocalList = coral.list.Stack(?*RuntimeObj); + /// + /// Optional settings for a [RuntimeEnv]. + /// 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; + /// + /// Byte string printing function. + /// + pub const Printer = fn ([]const coral.io.Byte) void; + }; const SymbolSet = coral.map.StringTable([:0]coral.io.Byte); - pub fn add(self: *RuntimeEnv, lhs: *const RuntimeRef, rhs: *const RuntimeRef) RuntimeError!*RuntimeRef { - return switch (lhs.object().payload) { - .fixed => |lhs_fixed| switch (rhs.object().payload) { - .fixed => |rhs_fixed| add: { - if (coral.math.checked_add(lhs_fixed, rhs_fixed)) |fixed| { - break: add self.new_fixed(fixed); - } + pub fn add_fixed(self: *RuntimeEnv, rhs_fixed: Fixed) RuntimeError!*RuntimeEnv { + const addable = try self.expect_object(self.pop()); - break: add self.new_float(@as(Float, @floatFromInt(lhs_fixed)) + @as(Float, @floatFromInt(rhs_fixed))); - }, + defer self.release(addable); - .float => |rhs_float| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) + rhs_float), + return switch (addable.internal().payload) { + .fixed => |lhs_fixed| if (coral.math.checked_add(lhs_fixed, rhs_fixed)) |result| + self.new_fixed(result) + else + self.new_float(@as(Float, @floatFromInt(lhs_fixed)) + @as(Float, @floatFromInt(rhs_fixed))), - else => self.raise(error.TypeMismatch, "right-hand {typename} is not addable", .{ - .typename = rhs.typename(), - }), + .float => |lhs_float| self.new_float(lhs_float + @as(Float, @floatFromInt(rhs_fixed))), + + .vector2 => |lhs_vector2| new: { + const rhs_scalar = @as(f32, @floatFromInt(rhs_fixed)); + + break: new self.new_vector2(lhs_vector2[0] + rhs_scalar, lhs_vector2[1] + rhs_scalar); }, - .float => |lhs_float| switch (rhs.object().payload) { - .float => |rhs_float| self.new_float(lhs_float + rhs_float), - .fixed => |rhs_fixed| self.new_float(lhs_float + @as(Float, @floatFromInt(rhs_fixed))), + .vector3 => |lhs_vector3| new: { + const rhs_scalar = @as(f32, @floatFromInt(rhs_fixed)); - else => self.raise(error.TypeMismatch, "right-hand {typename} is not addable", .{ - .typename = rhs.typename(), - }), + break: new self.new_vector3(lhs_vector3[0] + rhs_scalar, lhs_vector3[1] + rhs_scalar, lhs_vector3[2] + rhs_scalar); }, - else => self.raise(error.TypeMismatch, "left-hand {typename} is not addable", .{ - .typename = lhs.typename(), + else => self.raise(error.TypeMismatch, "fixed types are not addable with {typename}", .{ + .typename = addable.get_typename(), }), }; } - pub fn call(self: *RuntimeEnv, callable: *const RuntimeRef, args: []const *RuntimeRef) RuntimeError!?*RuntimeRef { - // TODO: Handle errors. - for (args) |arg| { - try self.locals.push_one(arg.acquire()); - } + pub fn add_float(self: *RuntimeEnv, rhs_float: Float) RuntimeError!*RuntimeEnv { + const addable = try self.expect_object(self.pop()); - const frame = try self.push_frame(callable, @intCast(args.len)); + defer self.release(addable); - defer self.pop_frame(); + return switch (addable.internal().payload) { + .fixed => |lhs_fixed| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) + rhs_float), + .float => |lhs_float| self.new_float(lhs_float + lhs_float), - return self.call_frame(&frame); - } + .vector2 => |lhs_vector2| new: { + const rhs_scalar = @as(f32, @floatCast(rhs_float)); - pub fn call_frame(self: *RuntimeEnv, frame: *const Frame) RuntimeError!?*RuntimeRef { - return switch (frame.callable.object().payload) { - .syscall => |syscall| syscall(self, frame), - .dynamic => |dynamic| dynamic.typeinfo().call(self, dynamic.userdata(), frame), - else => self.raise(error.TypeMismatch, "{typename} is not callable", .{.typename = frame.callable.typename()}), + break: new self.new_vector2(lhs_vector2[0] + rhs_scalar, lhs_vector2[1] + rhs_scalar); + }, + + .vector3 => |lhs_vector3| new: { + const rhs_scalar = @as(f32, @floatCast(rhs_float)); + + break: new self.new_vector3(lhs_vector3[0] + rhs_scalar, lhs_vector3[1] + rhs_scalar, lhs_vector3[2] + rhs_scalar); + }, + + else => self.raise(error.TypeMismatch, "fixed types are not addable with {typename}", .{ + .typename = addable.get_typename(), + }), }; } - pub fn compare(self: *RuntimeEnv, lhs: *const RuntimeRef, rhs: *const RuntimeRef) RuntimeError!Float { - return switch (lhs.object().payload) { - .fixed => |lhs_fixed| switch (rhs.object().payload) { - .fixed => |rhs_fixed| @as(Float, @floatFromInt(lhs_fixed)) - @as(Float, @floatFromInt(rhs_fixed)), - .float => |rhs_float| @as(Float, @floatFromInt(lhs_fixed)) - rhs_float, + pub fn add_vector2(self: *RuntimeEnv, rhs_vector2: Vector2) RuntimeError!*RuntimeEnv { + const addable = try self.expect_object(self.pop()); - else => return self.raise(error.TypeMismatch, "right-hand {typename} is not comparable", .{ - .typename = rhs.typename(), + defer self.release(addable); + + return if (addable.is_vector2()) |lhs_vector2| + self.new_vector2(lhs_vector2[0] + rhs_vector2[0], lhs_vector2[1] + rhs_vector2[1]) + else + self.raise(error.TypeMismatch, "vector2 types are not addable with {typename}", .{ + .typename = addable.get_typename(), + }); + } + + pub fn add_vector3(self: *RuntimeEnv, rhs_vector3: Vector3) RuntimeError!*RuntimeEnv { + const addable = try self.expect_object(self.pop()); + + defer self.release(addable); + + return if (addable.is_vector3()) |lhs_vector3| + self.new_vector3(lhs_vector3[0] + rhs_vector3[0], lhs_vector3[1] + rhs_vector3[1], lhs_vector3[2] + rhs_vector3[2]) + else + self.raise(error.TypeMismatch, "vector3 types are not addable with {typename}", .{ + .typename = addable.get_typename(), + }); + } + + pub fn arg_get(self: *RuntimeEnv, arg_index: LocalIndex) RuntimeError!*RuntimeEnv { + const frame = &self.frames.values[self.frames.values.len - 1]; + const args = self.locals.values[frame.locals_top .. (frame.locals_top + frame.arg_count)]; + + if (arg_index < args.len) { + return self.push(args[arg_index]); + } + + try self.locals.push_one(null); + + return self; + } + + pub fn call(self: *RuntimeEnv, local_arg_count: LocalIndex) RuntimeError!*RuntimeEnv { + const callable = try self.expect_object(self.pop()); + + defer self.release(callable); + + const result = get_result: { + try self.frames.push_one(.{ + .locals_top = self.locals.values.len - local_arg_count, + .callable = callable.acquire(), + .arg_count = local_arg_count, + }); + + defer { + const popped_frame = self.frames.pop().?; + + self.release(popped_frame.callable); + coral.debug.assert(popped_frame.locals_top < self.locals.values.len); + + var to_discard = self.locals.values.len - popped_frame.locals_top; + + while (to_discard != 0) : (to_discard -= 1) { + self.discard(); + } + } + + const frame = &self.frames.values[self.frames.values.len - 1]; + + break: get_result try switch (frame.callable.internal().payload) { + .syscall => |syscall| syscall(self), + .dynamic => |dynamic| dynamic.typeinfo().call(self, dynamic.userdata()), + + else => self.raise(error.TypeMismatch, "{typename} is not callable", .{ + .typename = frame.callable.get_typename(), + }), + }; + }; + + errdefer { + if (result) |object| { + self.release(object); + } + } + + try self.locals.push_one(result); + + return self; + } + + pub fn compare_greater(self: *RuntimeEnv, rhs_comparable: *RuntimeObj) RuntimeError!*RuntimeEnv { + const lhs_comparable = try self.expect_object(self.pop()); + + defer self.release(lhs_comparable); + + return switch (lhs_comparable.internal().payload) { + .fixed => |lhs_fixed| switch (rhs_comparable.internal().payload) { + .fixed => |rhs_fixed| self.new_boolean(@as(Float, @floatFromInt(lhs_fixed)) > @as(Float, @floatFromInt(rhs_fixed))), + .float => |rhs_float| self.new_boolean(@as(Float, @floatFromInt(lhs_fixed)) > rhs_float), + + else => self.raise(error.TypeMismatch, "right-hand {typename} is not comparable", .{ + .typename = rhs_comparable.get_typename(), }), }, - .float => |lhs_float| switch (rhs.object().payload) { - .float => |rhs_float| lhs_float - rhs_float, - .fixed => |rhs_fixed| lhs_float - @as(Float, @floatFromInt(rhs_fixed)), + .float => |lhs_float| switch (rhs_comparable.internal().payload) { + .float => |rhs_float| self.new_boolean(lhs_float > rhs_float), + .fixed => |rhs_fixed| self.new_boolean(lhs_float > @as(Float, @floatFromInt(rhs_fixed))), + + else => self.raise(error.TypeMismatch, "right-hand {typename} is not comparable", .{ + .typename = rhs_comparable.get_typename(), + }), + }, + + else => self.raise(error.TypeMismatch, "left-hand {typename} is not comparable", .{ + .typename = lhs_comparable.get_typename(), + }), + }; + } + + pub fn compare_greater_equals(self: *RuntimeEnv, rhs_comparable: *RuntimeObj) RuntimeError!*RuntimeEnv { + const lhs_comparable = try self.expect_object(self.pop()); + + defer self.release(lhs_comparable); + + return switch (lhs_comparable.internal().payload) { + .fixed => |lhs_fixed| switch (rhs_comparable.internal().payload) { + .fixed => |rhs_fixed| self.new_boolean(@as(Float, @floatFromInt(lhs_fixed)) >= @as(Float, @floatFromInt(rhs_fixed))), + .float => |rhs_float| self.new_boolean(@as(Float, @floatFromInt(lhs_fixed)) >= rhs_float), + + else => self.raise(error.TypeMismatch, "right-hand {typename} is not comparable", .{ + .typename = rhs_comparable.get_typename(), + }), + }, + + .float => |lhs_float| switch (rhs_comparable.internal().payload) { + .float => |rhs_float| self.new_boolean(lhs_float >= rhs_float), + .fixed => |rhs_fixed| self.new_boolean(lhs_float >= @as(Float, @floatFromInt(rhs_fixed))), + + else => self.raise(error.TypeMismatch, "right-hand {typename} is not comparable", .{ + .typename = rhs_comparable.get_typename(), + }), + }, + + else => self.raise(error.TypeMismatch, "left-hand {typename} is not comparable", .{ + .typename = lhs_comparable.get_typename(), + }), + }; + } + + pub fn compare_less(self: *RuntimeEnv, rhs_comparable: *RuntimeObj) RuntimeError!*RuntimeEnv { + const lhs_comparable = try self.expect_object(self.pop()); + + defer self.release(lhs_comparable); + + return switch (lhs_comparable.internal().payload) { + .fixed => |lhs_fixed| switch (rhs_comparable.internal().payload) { + .fixed => |rhs_fixed| self.new_boolean(@as(Float, @floatFromInt(lhs_fixed)) < @as(Float, @floatFromInt(rhs_fixed))), + .float => |rhs_float| self.new_boolean(@as(Float, @floatFromInt(lhs_fixed)) < rhs_float), else => return self.raise(error.TypeMismatch, "right-hand {typename} is not comparable", .{ - .typename = rhs.typename(), + .typename = rhs_comparable.get_typename(), + }), + }, + + .float => |lhs_float| switch (rhs_comparable.internal().payload) { + .float => |rhs_float| self.new_boolean(lhs_float < rhs_float), + .fixed => |rhs_fixed| self.new_boolean(lhs_float < @as(Float, @floatFromInt(rhs_fixed))), + + else => return self.raise(error.TypeMismatch, "right-hand {typename} is not comparable", .{ + .typename = rhs_comparable.get_typename(), }), }, else => return self.raise(error.TypeMismatch, "left-hand {typename} is not comparable", .{ - .typename = lhs.typename(), + .typename = lhs_comparable.get_typename(), }), }; } - pub fn concat(self: *RuntimeEnv, values: []const *const RuntimeRef) RuntimeError!*RuntimeRef { - if (values.len == 0) { + pub fn compare_less_equals(self: *RuntimeEnv, rhs_comparable: *RuntimeObj) RuntimeError!*RuntimeEnv { + const lhs_comparable = try self.expect_object(self.pop()); + + defer self.release(lhs_comparable); + + return switch (lhs_comparable.internal().payload) { + .fixed => |lhs_fixed| switch (rhs_comparable.internal().payload) { + .fixed => |rhs_fixed| self.new_boolean(@as(Float, @floatFromInt(lhs_fixed)) <= @as(Float, @floatFromInt(rhs_fixed))), + .float => |rhs_float| self.new_boolean(@as(Float, @floatFromInt(lhs_fixed)) <= rhs_float), + + else => return self.raise(error.TypeMismatch, "right-hand {typename} is not comparable", .{ + .typename = rhs_comparable.get_typename(), + }), + }, + + .float => |lhs_float| switch (rhs_comparable.internal().payload) { + .float => |rhs_float| self.new_boolean(lhs_float <= rhs_float), + .fixed => |rhs_fixed| self.new_boolean(lhs_float <= @as(Float, @floatFromInt(rhs_fixed))), + + else => return self.raise(error.TypeMismatch, "right-hand {typename} is not comparable", .{ + .typename = rhs_comparable.get_typename(), + }), + }, + + else => return self.raise(error.TypeMismatch, "left-hand {typename} is not comparable", .{ + .typename = lhs_comparable.get_typename(), + }), + }; + } + + pub fn concat(self: *RuntimeEnv, local_concat_count: LocalIndex) RuntimeError!*RuntimeEnv { + if (local_concat_count == 0) { return self.new_string(""); } - var remaining_value_count = values.len; + const concated_buffer = build_buffer: { + var concat_buffer = coral.list.ByteStack.init(self.allocator); - errdefer { - var poppable_value_count = values.len - remaining_value_count; + errdefer concat_buffer.deinit(); - while (poppable_value_count != 0) { - poppable_value_count -= 1; + const concat_values = self.locals.values[(self.locals.values.len - local_concat_count) .. self.locals.values.len]; - const popped_value = self.pop_local() catch unreachable; + for (concat_values) |concat_value| { + const string = (try (try self.push(concat_value)).to_string()).pop().?; - coral.debug.assert(popped_value != null); - self.discard(popped_value.?); + defer self.release(string); + + try concat_buffer.push_all(string.is_string().?); } - } - var length = @as(usize, 0); + { + var concated_value_count = concat_values.len; - while (remaining_value_count != 0) { - remaining_value_count -= 1; + while (concated_value_count != 0) : (concated_value_count -= 1) { + self.discard(); + } + } - const value_string = try self.to_string(values[remaining_value_count]); + break: build_buffer try concat_buffer.pack(); + }; - errdefer self.discard(value_string); + errdefer self.allocator.deallocate(concated_buffer); - const string = value_string.as_string(); - - coral.debug.assert(string != null); - - try self.locals.push_one(value_string); - - length += string.?.len; - } - - const buffer = try coral.io.allocate_many(self.allocator, length, @as(coral.io.Byte, 0)); - - errdefer self.allocator.deallocate(buffer); - - var written = @as(usize, 0); - - while (remaining_value_count != values.len) : (remaining_value_count += 1) { - const value_string = try self.pop_local(); - - coral.debug.assert(value_string != null); - - defer self.discard(value_string.?); - - const string = value_string.?.as_string(); - - coral.debug.assert(string != null); - - coral.io.copy(buffer[written ..], string.?); - - written += string.?.len; - - coral.debug.assert(written <= length); - } - - return RuntimeRef.allocate(self.allocator, .{ + const string = try RuntimeObj.allocate(self.allocator, .{ .ref_count = 1, .payload = .{ .string = .{ - .ptr = buffer.ptr, - .len = @intCast(buffer.len), + .ptr = concated_buffer.ptr, + .len = @intCast(concated_buffer.len), }, }, }); + + errdefer self.release(string); + + try self.locals.push_one(string); + + return self; } - pub fn discard(self: *RuntimeEnv, value: *RuntimeRef) void { - var object = value.object(); - - coral.debug.assert(object.ref_count != 0); - - object.ref_count -= 1; - - if (object.ref_count == 0) { - switch (object.payload) { - .false, .true, .float, .fixed, .symbol, .vector2, .vector3, .syscall => {}, - - .boxed => |*boxed| { - if (boxed.*) |boxed_value| { - self.discard(boxed_value); - } - }, - - .string => |string| { - coral.debug.assert(string.len >= 0); - self.allocator.deallocate(string.ptr[0 .. @intCast(string.len)]); - }, - - .dynamic => |dynamic| { - if (dynamic.typeinfo().destruct) |destruct| { - destruct(self, dynamic.userdata()); - } - - self.allocator.deallocate(dynamic.unpack()); - }, - } - - self.allocator.deallocate(object); - } - } - - pub fn div(self: *RuntimeEnv, lhs: *const RuntimeRef, rhs: *const RuntimeRef) RuntimeError!*RuntimeRef { - return switch (lhs.object().payload) { - .fixed => |lhs_fixed| switch (rhs.object().payload) { - .fixed => |rhs_fixed| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) / @as(Float, @floatFromInt(rhs_fixed))), - .float => |rhs_float| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) / rhs_float), - - else => self.raise(error.TypeMismatch, "right-hand {typename} is not divisible", .{ - .typename = rhs.typename(), - }), - }, - - .float => |lhs_float| switch (rhs.object().payload) { - .float => |rhs_float| self.new_float(lhs_float / rhs_float), - .fixed => |rhs_fixed| self.new_float(lhs_float / @as(Float, @floatFromInt(rhs_fixed))), - - else => self.raise(error.TypeMismatch, "right-hand {typename} is not divisible", .{ - .typename = rhs.typename(), - }), - }, - - else => self.raise(error.TypeMismatch, "left-hand {typename} is not divisible", .{ - .typename = lhs.typename(), - }), - }; - } - - pub fn expect(self: *RuntimeEnv, value: ?*RuntimeRef) RuntimeError!*RuntimeRef { - return value orelse self.raise(error.TypeMismatch, "nil reference", .{}); - } - - pub fn free(self: *RuntimeEnv) void { + /// + /// Deinitializes `self`, freeing all allocated virtual machine resources. + /// + pub fn deinit(self: *RuntimeEnv) void { while (self.locals.pop()) |local| { if (local) |ref| { - self.discard(ref); + self.release(ref); } } @@ -287,240 +398,633 @@ pub const RuntimeEnv = struct { } } - self.frames.free(); - self.locals.free(); - self.interned_symbols.free(); + self.interned_symbols.deinit(); + self.locals.deinit(); + self.frames.deinit(); } - pub fn get(self: *RuntimeEnv, indexable: *RuntimeRef, index: *const RuntimeRef) RuntimeError!?*RuntimeRef { - return switch (indexable.object().payload) { - .vector2 => |vector2| swizzle: { - const swizzle_symbol = try self.unwrap_symbol(index); - var swizzle_buffer = [_]f32{0} ** 3; + pub fn discard(self: *RuntimeEnv) void { + if (self.pop()) |popped| { + self.release(popped); + } + } + + pub fn div_fixed(self: *RuntimeEnv, rhs_fixed: Fixed) RuntimeError!*RuntimeEnv { + if (rhs_fixed == 0) { + return self.raise(error.BadOperation, "cannot divide by zero", .{}); + } + + const addable = try self.expect_object(self.pop()); + + defer self.release(addable); + + return switch (addable.internal().payload) { + .fixed => |lhs_fixed| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) / @as(Float, @floatFromInt(rhs_fixed))), + .float => |lhs_float| self.new_float(lhs_float / @as(Float, @floatFromInt(rhs_fixed))), + + .vector2 => |lhs_vector2| new: { + const rhs_scalar = @as(f32, @floatFromInt(rhs_fixed)); + + break: new self.new_vector2(lhs_vector2[0] / rhs_scalar, lhs_vector2[1] / rhs_scalar); + }, + + .vector3 => |lhs_vector3| new: { + const rhs_scalar = @as(f32, @floatFromInt(rhs_fixed)); + + break: new self.new_vector3(lhs_vector3[0] / rhs_scalar, lhs_vector3[1] / rhs_scalar, lhs_vector3[2] / rhs_scalar); + }, + + else => self.raise(error.TypeMismatch, "fixed types are not divisible with {typename}", .{ + .typename = addable.get_typename(), + }), + }; + } + + pub fn div_float(self: *RuntimeEnv, rhs_float: Float) RuntimeError!*RuntimeEnv { + if (rhs_float == 0) { + return self.raise(error.BadOperation, "cannot divide by zero", .{}); + } + + const addable = try self.expect_object(self.pop()); + + defer self.release(addable); + + return switch (addable.internal().payload) { + .fixed => |lhs_fixed| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) / rhs_float), + .float => |lhs_float| self.new_float(lhs_float / lhs_float), + + .vector2 => |lhs_vector2| new: { + const rhs_scalar = @as(f32, @floatCast(rhs_float)); + + break: new self.new_vector2(lhs_vector2[0] / rhs_scalar, lhs_vector2[1] / rhs_scalar); + }, + + .vector3 => |lhs_vector3| new: { + const rhs_scalar = @as(f32, @floatCast(rhs_float)); + + break: new self.new_vector3(lhs_vector3[0] / rhs_scalar, lhs_vector3[1] / rhs_scalar, lhs_vector3[2] / rhs_scalar); + }, + + else => self.raise(error.TypeMismatch, "fixed types are not divisible with {typename}", .{ + .typename = addable.get_typename(), + }), + }; + } + + pub fn div_vector2(self: *RuntimeEnv, rhs_vector2: Vector2) RuntimeError!*RuntimeEnv { + if (rhs_vector2[0] == 0 or rhs_vector2[1] == 0) { + return self.raise(error.BadOperation, "cannot divide by zero", .{}); + } + + const addable = try self.expect_object(self.pop()); + + defer self.release(addable); + + return if (addable.is_vector2()) |lhs_vector2| + self.new_vector2(lhs_vector2[0] / rhs_vector2[0], lhs_vector2[1] / rhs_vector2[1]) + else + self.raise(error.TypeMismatch, "vector2 types are not divisible with {typename}", .{ + .typename = addable.get_typename(), + }); + } + + pub fn div_vector3(self: *RuntimeEnv, rhs_vector3: Vector3) RuntimeError!*RuntimeEnv { + if (rhs_vector3[0] == 0 or rhs_vector3[1] == 0 or rhs_vector3[2] == 0) { + return self.raise(error.BadOperation, "cannot divide by zero", .{}); + } + + const addable = try self.expect_object(self.pop()); + + defer self.release(addable); + + return if (addable.is_vector3()) |lhs_vector3| + self.new_vector3(lhs_vector3[0] / rhs_vector3[0], lhs_vector3[1] / rhs_vector3[1], lhs_vector3[2] / rhs_vector3[2]) + else + self.raise(error.TypeMismatch, "vector3 types are not divisible with {typename}", .{ + .typename = addable.get_typename(), + }); + } + + pub fn equals_nil(self: *RuntimeEnv) RuntimeError!*RuntimeEnv { + if (self.pop()) |lhs_object| { + defer self.release(lhs_object); + + return self.new_boolean(false); + } + + return self.new_boolean(true); + } + + pub fn equals_object(self: *RuntimeEnv, rhs_object: *RuntimeObj) RuntimeError!*RuntimeEnv { + if (self.pop()) |lhs_object| { + defer self.release(lhs_object); + + return self.new_boolean(lhs_object.equals(rhs_object)); + } + + return self.new_boolean(false); + } + + pub fn expect_dynamic(self: *RuntimeEnv, value: *RuntimeObj, typeinfo: *const Typeinfo) RuntimeError![]coral.io.Byte { + return value.is_dynamic(typeinfo) orelse self.raise(error.TypeMismatch, "expected dynamic object, not {typename}", .{ + .typename = value.get_typename(), + }); + } + + pub fn expect_float(self: *RuntimeEnv, value: *RuntimeObj) RuntimeError!Float { + return switch (value.internal().payload) { + .fixed => |fixed| @floatFromInt(fixed), + .float => |float| float, + + else => self.raise(error.TypeMismatch, "expected float type, not {typename}", .{ + .typename = value.get_typename(), + }), + }; + } + + pub fn expect_fixed(self: *RuntimeEnv, value: *RuntimeObj) RuntimeError!Fixed { + return value.is_fixed() orelse self.raise(error.TypeMismatch, "expected fixed type, not {typename}", .{ + .typename = value.get_typename(), + }); + } + + pub fn expect_numeric(self: *RuntimeEnv, value: *RuntimeObj) RuntimeError!Numeric { + return value.is_numeric() orelse self.raise(error.TypeMismatch, "expected numeric type, not {typename}", .{ + .typename = value.get_typename(), + }); + } + + pub fn expect_object(self: *RuntimeEnv, value: ?*RuntimeObj) RuntimeError!*RuntimeObj { + return value orelse self.raise(error.TypeMismatch, "expected object type, not nil", .{}); + } + + pub fn expect_string(self: *RuntimeEnv, value: *RuntimeObj) RuntimeError![]const coral.io.Byte { + return value.is_string() orelse self.raise(error.TypeMismatch, "expected string type, not {typename}", .{ + .typename = value.get_typename(), + }); + } + + pub fn expect_symbol(self: *RuntimeEnv, value: *RuntimeObj) RuntimeError![*:0]const coral.io.Byte { + return value.is_symbol() orelse self.raise(error.TypeMismatch, "expected symbol type, not {typename}", .{ + .typename = value.get_typename(), + }); + } + + pub fn index_get(self: *RuntimeEnv, index: *RuntimeObj) RuntimeError!*RuntimeEnv { + const vector_max = 3; + const indexable = try self.expect_object(self.pop()); + + defer self.release(indexable); + + switch (indexable.internal().payload) { + .vector2 => |vector2| { + const swizzle_symbol = try self.expect_symbol(index); + var swizzle_buffer = [_]f32{0} ** vector_max; var swizzle_count = @as(usize, 0); while (true) : (swizzle_count += 1) { if (swizzle_count > swizzle_buffer.len) { - return null; + try self.locals.push_one(null); + + return self; } swizzle_buffer[swizzle_count] = switch (swizzle_symbol[swizzle_count]) { - 0 => break: swizzle switch (swizzle_count) { + 'x' => vector2[0], + 'y' => vector2[1], + + 0 => return 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, + else => return self.raise(error.BadOperation, "no such vector2 index or swizzle: `{index}`", .{ + .index = coral.io.slice_sentineled(@as(coral.io.Byte, 0), swizzle_symbol), + }), }; } }, - .vector3 => |vector3| swizzle: { - const swizzle_symbol = try self.unwrap_symbol(index); - var swizzle_buffer = [_]f32{0} ** 3; + .vector3 => |vector3| { + const swizzle_symbol = try self.expect_symbol(index); + var swizzle_buffer = [_]f32{0} ** vector_max; var swizzle_count = @as(usize, 0); while (true) : (swizzle_count += 1) { if (swizzle_count > swizzle_buffer.len) { - return null; + try self.locals.push_one(null); + + return self; } 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, + + 0 => return 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, + }, + + else => return self.raise(error.BadOperation, "no such vector3 index or swizzle: `{index}`", .{ + .index = coral.io.slice_sentineled(@as(coral.io.Byte, 0), swizzle_symbol), + }), }; } }, - .dynamic => |dynamic| dynamic.typeinfo().get(self, dynamic.userdata(), index), + .dynamic => |dynamic| { + const value = try dynamic.typeinfo().get(self, dynamic.userdata(), index); - else => self.raise(error.TypeMismatch, "{typename} is not get-indexable", .{ - .typename = indexable.typename(), + errdefer { + if (value) |object| { + self.release(object); + } + } + + try self.locals.push_one(value); + + return self; + }, + + else => return self.raise(error.TypeMismatch, "{typename} is not get-indexable", .{ + .typename = indexable.get_typename(), + }), + } + } + + pub fn index_set(self: *RuntimeEnv, index: *RuntimeObj, value: ?*RuntimeObj) RuntimeError!void { + const indexable = try self.expect_object(self.pop()); + + defer self.release(indexable); + + try switch (indexable.internal().payload) { + .dynamic => |dynamic| dynamic.typeinfo().set(self, dynamic.userdata(), index, value), + + else => self.raise(error.TypeMismatch, "{typename} is not set-indexable", .{ + .typename = indexable.get_typename(), }), }; } - pub fn get_boxed(self: *RuntimeEnv, boxable: *RuntimeRef) RuntimeError!?*RuntimeRef { - return switch (boxable.object().payload) { - .boxed => |boxed| if (boxed) |boxed_value| boxed_value.acquire() else null, - else => self.raise(error.TypeMismatch, "{typename} is not box-gettable", .{.typename = boxable.typename()}), - }; + pub fn import(self: *RuntimeEnv, file_path: file.Path) RuntimeError!*RuntimeEnv { + { + var callable = new_chunk: { + const file_name = file_path.get_string(); + + const file_data = + (try file.allocate_and_load(self.allocator, self.options.import_access, file_path)) orelse { + return self.raise(error.BadOperation, "failed to open or read `{name}`", .{ + .name = file_name, + }); + }; + + defer self.allocator.deallocate(file_data); + + var root = try tree.Root.init(self.allocator); + + defer root.deinit(); + + { + var stream = tokens.Stream{.source = file_data}; + + root.parse(&stream) catch |parse_error| { + for (root.error_messages.values) |error_message| { + self.print(error_message); + } + + return self.raise(parse_error, "failed to parse `{name}`", .{.name = file_name}); + }; + } + + var chunk = try Chunk.init(self, file_name, &root.environment); + + errdefer chunk.deinit(self); + + break: new_chunk (try self.new_dynamic(coral.io.bytes_of(&chunk), Chunk.typeinfo)).pop().?; + }; + + errdefer self.release(callable); + + try self.locals.push_one(callable); + } + + return self.call(0); } - pub fn import(self: *RuntimeEnv, file_path: file.Path) RuntimeError!?*RuntimeRef { - var callable = try self.new_dynamic(coral.io.bytes_of(&make_chunk: { - const file_name = file_path.get_string(); + /// + /// Attempts to initialize a new virtual machine instance with `allocator` as the allocation strategy, `frames_max` + /// as the maximum call depth, and `options` as the optional values to pass to the VM state. + /// + /// This function will fail if `allocator` cannot allocate the memory required to create the runtime environment. + /// + pub fn init(allocator: coral.io.Allocator, frames_max: u32, options: Options) coral.io.AllocationError!RuntimeEnv { + var frames = FrameList.init(allocator); - const file_data = - (try file.allocate_and_load(self.allocator, self.options.import_access, file_path)) orelse { - return self.raise(error.BadOperation, "failed to open or read `{name}`", .{ - .name = file_name, - }); - }; + errdefer frames.deinit(); - defer self.allocator.deallocate(file_data); + try frames.grow(frames_max); - var root = try tree.Root.make(self.allocator); + var locals = LocalList.init(allocator); - defer root.free(); + errdefer locals.deinit(); - { - var stream = tokens.Stream{.source = file_data}; + try locals.grow(local_max * frames_max); - root.parse(&stream) catch |parse_error| { - for (root.error_messages.values) |error_message| { - self.print(error_message); - } - - return parse_error; - }; - } - - break: make_chunk try Chunk.make(self, file_name, &root.environment); - }), Chunk.typeinfo); - - defer self.discard(callable); - - return self.call(callable, &.{}); - } - - pub fn make(allocator: coral.io.Allocator, options: Options) coral.io.AllocationError!RuntimeEnv { - return RuntimeEnv{ - .locals = LocalList.make(allocator), - .frames = FrameStack.make(allocator), - .interned_symbols = SymbolSet.make(allocator, .{}), - .options = options, + return .{ + .interned_symbols = SymbolSet.init(allocator, .{}), + .locals = locals, + .frames = frames, .allocator = allocator, + .options = options, }; } - pub fn mul(self: *RuntimeEnv, lhs: *const RuntimeRef, rhs: *const RuntimeRef) RuntimeError!*RuntimeRef { - return switch (lhs.object().payload) { - .fixed => |lhs_fixed| switch (rhs.object().payload) { - .fixed => |rhs_fixed| mul_fixed: { - if (coral.math.checked_mul(lhs_fixed, rhs_fixed)) |fixed| { - break: mul_fixed self.new_fixed(fixed); - } + const LocalIndex = u8; - break: mul_fixed self.new_float(@as(Float, @floatFromInt(lhs_fixed)) * @as(Float, @floatFromInt(rhs_fixed))); - }, + const local_max = coral.math.max_int(@typeInfo(LocalIndex).Int); - .float => |rhs_float| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) * rhs_float), + pub fn local_get(self: *RuntimeEnv, index: LocalIndex) RuntimeError!*RuntimeEnv { + const frame = &self.frames.values[self.frames.values.len - 1]; - else => self.raise(error.TypeMismatch, "right-hand {typename} is not multiplicable", .{ - .typename = rhs.typename(), - }), + return self.push(self.locals.values[frame.locals_top + index]); + } + + pub fn local_set(self: *RuntimeEnv, index: LocalIndex, value: ?*RuntimeObj) void { + const frame = &self.frames.values[self.frames.values.len - 1]; + const local = &self.locals.values[frame.locals_top + index]; + + if (local.*) |previous_local| { + self.release(previous_local); + } + + local.* = if (value) |object| object.acquire() else null; + } + + pub fn local_count(self: *RuntimeEnv) LocalIndex { + const frame = &self.frames.values[self.frames.values.len - 1]; + + return @intCast(self.locals.values[frame.locals_top ..].len); + } + + pub fn mul_fixed(self: *RuntimeEnv, rhs_fixed: Fixed) RuntimeError!*RuntimeEnv { + const addable = try self.expect_object(self.pop()); + + defer self.release(addable); + + return switch (addable.internal().payload) { + .fixed => |lhs_fixed| if (coral.math.checked_mul(lhs_fixed, rhs_fixed)) |result| + self.new_fixed(result) + else + self.new_float(@as(Float, @floatFromInt(lhs_fixed)) * @as(Float, @floatFromInt(rhs_fixed))), + + .float => |lhs_float| self.new_float(lhs_float + @as(Float, @floatFromInt(rhs_fixed))), + + .vector2 => |lhs_vector2| new: { + const rhs_scalar = @as(f32, @floatFromInt(rhs_fixed)); + + break: new self.new_vector2(lhs_vector2[0] * rhs_scalar, lhs_vector2[1] * rhs_scalar); }, - .float => |lhs_float| switch (rhs.object().payload) { - .float => |rhs_float| self.new_float(lhs_float * rhs_float), - .fixed => |rhs_fixed| self.new_float(lhs_float * @as(Float, @floatFromInt(rhs_fixed))), + .vector3 => |lhs_vector3| new: { + const rhs_scalar = @as(f32, @floatFromInt(rhs_fixed)); - else => self.raise(error.TypeMismatch, "right-hand {typename} is not multiplicable", .{ - .typename = rhs.typename(), - }), + break: new self.new_vector3(lhs_vector3[0] * rhs_scalar, lhs_vector3[1] * rhs_scalar, lhs_vector3[2] * rhs_scalar); }, - else => self.raise(error.TypeMismatch, "left-hand {typename} is not multiplicable", .{ - .typename = lhs.typename(), + else => self.raise(error.TypeMismatch, "fixed types are not multiplicable with {typename}", .{ + .typename = addable.get_typename(), }), }; } - pub fn neg(self: *RuntimeEnv, value: *const RuntimeRef) RuntimeError!*RuntimeRef { - return switch (value.object().payload) { + pub fn mul_float(self: *RuntimeEnv, rhs_float: Float) RuntimeError!*RuntimeEnv { + const addable = try self.expect_object(self.pop()); + + defer self.release(addable); + + return switch (addable.internal().payload) { + .fixed => |lhs_fixed| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) * rhs_float), + .float => |lhs_float| self.new_float(lhs_float * lhs_float), + + .vector2 => |lhs_vector2| new: { + const rhs_scalar = @as(f32, @floatCast(rhs_float)); + + break: new self.new_vector2(lhs_vector2[0] * rhs_scalar, lhs_vector2[1] * rhs_scalar); + }, + + .vector3 => |lhs_vector3| new: { + const rhs_scalar = @as(f32, @floatCast(rhs_float)); + + break: new self.new_vector3(lhs_vector3[0] * rhs_scalar, lhs_vector3[1] + rhs_scalar, lhs_vector3[2] * rhs_scalar); + }, + + else => self.raise(error.TypeMismatch, "fixed types are not multiplicable with {typename}", .{ + .typename = addable.get_typename(), + }), + }; + } + + pub fn mul_vector2(self: *RuntimeEnv, rhs_vector2: Vector2) RuntimeError!*RuntimeEnv { + const addable = try self.expect_object(self.pop()); + + defer self.release(addable); + + return if (addable.is_vector2()) |lhs_vector2| + self.new_vector2(lhs_vector2[0] * rhs_vector2[0], lhs_vector2[1] * rhs_vector2[1]) + else + self.raise(error.TypeMismatch, "vector2 types are not multiplicable with {typename}", .{ + .typename = addable.get_typename(), + }); + } + + pub fn mul_vector3(self: *RuntimeEnv, rhs_vector3: Vector3) RuntimeError!*RuntimeEnv { + const addable = try self.expect_object(self.pop()); + + defer self.release(addable); + + return if (addable.is_vector3()) |lhs_vector3| + self.new_vector3(lhs_vector3[0] * rhs_vector3[0], lhs_vector3[1] * rhs_vector3[1], lhs_vector3[2] * rhs_vector3[2]) + else + self.raise(error.TypeMismatch, "vector3 types are not multiplicable with {typename}", .{ + .typename = addable.get_typename(), + }); + } + + pub fn neg(self: *RuntimeEnv) RuntimeError!*RuntimeEnv { + const negatable = try self.expect_object(self.pop()); + + defer self.release(negatable); + + return switch (negatable.internal().payload) { .fixed => |fixed| self.new_fixed(-fixed), .float => |float| self.new_float(-float), - else => self.raise(error.TypeMismatch, "{typename} is not negatable", .{.typename = value.typename()}), + + else => self.raise(error.TypeMismatch, "{typename} is not scalar negatable", .{ + .typename = negatable.get_typename(), + }), }; } - pub fn new_boolean(self: *RuntimeEnv, value: bool) coral.io.AllocationError!*RuntimeRef { - return RuntimeRef.allocate(self.allocator, .{ + pub fn new_boolean(self: *RuntimeEnv, value: bool) RuntimeError!*RuntimeEnv { + const boolean = try RuntimeObj.allocate(self.allocator, .{ .ref_count = 1, .payload = if (value) .true else .false, }); + + errdefer self.release(boolean); + + try self.locals.push_one(boolean); + + return self; } - pub fn new_boxed(self: *RuntimeEnv, value: ?*const RuntimeRef) coral.io.AllocationError!*RuntimeRef { - return RuntimeRef.allocate(self.allocator, .{ + pub fn new_boxed(self: *RuntimeEnv, value: ?*RuntimeObj) RuntimeError!*RuntimeEnv { + const boxed = try RuntimeObj.allocate(self.allocator, .{ .ref_count = 1, .payload = .{.boxed = if (value) |ref| ref.acquire() else null}, }); + + errdefer self.release(boxed); + + try self.locals.push_one(boxed); + + return self; } - pub fn new_dynamic( - self: *RuntimeEnv, - userdata: []const coral.io.Byte, - typeinfo: *const Typeinfo, - ) coral.io.AllocationError!*RuntimeRef { + pub fn new_dynamic(self: *RuntimeEnv, userdata: []const coral.io.Byte, typeinfo: *const Typeinfo) RuntimeError!*RuntimeEnv { coral.debug.assert(userdata.len == typeinfo.size); - const dynamic = try self.allocator.reallocate(null, @sizeOf(usize) + typeinfo.size); + const dynamic = new: { + const dynamic = try self.allocator.reallocate(null, @sizeOf(usize) + typeinfo.size); - errdefer self.allocator.deallocate(dynamic); + errdefer self.allocator.deallocate(dynamic); - coral.io.copy(dynamic, coral.io.bytes_of(&typeinfo)); - coral.io.copy(dynamic[@sizeOf(usize) ..], userdata[0 .. typeinfo.size]); + 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}}, - }); + break: new try RuntimeObj.allocate(self.allocator, .{ + .ref_count = 1, + .payload = .{.dynamic = .{.ptr = dynamic.ptr}}, + }); + }; + + errdefer self.release(dynamic); + + try self.locals.push_one(dynamic); + + return self; } - pub fn new_fixed(self: *RuntimeEnv, value: Fixed) coral.io.AllocationError!*RuntimeRef { - return RuntimeRef.allocate(self.allocator, .{ + pub fn new_fixed(self: *RuntimeEnv, value: Fixed) RuntimeError!*RuntimeEnv { + const syscall = try RuntimeObj.allocate(self.allocator, .{ .ref_count = 1, .payload = .{.fixed = value}, }); + + errdefer self.release(syscall); + + try self.locals.push_one(syscall); + + return self; } - pub fn new_float(self: *RuntimeEnv, value: Float) coral.io.AllocationError!*RuntimeRef { - return RuntimeRef.allocate(self.allocator, .{ + pub fn new_float(self: *RuntimeEnv, value: Float) RuntimeError!*RuntimeEnv { + const syscall = try RuntimeObj.allocate(self.allocator, .{ .ref_count = 1, .payload = .{.float = value}, }); + + errdefer self.release(syscall); + + try self.locals.push_one(syscall); + + return self; } - pub fn new_string(self: *RuntimeEnv, value: []const coral.io.Byte) coral.io.AllocationError!*RuntimeRef { + pub fn new_string(self: *RuntimeEnv, value: []const coral.io.Byte) RuntimeError!*RuntimeEnv { if (value.len > coral.math.max_int(@typeInfo(Fixed).Int)) { return error.OutOfMemory; } - const string = try coral.io.allocate_copy(coral.io.Byte, self.allocator, value); + const string = new: { + const string = try coral.io.allocate_copy(coral.io.Byte, self.allocator, value); - errdefer self.allocator.deallocate(string); + errdefer self.allocator.deallocate(string); - return RuntimeRef.allocate(self.allocator, .{ - .ref_count = 1, - - .payload = .{ - .string = .{ - .ptr = string.ptr, - .len = @intCast(string.len), + break: new try RuntimeObj.allocate(self.allocator, .{ + .payload = .{ + .string = .{ + .ptr = string.ptr, + .len = @intCast(string.len), + }, }, - }, - }); + + .ref_count = 1, + }); + }; + + errdefer self.release(string); + + try self.locals.push_one(string); + + return self; } - pub fn new_symbol(self: *RuntimeEnv, value: []const coral.io.Byte) coral.io.AllocationError!*RuntimeRef { - return RuntimeRef.allocate(self.allocator, .{ + pub fn new_syscall(self: *RuntimeEnv, value: *const Syscall) RuntimeError!*RuntimeEnv { + const syscall = try RuntimeObj.allocate(self.allocator, .{ .ref_count = 1, + .payload = .{.syscall = value}, + }); + errdefer self.release(syscall); + + try self.locals.push_one(syscall); + + return self; + } + + pub fn new_table(self: *RuntimeEnv) RuntimeError!*RuntimeEnv { + var table = Table.init(self); + + errdefer table.deinit(self); + + return self.new_dynamic(coral.io.bytes_of(&table), Table.typeinfo); + } + + pub fn new_vector2(self: *RuntimeEnv, x: f32, y: f32) RuntimeError!*RuntimeEnv { + const vector2 = try RuntimeObj.allocate(self.allocator, .{ + .ref_count = 1, + .payload = .{.vector2 = .{x, y}}, + }); + + errdefer self.release(vector2); + + try self.locals.push_one(vector2); + + return self; + } + + pub fn new_vector3(self: *RuntimeEnv, x: f32, y: f32, z: f32) RuntimeError!*RuntimeEnv { + const vector3 = try RuntimeObj.allocate(self.allocator, .{ + .ref_count = 1, + .payload = .{.vector3 = .{x, y, z}}, + }); + + errdefer self.release(vector3); + + try self.locals.push_one(vector3); + + return self; + } + + pub fn new_symbol(self: *RuntimeEnv, value: []const coral.io.Byte) RuntimeError!*RuntimeEnv { + const symbol = try RuntimeObj.allocate(self.allocator, .{ .payload = .{ .symbol = self.interned_symbols.lookup(value) orelse create: { const symbol_string = try coral.io.allocate_string(self.allocator, value); @@ -532,97 +1036,49 @@ pub const RuntimeEnv = struct { break: create symbol_string; }, }, - }); - } - pub fn new_syscall(self: *RuntimeEnv, value: *const Syscall) coral.io.AllocationError!*RuntimeRef { - return RuntimeRef.allocate(self.allocator, .{ .ref_count = 1, - .payload = .{.syscall = value}, }); + + errdefer self.release(symbol); + + try self.locals.push_one(symbol); + + return self; } - pub fn new_table(self: *RuntimeEnv) coral.io.AllocationError!*RuntimeRef { - var table = Table.make(self); + pub fn pop(self: *RuntimeEnv) ?*RuntimeObj { + const popped = self.locals.pop(); - errdefer table.free(self); + coral.debug.assert(popped != null); - return try self.new_dynamic(coral.io.bytes_of(&table), Table.typeinfo); - } - - pub fn new_vector2(self: *RuntimeEnv, x: f32, y: f32) coral.io.AllocationError!*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) coral.io.AllocationError!*RuntimeRef { - return RuntimeRef.allocate(self.allocator, .{ - .ref_count = 1, - .payload = .{.vector3 = .{x, y, z}}, - }); + return popped.?; } pub fn print(self: *RuntimeEnv, buffer: []const coral.io.Byte) void { - if (self.options.print) |bound_print| { - bound_print(buffer); + if (self.options.print) |op| { + op(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); + if (self.options.print_error) |op| { + op(buffer); } } - pub fn pop_frame(self: *RuntimeEnv) void { - const popped_frame = self.frames.pop(); + pub fn push(self: *RuntimeEnv, value: ?*RuntimeObj) RuntimeError!*RuntimeEnv { + if (value) |object| { + const acquired = object.acquire(); - coral.debug.assert(popped_frame != null); - self.discard(popped_frame.?.callable); + errdefer self.release(acquired); - var locals_to_pop = self.locals.values.len - popped_frame.?.locals_top; - - while (locals_to_pop != 0) { - const popped_local = self.locals.pop(); - - coral.debug.assert(popped_local != null); - - if (popped_local.?) |local| { - self.discard(local); - } - - locals_to_pop -= 1; - } - } - - pub fn pop_local(self: *RuntimeEnv) RuntimeError!?*RuntimeRef { - return self.locals.pop() orelse self.raise(error.IllegalState, "stack underflow", .{}); - } - - pub fn push_frame(self: *RuntimeEnv, callable: *const RuntimeRef, arg_count: u8) RuntimeError!Frame { - if (callable.as_dynamic(Chunk.typeinfo)) |chunk_userdata| { - const arity = @as(*Chunk, @ptrCast(@alignCast(chunk_userdata))).arity; - - if (arg_count < arity) { - return self.raise(error.BadOperation, "expected `{expected}` {noun}, {provided} provided", .{ - .expected = arg_count, - .provided = arity, - .noun = if (arity == 1) "argument" else "arguments", - }); - } + try self.locals.push_one(acquired); + } else { + try self.locals.push_one(null); } - const frame = Frame{ - .callable = callable.acquire(), - .arg_count = arg_count, - .locals_top = self.locals.values.len - arg_count, - }; - - try self.frames.push_one(frame); - - return frame; + return self; } pub fn raise(self: *RuntimeEnv, error_value: RuntimeError, comptime format: []const coral.io.Byte, args: anytype) RuntimeError { @@ -641,17 +1097,17 @@ pub const RuntimeEnv = struct { remaining_frames -= 1; const callable = self.frames.values[remaining_frames].callable; - const name = try self.to_string(callable); + const name = (try (try self.push(callable)).to_string()).pop().?; - defer self.discard(name); + defer self.release(name); - if (callable.as_dynamic(Chunk.typeinfo)) |chunk_userdata| { + if (callable.is_dynamic(Chunk.typeinfo)) |chunk_userdata| { const chunk = @as(*Chunk, @ptrCast(@alignCast(chunk_userdata))); const line = chunk.lines.values[chunk.cursor]; const chunk_name = try coral.utf8.alloc_formatted(self.allocator, "{name}@{line_number}", .{ .name = get_name: { - const string = name.as_string(); + const string = name.is_string(); coral.debug.assert(string != null); @@ -666,7 +1122,7 @@ pub const RuntimeEnv = struct { self.print_error(chunk_name); } else { self.print_error(get_name: { - const string = name.as_string(); + const string = name.is_string(); coral.debug.assert(string != null); @@ -679,69 +1135,130 @@ pub const RuntimeEnv = struct { return error_value; } - pub fn set(self: *RuntimeEnv, indexable: *RuntimeRef, index: *const RuntimeRef, value: ?*const RuntimeRef) RuntimeError!void { - return switch (indexable.object().payload) { - .dynamic => |dynamic| dynamic.typeinfo().set(self, dynamic.userdata(), index, value), + pub fn release(self: *RuntimeEnv, value: *RuntimeObj) void { + coral.debug.assert(value.internal().ref_count != 0); - else => self.raise(error.TypeMismatch, "{typename} is not set-indexable", .{ - .typename = indexable.typename(), - }), - }; - } + value.internal().ref_count -= 1; - pub fn set_boxed(self: *RuntimeEnv, boxable: *RuntimeRef, value: ?*const RuntimeRef) RuntimeError!void { - switch (boxable.object().payload) { - .boxed => |*boxed| { - if (boxed.*) |unboxed_value| { - self.discard(unboxed_value); - } + if (value.internal().ref_count == 0) { + switch (value.internal().payload) { + .false, .true, .float, .fixed, .symbol, .vector2, .vector3, .syscall => {}, - boxed.* = if (value) |ref| ref.acquire() else null; - }, + .boxed => |*boxed| { + if (boxed.*) |boxed_value| { + self.release(boxed_value); + } + }, - else => return self.raise(error.TypeMismatch, "{typename} is not box-settable", .{ - .typename = boxable.typename(), - }), + .string => |string| { + coral.debug.assert(string.len >= 0); + self.allocator.deallocate(string.ptr[0 .. @intCast(string.len)]); + }, + + .dynamic => |dynamic| { + if (dynamic.typeinfo().destruct) |destruct| { + destruct(self, dynamic.userdata()); + } + + self.allocator.deallocate(dynamic.unpack()); + }, + } + + self.allocator.deallocate(value.internal()); } } - pub fn sub(self: *RuntimeEnv, lhs: *const RuntimeRef, rhs: *const RuntimeRef) RuntimeError!*RuntimeRef { - return switch (lhs.object().payload) { - .fixed => |lhs_fixed| switch (rhs.object().payload) { - .fixed => |rhs_fixed| sub_fixed: { - if (coral.math.checked_sub(lhs_fixed, rhs_fixed)) |fixed| { - break: sub_fixed self.new_fixed(fixed); - } + pub fn sub_fixed(self: *RuntimeEnv, rhs_fixed: Fixed) RuntimeError!*RuntimeEnv { + const addable = try self.expect_object(self.pop()); - break: sub_fixed self.new_float(@as(Float, @floatFromInt(lhs_fixed)) - @as(Float, @floatFromInt(rhs_fixed))); - }, + defer self.release(addable); - .float => |rhs_float| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) - rhs_float), + return switch (addable.internal().payload) { + .fixed => |lhs_fixed| if (coral.math.checked_sub(lhs_fixed, rhs_fixed)) |result| + self.new_fixed(result) + else + self.new_float(@as(Float, @floatFromInt(lhs_fixed)) - @as(Float, @floatFromInt(rhs_fixed))), - else => self.raise(error.TypeMismatch, "right-hand {typename} is not subtractable", .{ - .typename = rhs.typename(), - }), + .float => |lhs_float| self.new_float(lhs_float + @as(Float, @floatFromInt(rhs_fixed))), + + .vector2 => |lhs_vector2| new: { + const rhs_scalar = @as(f32, @floatFromInt(rhs_fixed)); + + break: new self.new_vector2(lhs_vector2[0] - rhs_scalar, lhs_vector2[1] - rhs_scalar); }, - .float => |lhs_float| switch (rhs.object().payload) { - .float => |rhs_float| self.new_float(lhs_float - rhs_float), - .fixed => |rhs_fixed| self.new_float(lhs_float - @as(Float, @floatFromInt(rhs_fixed))), + .vector3 => |lhs_vector3| new: { + const rhs_scalar = @as(f32, @floatFromInt(rhs_fixed)); - else => self.raise(error.TypeMismatch, "right-hand {typename} is not subtractable", .{ - .typename = rhs.typename(), - }), + break: new self.new_vector3(lhs_vector3[0] - rhs_scalar, lhs_vector3[1] - rhs_scalar, lhs_vector3[2] - rhs_scalar); }, - else => self.raise(error.TypeMismatch, "left-hand {typename} is not subtractable", .{ - .typename = lhs.typename(), + else => self.raise(error.TypeMismatch, "fixed types are not multiplicable with {typename}", .{ + .typename = addable.get_typename(), }), }; } - pub fn to_string(self: *RuntimeEnv, value: *const RuntimeRef) coral.io.AllocationError!*RuntimeRef { - const decimal_format = coral.utf8.DecimalFormat.default; + pub fn sub_float(self: *RuntimeEnv, rhs_float: Float) RuntimeError!*RuntimeEnv { + const addable = try self.expect_object(self.pop()); - return switch (value.object().payload) { + defer self.release(addable); + + return switch (addable.internal().payload) { + .fixed => |lhs_fixed| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) - rhs_float), + .float => |lhs_float| self.new_float(lhs_float - lhs_float), + + .vector2 => |lhs_vector2| new: { + const rhs_scalar = @as(f32, @floatCast(rhs_float)); + + break: new self.new_vector2(lhs_vector2[0] - rhs_scalar, lhs_vector2[1] - rhs_scalar); + }, + + .vector3 => |lhs_vector3| new: { + const rhs_scalar = @as(f32, @floatCast(rhs_float)); + + break: new self.new_vector3(lhs_vector3[0] - rhs_scalar, lhs_vector3[1] - rhs_scalar, lhs_vector3[2] - rhs_scalar); + }, + + else => self.raise(error.TypeMismatch, "fixed types are not multiplicable with {typename}", .{ + .typename = addable.get_typename(), + }), + }; + } + + pub fn sub_vector2(self: *RuntimeEnv, rhs_vector2: Vector2) RuntimeError!*RuntimeEnv { + const addable = try self.expect_object(self.pop()); + + defer self.release(addable); + + return if (addable.is_vector2()) |lhs_vector2| + self.new_vector2(lhs_vector2[0] - rhs_vector2[0], lhs_vector2[1] - rhs_vector2[1]) + else + self.raise(error.TypeMismatch, "vector2 types are not multiplicable with {typename}", .{ + .typename = addable.get_typename(), + }); + } + + pub fn sub_vector3(self: *RuntimeEnv, rhs_vector3: Vector3) RuntimeError!*RuntimeEnv { + const addable = try self.expect_object(self.pop()); + + defer self.release(addable); + + return if (addable.is_vector3()) |lhs_vector3| + self.new_vector3(lhs_vector3[0] - rhs_vector3[0], lhs_vector3[1] - rhs_vector3[1], lhs_vector3[2] - rhs_vector3[2]) + else + self.raise(error.TypeMismatch, "vector3 types are not multiplicable with {typename}", .{ + .typename = addable.get_typename(), + }); + } + + pub fn to_string(self: *RuntimeEnv) RuntimeError!*RuntimeEnv { + const decimal_format = coral.utf8.DecimalFormat.default; + const value = try self.expect_object(self.pop()); + + defer self.release(value); + + return switch (value.internal().payload) { .false => self.new_string("false"), .true => self.new_string("true"), @@ -765,9 +1282,14 @@ pub const RuntimeEnv = struct { break: convert self.new_string(string[0 .. length.?]); }, - .boxed => |boxed| if (boxed) |boxed_value| self.to_string(boxed_value) else self.new_string("nil"), + .boxed => |boxed| (try self.push(try self.expect_object(boxed))).to_string(), .symbol => |symbol| self.new_string(coral.io.slice_sentineled(@as(coral.io.Byte, 0), symbol)), - .string => value.acquire(), + + .string => acquire: { + try self.locals.push_one(value.acquire()); + + break: acquire self; + }, .vector2 => |vector2| convert: { var string = [_:0]coral.io.Byte{0} ** 64; @@ -799,40 +1321,51 @@ pub const RuntimeEnv = struct { }, .syscall => self.new_string("syscall"), - .dynamic => |dynamic| dynamic.typeinfo().to_string(self, dynamic.userdata()), + + .dynamic => |dynamic| dynamic_to_string: { + const string = try dynamic.typeinfo().to_string(self, dynamic.userdata()); + + errdefer self.release(string); + + try self.locals.push_one(string); + + break: dynamic_to_string self; + }, }; } - pub fn unwrap_dynamic(self: *RuntimeEnv, value: *const RuntimeRef, typeinfo: *const Typeinfo) RuntimeError![]coral.io.Byte { - return value.as_dynamic(typeinfo) orelse self.raise(error.TypeMismatch, "expected dynamic object, not {typename}", .{ - .typename = value.typename(), - }); - } + pub fn boxed_get(self: *RuntimeEnv) RuntimeError!*RuntimeEnv { + const unboxable = try self.expect_object(self.pop()); - pub fn unwrap_float(self: *RuntimeEnv, value: *const RuntimeRef) RuntimeError!Float { - return switch (value.object().payload) { - .fixed => |fixed| @floatFromInt(fixed), - .float => |float| float, - else => self.raise(error.TypeMismatch, "expected float, not {typename}", .{.typename = value.typename()}), + defer self.release(unboxable); + + return switch (unboxable.internal().payload) { + .boxed => |boxed| self.push(boxed), + + else => self.raise(error.TypeMismatch, "{typename} is not unboxable", .{ + .typename = unboxable.get_typename(), + }), }; } - pub fn unwrap_fixed(self: *RuntimeEnv, value: *const RuntimeRef) RuntimeError!Fixed { - return value.as_fixed() orelse self.raise(error.TypeMismatch, "expected fixed, not {typename}", .{ - .typename = value.typename(), - }); - } + pub fn boxed_set(self: *RuntimeEnv, value: ?*RuntimeObj) RuntimeError!void { + const unboxable = try self.expect_object(self.pop()); - pub fn unwrap_string(self: *RuntimeEnv, value: *const RuntimeRef) RuntimeError![]const coral.io.Byte { - return value.as_string() orelse self.raise(error.TypeMismatch, "expected string, not {typename}", .{ - .typename = value.typename(), - }); - } + defer self.release(unboxable); - pub fn unwrap_symbol(self: *RuntimeEnv, value: *const RuntimeRef) RuntimeError![*:0]const coral.io.Byte { - return value.as_symbol() orelse self.raise(error.TypeMismatch, "expected symbol, not {typename}", .{ - .typename = value.typename(), - }); + switch (unboxable.internal().payload) { + .boxed => |*unboxed_value| { + if (unboxed_value.*) |unboxed_object| { + self.release(unboxed_object); + } + + unboxed_value.* = if (value) |object| object.acquire() else null; + }, + + else => return self.raise(error.TypeMismatch, "{typename} is not unboxable", .{ + .typename = unboxable.get_typename(), + }), + } } }; @@ -843,8 +1376,8 @@ pub const RuntimeError = coral.io.AllocationError || error { BadSyntax, }; -pub const RuntimeRef = opaque { - const Object = struct { +pub const RuntimeObj = opaque { + const Internal = struct { ref_count: u16, payload: union (enum) { @@ -853,10 +1386,10 @@ pub const RuntimeRef = opaque { float: Float, fixed: Fixed, symbol: [*:0]const coral.io.Byte, - vector2: [2]f32, - vector3: [3]f32, + vector2: Vector2, + vector3: Vector3, syscall: *const Syscall, - boxed: ?*RuntimeRef, + boxed: ?*RuntimeObj, string: struct { ptr: [*]coral.io.Byte, @@ -896,122 +1429,78 @@ pub const RuntimeRef = opaque { }, }; - pub fn acquire(self: *const RuntimeRef) *RuntimeRef { - const self_object = self.object(); + pub fn acquire(self: *const RuntimeObj) *RuntimeObj { + const self_object = self.internal(); self_object.ref_count += 1; - return @ptrCast(self_object); + return@ptrCast(self_object); } - fn allocate(allocator: coral.io.Allocator, data: Object) coral.io.AllocationError!*RuntimeRef { + fn allocate(allocator: coral.io.Allocator, data: Internal) coral.io.AllocationError!*RuntimeObj { return @ptrCast(try coral.io.allocate_one(allocator, data)); } - pub fn as_dynamic(self: *const RuntimeRef, typeinfo: *const Typeinfo) ?[]u8 { - return switch (self.object().payload) { - .dynamic => |dynamic| if (dynamic.typeinfo() == typeinfo) dynamic.userdata() else null, - else => null, - }; - } - - pub fn as_fixed(self: *const RuntimeRef) ?Fixed { - return switch (self.object().payload) { - .fixed => |fixed| fixed, - else => null, - }; - } - - pub fn as_string(self: *const RuntimeRef) ?[]const coral.io.Byte { - return switch (self.object().payload) { - .string => |string| get: { - coral.debug.assert(string.len > -1); - - break: get string.ptr[0 .. @intCast(string.len)]; - }, - - .symbol => |symbol| coral.io.slice_sentineled(@as(u8, 0), symbol), - else => null, - }; - } - - pub fn as_symbol(self: *const RuntimeRef) ?[*:0]const coral.io.Byte { - return switch (self.object().payload) { - .symbol => |symbol| symbol, - else => null, - }; - } - - fn object(self: *const RuntimeRef) *Object { + fn internal(self: *const RuntimeObj) *Internal { 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, + pub fn equals(self: *RuntimeObj, other: *RuntimeObj) bool { + return switch (self.internal().payload) { + .false => other.internal().payload == .false, + .true => other.internal().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)), + .fixed => |lhs_fixed| switch (other.internal().payload) { + .fixed => |rhs_fixed| rhs_fixed == lhs_fixed, + .float => |rhs_float| rhs_float == @as(Float, @floatFromInt(lhs_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, + .float => |lhs_float| switch (other.internal().payload) { + .float => |rhs_float| rhs_float == lhs_float, + .fixed => |rhs_fixed| @as(Float, @floatFromInt(rhs_fixed)) == lhs_float, else => false, }, - .symbol => |self_symbol| switch (other.object().payload) { - .symbol => |other_symbol| self_symbol == other_symbol, + .symbol => |lhs_symbol| switch (other.internal().payload) { + .symbol => |rhs_symbol| lhs_symbol == rhs_symbol, else => false, }, - .boxed => |boxed| unbox: { - if (boxed) |boxed_value| { - break: unbox boxed_value.equals(other); - } + .boxed => |boxed| if (boxed) |object| self.equals(object) else false, - if (other.object().payload == .boxed) { - break: unbox other.object().payload.boxed == null; - } - - break: unbox false; - }, - - .vector2 => |self_vector| switch (other.object().payload) { - .vector2 => |other_vector| coral.io.are_equal(coral.io.bytes_of(&self_vector), coral.io.bytes_of(&other_vector)), + .vector2 => |lhs_vector| switch (other.internal().payload) { + .vector2 => |rhs_vector| lhs_vector[0] == rhs_vector[0] and lhs_vector[1] == rhs_vector[1], 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)), + .vector3 => |lhs_vector| switch (other.internal().payload) { + .vector3 => |rhs_vector| lhs_vector[0] == rhs_vector[0] and lhs_vector[1] == rhs_vector[1] and lhs_vector[2] == rhs_vector[2], else => false, }, - .syscall => |self_syscall| switch (other.object().payload) { - .syscall => |other_syscall| self_syscall == other_syscall, + .syscall => |lhs_syscall| switch (other.internal().payload) { + .syscall => |rhs_syscall| lhs_syscall == rhs_syscall, else => false, }, - .string => |self_string| switch (other.object().payload) { - .string => |other_string| coral.io.are_equal(self_string.unpack(), other_string.unpack()), + .string => |lhs_string| switch (other.internal().payload) { + .string => |rhs_string| coral.io.are_equal(lhs_string.unpack(), rhs_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, + .dynamic => |lhs_dynamic| switch (other.internal().payload) { + .dynamic => |rhs_dynamic| + lhs_dynamic.typeinfo() == rhs_dynamic.typeinfo() and + lhs_dynamic.userdata().ptr == rhs_dynamic.userdata().ptr, else => false, }, }; } - pub fn hash(self: *const RuntimeRef) usize { - return switch (self.object().payload) { + pub fn get_hash(self: *RuntimeObj) usize { + return switch (self.internal().payload) { .false => 1237, .true => 1231, .float => |float| @bitCast(float), @@ -1026,8 +1515,72 @@ pub const RuntimeRef = opaque { }; } - pub fn is_truthy(self: *const RuntimeRef) bool { - return switch (self.object().payload) { + pub fn get_typename(self: *RuntimeObj) []const coral.io.Byte { + return switch (self.internal().payload) { + .false => "false", + .true => "true", + .float => "float", + .fixed => "fixed", + .symbol => "symbol", + .vector2 => "vector2", + .vector3 => "vector3", + .syscall => "syscall", + .boxed => "boxed", + .string => "string", + .dynamic => "dynamic", + }; + } + + pub fn is_dynamic(self: *RuntimeObj, typeinfo: *const Typeinfo) ?[]u8 { + return switch (self.internal().payload) { + .dynamic => |dynamic| if (dynamic.typeinfo() == typeinfo) dynamic.userdata() else null, + else => null, + }; + } + + pub fn is_false(self: *RuntimeObj) bool { + return !self.is_true(); + } + + pub fn is_fixed(self: *RuntimeObj) ?Fixed { + return switch (self.internal().payload) { + .fixed => |fixed| fixed, + else => null, + }; + } + + pub fn is_numeric(self: *RuntimeObj) ?Numeric { + return switch (self.internal().payload) { + .fixed => |fixed| .{.fixed = fixed}, + .float => |float| .{.float = float}, + .vector2 => |vector2| .{.vector2 = vector2}, + .vector3 => |vector3| .{.vector3 = vector3}, + else => null, + }; + } + + pub fn is_string(self: *RuntimeObj) ?[]const coral.io.Byte { + return switch (self.internal().payload) { + .string => |string| get: { + coral.debug.assert(string.len > -1); + + break: get string.ptr[0 .. @intCast(string.len)]; + }, + + .symbol => |symbol| coral.io.slice_sentineled(@as(u8, 0), symbol), + else => null, + }; + } + + pub fn is_symbol(self: *RuntimeObj) ?[*:0]const coral.io.Byte { + return switch (self.internal().payload) { + .symbol => |symbol| symbol, + else => null, + }; + } + + pub fn is_true(self: *RuntimeObj) bool { + return switch (self.internal().payload) { .false => false, .true => true, .float => |float| float != 0, @@ -1042,79 +1595,73 @@ pub const RuntimeRef = opaque { }; } - pub fn typename(self: *const RuntimeRef) []const coral.io.Byte { - return switch (self.object().payload) { - .false => "false", - .true => "true", - .float => "float", - .fixed => "fixed", - .symbol => "symbol", - .vector2 => "vector2", - .vector3 => "vector3", - .syscall => "syscall", - .boxed => "boxed", - .string => "string", - .dynamic => "dynamic", + pub fn is_vector2(self: *RuntimeObj) ?Vector2 { + return switch (self.internal().payload) { + .vector2 => |vector2| vector2, + else => null, + }; + } + + pub fn is_vector3(self: *RuntimeObj) ?Vector3 { + return switch (self.internal().payload) { + .vector3 => |vector3| vector3, + else => null, }; } }; -pub const Syscall = fn (env: *RuntimeEnv, frame: *const Frame) RuntimeError!?*RuntimeRef; +pub const Syscall = fn (env: *RuntimeEnv) RuntimeError!?*RuntimeObj; pub const Typeinfo = struct { name: []const coral.io.Byte, size: usize, destruct: ?*const fn (env: *RuntimeEnv, userdata: []coral.io.Byte) void = null, - to_string: *const fn (env: *RuntimeEnv, userdata: []coral.io.Byte) coral.io.AllocationError!*RuntimeRef = default_to_string, - call: *const fn (env: *RuntimeEnv, userdata: []coral.io.Byte, frame: *const Frame) RuntimeError!?*RuntimeRef = default_call, - get: *const fn (env: *RuntimeEnv, userdata: []coral.io.Byte, index: *const RuntimeRef) RuntimeError!?*RuntimeRef = default_get, - set: *const fn (env: *RuntimeEnv, userdata: []coral.io.Byte, value: *const RuntimeRef, value: ?*const RuntimeRef) RuntimeError!void = default_set, + to_string: *const fn (env: *RuntimeEnv, userdata: []coral.io.Byte) RuntimeError!*RuntimeObj = default_to_string, + call: *const fn (env: *RuntimeEnv, userdata: []coral.io.Byte) RuntimeError!?*RuntimeObj = default_call, + get: *const fn (env: *RuntimeEnv, userdata: []coral.io.Byte, index: *RuntimeObj) RuntimeError!?*RuntimeObj = default_get, + set: *const fn (env: *RuntimeEnv, userdata: []coral.io.Byte, value: *RuntimeObj, value: ?*RuntimeObj) RuntimeError!void = default_set, - fn default_call(env: *RuntimeEnv, _: []coral.io.Byte, _: *const Frame) RuntimeError!?*RuntimeRef { + fn default_call(env: *RuntimeEnv, _: []coral.io.Byte) RuntimeError!?*RuntimeObj { return env.raise(error.BadOperation, "this dynamic object is not callable", .{}); } - fn default_get(env: *RuntimeEnv, _: []coral.io.Byte, _: *const RuntimeRef) RuntimeError!?*RuntimeRef { + fn default_get(env: *RuntimeEnv, _: []coral.io.Byte, _: *RuntimeObj) RuntimeError!?*RuntimeObj { return env.raise(error.BadOperation, "this dynamic object is not get-indexable", .{}); } - fn default_set(env: *RuntimeEnv, _: []coral.io.Byte, _: *const RuntimeRef, _: ?*const RuntimeRef) RuntimeError!void { + fn default_set(env: *RuntimeEnv, _: []coral.io.Byte, _: *RuntimeObj, _: ?*RuntimeObj) RuntimeError!void { return env.raise(error.BadOperation, "this dynamic object is not set-indexable", .{}); } - fn default_to_string(env: *RuntimeEnv, _: []coral.io.Byte) coral.io.AllocationError!*RuntimeRef { - return env.new_string("{}"); + fn default_to_string(env: *RuntimeEnv, _: []coral.io.Byte) RuntimeError!*RuntimeObj { + return (try env.new_string("{}")).pop().?; } }; -pub fn assert(env: *RuntimeEnv, condition: bool) RuntimeError!void { - coral.debug.assert(condition); +pub const Vector2 = [2]f32; - if (!condition) { - return env.raise(error.IllegalState, "assertion", .{}); - } -} +pub const Vector3 = [3]f32; -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); +pub fn get_at(env: *RuntimeEnv, indexable: *RuntimeObj, index: Fixed) RuntimeError!?*RuntimeObj { + const index_number = (try env.new_fixed(index)).pop(); defer env.discard(index_number); - return env.get(indexable, index_number); + return (try (try env.push(indexable)).index_get(index_number)).pop(); } -pub fn get_key(env: *RuntimeEnv, indexable: *RuntimeRef, key: []const coral.io.Byte) RuntimeError!?*RuntimeRef { - const key_string = try env.new_string(key); +pub fn get_field(env: *RuntimeEnv, indexable: *RuntimeObj, field: []const coral.io.Byte) RuntimeError!?*RuntimeObj { + const field_symbol = (try env.new_symbol(field)).pop().?; + + defer env.release(field_symbol); + + return (try (try env.push(indexable)).index_get(field_symbol)).pop(); +} + +pub fn get_key(env: *RuntimeEnv, indexable: *RuntimeObj, key: []const coral.io.Byte) RuntimeError!?*RuntimeObj { + const key_string = (try env.new_string(key)).pop(); defer env.discard(key_string); - return env.get(indexable, key_string); + return (try (try env.push(indexable)).index_get(key_string)).pop(); } diff --git a/source/ona/kym/Chunk.zig b/source/ona/kym/Chunk.zig index 6fef2c3..7ff4688 100644 --- a/source/ona/kym/Chunk.zig +++ b/source/ona/kym/Chunk.zig @@ -10,13 +10,13 @@ const tokens = @import("./tokens.zig"); const tree = @import("./tree.zig"); -name: *kym.RuntimeRef, +name: *kym.RuntimeObj, arity: u8, opcodes: OpcodeList, lines: LineList, cursor: usize, constants: ConstList, -bindings: []?*kym.RuntimeRef, +bindings: []?*kym.RuntimeObj, const Builtin = enum { import, @@ -118,9 +118,9 @@ const Compiler = struct { }, .lambda_construct => |lambda_construct| { - var chunk = try Self.make(self.env, name orelse "", lambda_construct.environment); + var chunk = try Self.init(self.env, name orelse "", lambda_construct.environment); - errdefer chunk.free(self.env); + errdefer chunk.deinit(self.env); if (lambda_construct.environment.capture_count == 0) { try self.chunk.write(expression.line, .{.push_const = try self.declare_chunk(chunk)}); @@ -193,7 +193,7 @@ const Compiler = struct { return; } - if (try self.get_binding_index(environment, declaration_get.declaration)) |index| { + if (try get_binding_index(environment, declaration_get.declaration)) |index| { try self.chunk.write(expression.line, .{.push_binding = index}); if (is_declaration_boxed(declaration_get.declaration)) { @@ -208,19 +208,19 @@ const Compiler = struct { .declaration_set => |declaration_set| { if (get_local_index(environment, declaration_set.declaration)) |index| { - try self.compile_expression(environment, declaration_set.assign, null); - if (is_declaration_boxed(declaration_set.declaration)) { try self.chunk.write(expression.line, .{.push_local = index}); + try self.compile_expression(environment, declaration_set.assign, null); try self.chunk.write(expression.line, .set_box); } else { + try self.compile_expression(environment, declaration_set.assign, null); try self.chunk.write(expression.line, .{.set_local = index}); } return; } - if (try self.get_binding_index(environment, declaration_set.declaration)) |index| { + if (try get_binding_index(environment, declaration_set.declaration)) |index| { try self.compile_expression(environment, declaration_set.assign, null); try self.chunk.write(expression.line, .{.push_binding = index}); @@ -345,9 +345,9 @@ const Compiler = struct { }); } - const constant = try self.env.new_dynamic(coral.io.bytes_of(&chunk), typeinfo); + const constant = (try self.env.new_dynamic(coral.io.bytes_of(&chunk), typeinfo)).pop().?; - errdefer self.env.discard(constant); + errdefer self.env.release(constant); try self.chunk.constants.push_one(constant); @@ -361,9 +361,9 @@ const Compiler = struct { }); } - const constant = try self.env.new_fixed(fixed); + const constant = (try self.env.new_fixed(fixed)).pop().?; - errdefer self.env.discard(constant); + errdefer self.env.release(constant); try self.chunk.constants.push_one(constant); @@ -377,9 +377,9 @@ const Compiler = struct { }); } - const constant = try self.env.new_float(float); + const constant = (try self.env.new_float(float)).pop().?; - errdefer self.env.discard(constant); + errdefer self.env.release(constant); try self.chunk.constants.push_one(constant); @@ -393,9 +393,9 @@ const Compiler = struct { }); } - const constant = try self.env.new_string(string); + const constant = (try self.env.new_string(string)).pop().?; - errdefer self.env.discard(constant); + errdefer self.env.release(constant); try self.chunk.constants.push_one(constant); @@ -409,16 +409,16 @@ const Compiler = struct { }); } - const constant = try self.env.new_symbol(symbol); + const constant = (try self.env.new_symbol(symbol)).pop().?; - errdefer self.env.discard(constant); + errdefer self.env.release(constant); try self.chunk.constants.push_one(constant); return @intCast(self.chunk.constants.values.len - 1); } - fn get_binding_index(self: *const Compiler, environment: *const tree.Environment, declaration: *const tree.Declaration) kym.RuntimeError!?u8 { + fn get_binding_index(environment: *const tree.Environment, declaration: *const tree.Declaration) kym.RuntimeError!?u8 { var binding_index = @as(u8, 0); while (binding_index < environment.capture_count) : (binding_index += 1) { @@ -430,7 +430,7 @@ const Compiler = struct { target_environment = target_environment.enclosing orelse return null; } - try kym.assert(self.env, capture.* == .declaration_index); + coral.debug.assert(capture.* == .declaration_index); if (&target_environment.declarations[capture.declaration_index] == declaration) { return binding_index; @@ -459,7 +459,7 @@ const Compiler = struct { } }; -const ConstList = coral.list.Stack(*kym.RuntimeRef); +const ConstList = coral.list.Stack(*kym.RuntimeObj); const LineList = coral.list.Stack(tokens.Line); @@ -507,11 +507,33 @@ const OpcodeList = coral.list.Stack(Opcode); const Self = @This(); -pub fn dump(chunk: Self, env: *kym.RuntimeEnv) kym.RuntimeError!*kym.RuntimeRef { - var opcode_cursor = @as(u32, 0); - var buffer = coral.list.ByteStack.make(env.allocator); +pub fn deinit(self: *Self, env: *kym.RuntimeEnv) void { + while (self.constants.pop()) |constant| { + env.release(constant); + } - defer buffer.free(); + self.constants.deinit(); + self.opcodes.deinit(); + env.release(self.name); + + if (self.bindings.len != 0) { + for (self.bindings) |binding| { + if (binding) |value| { + env.release(value); + } + } + + env.allocator.deallocate(self.bindings); + } + + self.bindings = &.{}; +} + +pub fn dump(chunk: Self, env: *kym.RuntimeEnv) kym.RuntimeError!*kym.RuntimeObj { + var opcode_cursor = @as(u32, 0); + var buffer = coral.list.ByteStack.init(env.allocator); + + defer buffer.deinit(); const writer = coral.list.stack_as_writer(&buffer); @@ -528,15 +550,11 @@ pub fn dump(chunk: Self, env: *kym.RuntimeEnv) kym.RuntimeError!*kym.RuntimeRef .push_false => coral.utf8.print_string(writer, "push false\n"), .push_const => |push_const| print: { - try kym.assert(env, push_const < chunk.constants.values.len); + const string_ref = (try (try env.push(try chunk.get_constant(env, push_const))).to_string()).pop().?; - const string_ref = try env.to_string(chunk.constants.values[push_const]); + defer env.release(string_ref); - defer env.discard(string_ref); - - const string = string_ref.as_string(); - - coral.debug.assert(string != null); + const string = string_ref.is_string(); break: print coral.utf8.print_formatted(writer, "push const ({value})\n", .{.value = string.?}); }, @@ -599,407 +617,247 @@ pub fn dump(chunk: Self, env: *kym.RuntimeEnv) kym.RuntimeError!*kym.RuntimeRef }; } - return env.new_string(buffer.values); + return (try env.new_string(buffer.values)).pop().?; } -pub fn execute(self: *Self, env: *kym.RuntimeEnv, frame: *const kym.Frame) kym.RuntimeError!?*kym.RuntimeRef { +pub fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeObj { self.cursor = 0; while (self.cursor < self.opcodes.values.len) : (self.cursor += 1) { switch (self.opcodes.values[self.cursor]) { .ret => break, - - .pop => { - if (try env.pop_local()) |ref| { - env.discard(ref); - } - }, - - .push_nil => try env.locals.push_one(null), - .push_true => try env.locals.push_one(try env.new_boolean(true)), - .push_false => try env.locals.push_one(try env.new_boolean(false)), - - .push_const => |push_const| { - try kym.assert(env, push_const < self.constants.values.len); - try env.locals.push_one(self.constants.values[push_const].acquire()); - }, - - .push_local => |push_local| { - try kym.assert(env, push_local < (env.locals.values.len - frame.locals_top)); - - if (env.locals.values[frame.locals_top + push_local]) |local| { - try env.locals.push_one(local.acquire()); - } else { - try env.locals.push_one(null); - } - }, - - .push_top => { - const frame_locals = env.locals.values[frame.locals_top ..]; - - try kym.assert(env, frame_locals.len != 0); - - if (frame_locals[frame_locals.len - 1]) |local| { - try env.locals.push_one(local.acquire()); - } else { - try env.locals.push_one(null); - } - }, + .pop => env.discard(), + .push_nil => _ = try env.push(null), + .push_true => _ = try env.new_boolean(true), + .push_false => _ = try env.new_boolean(false), + .push_const => |push_const| _ = try env.push(try self.get_constant(env, push_const)), + .push_local => |push_local| _ = try env.local_get(push_local), + .push_top => _ = try env.local_get(env.local_count() - 1), .push_table => |push_table| { - const table = try env.new_table(); + const table = (try env.new_table()).pop().?; - errdefer env.discard(table); + errdefer env.release(table); - { + if (push_table != 0) { var popped = @as(usize, 0); while (popped < push_table) : (popped += 1) { - const index = try env.expect(try env.pop_local()); + const index = try env.expect_object(env.pop()); - defer env.discard(index); + defer env.release(index); - if (try env.pop_local()) |value| { - defer env.discard(value); + if (env.pop()) |object| { + defer env.release(object); - try env.set(table, index, value); + try (try env.push(table)).index_set(index, object); } } } - try env.locals.push_one(table); + _ = try env.push(table); }, .push_boxed => { - const value = try env.pop_local(); + const value = env.pop(); defer { - if (value) |ref| { - env.discard(ref); + if (value) |object| { + env.release(object); } } - const boxable = try env.new_boxed(value); - - errdefer env.discard(boxable); - - try env.locals.push_one(boxable); + _ = try env.new_boxed(value); }, - .push_binding => |push_binding| { - try kym.assert(env, push_binding <= self.bindings.len); - try env.locals.push_one(if (self.bindings[push_binding]) |value| value.acquire() else null); - }, - - .push_concat => |push_concat| { - const frame_locals = env.locals.values[frame.locals_top ..]; - - try kym.assert(env, push_concat <= frame_locals.len); - - const concat_locals = frame_locals[(frame_locals.len - push_concat) .. frame_locals.len]; - - for (concat_locals) |value| { - _ = try env.expect(value); - } - - const concatenated_value = try env.concat(@ptrCast(concat_locals)); - - errdefer env.discard(concatenated_value); - - var to_pop = concat_locals.len; - - while (to_pop != 0) : (to_pop -= 1) { - const popped = env.pop_local() catch unreachable; - - coral.debug.assert(popped != null); - env.discard(popped.?); - } - - try env.locals.push_one(concatenated_value); - }, + .push_binding => |push_binding| _ = try env.push(try self.get_binding(env, push_binding)), + .push_concat => |push_concat| _ = try env.concat(push_concat), .bind => |bind| { - const callable = try env.expect(try env.pop_local()); + const callable = try env.expect_object(env.pop()); - errdefer env.discard(callable); + defer env.release(callable); - const chunk = @as(*Self, @ptrCast(@alignCast(try env.unwrap_dynamic(callable, typeinfo)))); + const chunk = @as(*Self, @ptrCast(@alignCast(try env.expect_dynamic(callable, typeinfo)))); - chunk.bindings = try coral.io.allocate_many(env.allocator, bind, @as(?*kym.RuntimeRef, null)); + if (chunk.bindings.len != 0) { + return env.raise(error.IllegalState, "cannot bind values to an already-bound chunk", .{}); + } + + chunk.bindings = try coral.io.allocate_many(env.allocator, bind, @as(?*kym.RuntimeObj, null)); for (0 .. bind) |index| { - const value = try env.pop_local(); + const value = env.pop(); errdefer { - if (value) |ref| { - env.discard(ref); + if (value) |object| { + env.release(object); } } - const binding = &chunk.bindings[index]; - - if (binding.*) |*existing_binding| { - env.discard(existing_binding.*); - } - - binding.* = value; + chunk.bindings[index] = value; } - try env.locals.push_one(callable); + _ = try env.push(callable); }, - .push_builtin => |push_builtin| { - const builtin_syscall = try env.new_syscall(switch (push_builtin) { - .import => syscall_import, - .print => syscall_print, - .vec2 => syscall_vec2, - .vec3 => syscall_vec3, - }); + .push_builtin => |push_builtin| _ = try env.new_syscall(switch (push_builtin) { + .import => syscall_import, + .print => syscall_print, + .vec2 => syscall_vec2, + .vec3 => syscall_vec3, + }), - errdefer env.discard(builtin_syscall); - - try env.locals.push_one(builtin_syscall); - }, - - .set_local => |local_set| { - const local = &env.locals.values[frame.locals_top + local_set]; - - if (local.*) |previous_local| { - env.discard(previous_local); - } - - local.* = try env.pop_local(); - }, - - .get_box => { - const box = try env.expect(try env.pop_local()); - - defer env.discard(box); - - if (try env.get_boxed(box)) |unboxed| { - errdefer env.discard(unboxed); - - try env.locals.push_one(unboxed); - } else { - try env.locals.push_one(null); - } - }, + .set_local => |local_set| _ = env.local_set(local_set, env.pop()), + .get_box => _ = try env.boxed_get(), .set_box => { - const box = try env.expect(try env.pop_local()); + const value = env.pop(); - defer env.discard(box); + defer { + if (value) |object| { + env.release(object); + } + } - const value = try env.expect(try env.pop_local()); - - errdefer env.discard(value); - - try env.set_boxed(box, value); + try env.boxed_set(value); }, .get_dynamic => { - const index = try env.expect(try env.pop_local()); + const index = try env.expect_object(env.pop()); - defer env.discard(index); + defer env.release(index); - const indexable = try env.expect(try env.pop_local()); - - defer env.discard(indexable); - - const value = try env.get(indexable, index); - - errdefer { - if (value) |ref| { - env.discard(ref); - } - } - - try env.locals.push_one(value); + _ = try env.index_get(index); }, .set_dynamic => { - const value = try env.pop_local(); + const value = env.pop(); defer { - if (value) |ref| { - env.discard(ref); + if (value) |object| { + env.release(object); } } - const index = try env.expect(try env.pop_local()); + const index = try env.expect_object(env.pop()); - defer env.discard(index); + defer env.release(index); - const indexable = try env.expect(try env.pop_local()); - - defer env.discard(indexable); - - try env.set(indexable, index, value); + try env.index_set(index, value); }, - .call => |call| { - const result = call: { - const callable = try env.expect(try env.pop_local()); - - defer env.discard(callable); - - const call_frame = try env.push_frame(callable, call); - - defer env.pop_frame(); - - break: call try env.call_frame(&call_frame); - }; - - errdefer { - if (result) |ref| { - env.discard(ref); - } - } - - try env.locals.push_one(result); - }, + .call => |call| _ = try env.call(call), .not => { - if (try env.pop_local()) |value| { - defer env.discard(value); + const object = try env.expect_object(env.pop()); - try env.locals.push_one(try env.new_boolean(!value.is_truthy())); - } else { - try env.locals.push_one(try env.new_boolean(true)); - } + defer env.release(object); + + _ = try env.new_boolean(object.is_false()); }, - .neg => { - const value = try env.expect(try env.pop_local()); - - defer env.discard(value); - - try env.locals.push_one(try env.neg(value)); - }, + .neg => _ = try env.neg(), .add => { - const rhs = try env.expect(try env.pop_local()); + const addable = try env.expect_object(env.pop()); - defer env.discard(rhs); + defer env.release(addable); - const lhs = try env.expect(try env.pop_local()); - - defer env.discard(lhs); - - try env.locals.push_one(try env.add(lhs, rhs)); + _ = switch (try env.expect_numeric(addable)) { + .fixed => |fixed| try env.add_fixed(fixed), + .float => |float| try env.add_float(float), + .vector2 => |vector2| try env.add_vector2(vector2), + .vector3 => |vector3| try env.add_vector3(vector3), + }; }, .sub => { - const rhs = try env.expect(try env.pop_local()); + const subtractable = try env.expect_object(env.pop()); - defer env.discard(rhs); + defer env.release(subtractable); - const lhs = try env.expect(try env.pop_local()); - - defer env.discard(lhs); - - try env.locals.push_one(try env.sub(lhs, rhs)); + _ = switch (try env.expect_numeric(subtractable)) { + .fixed => |fixed| try env.sub_fixed(fixed), + .float => |float| try env.sub_float(float), + .vector2 => |vector2| try env.sub_vector2(vector2), + .vector3 => |vector3| try env.sub_vector3(vector3), + }; }, .mul => { - const rhs = try env.expect(try env.pop_local()); + const multiplicable = try env.expect_object(env.pop()); - defer env.discard(rhs); + defer env.release(multiplicable); - const lhs = try env.expect(try env.pop_local()); - - defer env.discard(lhs); - - try env.locals.push_one(try env.mul(lhs, rhs)); + _ = switch (try env.expect_numeric(multiplicable)) { + .fixed => |fixed| try env.mul_fixed(fixed), + .float => |float| try env.mul_float(float), + .vector2 => |vector2| try env.mul_vector2(vector2), + .vector3 => |vector3| try env.mul_vector3(vector3), + }; }, .div => { - const rhs = try env.expect(try env.pop_local()); + const divisible = try env.expect_object(env.pop()); - defer env.discard(rhs); + defer env.release(divisible); - const lhs = try env.expect(try env.pop_local()); - - defer env.discard(lhs); - - try env.locals.push_one(try env.div(lhs, rhs)); + _ = switch (try env.expect_numeric(divisible)) { + .fixed => |fixed| try env.div_fixed(fixed), + .float => |float| try env.div_float(float), + .vector2 => |vector2| try env.div_vector2(vector2), + .vector3 => |vector3| try env.div_vector3(vector3), + }; }, .eql => { - if (try env.pop_local()) |rhs| { - env.discard(rhs); + if (env.pop()) |equatable| { + defer env.release(equatable); - if (try env.pop_local()) |lhs| { - env.discard(lhs); - - try env.locals.push_one(try env.new_boolean(lhs.equals(rhs))); - } else { - try env.locals.push_one(try env.new_boolean(false)); - } + _ = try env.equals_object(equatable); } else { - if (try env.pop_local()) |lhs| { - env.discard(lhs); - - try env.locals.push_one(try env.new_boolean(false)); - } else { - try env.locals.push_one(try env.new_boolean(true)); - } + _ = try env.equals_nil(); } }, .cgt => { - const rhs = try env.expect(try env.pop_local()); + const comparable = try env.expect_object(env.pop()); - defer env.discard(rhs); + defer env.release(comparable); - const lhs = try env.expect(try env.pop_local()); - - defer env.discard(lhs); - - try env.locals.push_one(try env.new_boolean(try env.compare(lhs, rhs) > 0)); + _ = try env.compare_greater(comparable); }, .clt => { - const rhs = try env.expect(try env.pop_local()); + const comparable = try env.expect_object(env.pop()); - defer env.discard(rhs); + defer env.release(comparable); - const lhs = try env.expect(try env.pop_local()); - - defer env.discard(lhs); - - try env.locals.push_one(try env.new_boolean(try env.compare(lhs, rhs) < 0)); + _ = try env.compare_less(comparable); }, .cge => { - const rhs = try env.expect(try env.pop_local()); + const comparable = try env.expect_object(env.pop()); - defer env.discard(rhs); + defer env.release(comparable); - const lhs = try env.expect(try env.pop_local()); - - defer env.discard(lhs); - - try env.locals.push_one(try env.new_boolean(try env.compare(lhs, rhs) >= 0)); + _ = try env.compare_greater_equals(comparable); }, .cle => { - const rhs = try env.expect(try env.pop_local()); + const comparable = try env.expect_object(env.pop()); - defer env.discard(rhs); + defer env.release(comparable); - const lhs = try env.expect(try env.pop_local()); - - defer env.discard(lhs); - - try env.locals.push_one(try env.new_boolean(try env.compare(lhs, rhs) <= 0)); + _ = try env.compare_less_equals(comparable); }, .jf => |jf| { - if (try env.pop_local()) |condition| { - defer env.discard(condition); + if (env.pop()) |condition| { + defer env.release(condition); - if (!condition.is_truthy()) { + if (condition.is_false()) { self.cursor = jf; } } else { @@ -1008,10 +866,10 @@ pub fn execute(self: *Self, env: *kym.RuntimeEnv, frame: *const kym.Frame) kym.R }, .jt => |jt| { - if (try env.pop_local()) |condition| { - defer env.discard(condition); + if (env.pop()) |condition| { + defer env.release(condition); - if (condition.is_truthy()) { + if (condition.is_true()) { self.cursor = jt; } } @@ -1019,37 +877,31 @@ pub fn execute(self: *Self, env: *kym.RuntimeEnv, frame: *const kym.Frame) kym.R } } - return env.pop_local(); + return env.pop(); } -pub fn free(self: *Self, env: *kym.RuntimeEnv) void { - while (self.constants.pop()) |constant| { - env.discard(constant); +fn get_binding(self: *Self, env: *kym.RuntimeEnv, index: usize) kym.RuntimeError!?*kym.RuntimeObj { + if (index >= self.bindings.len) { + return env.raise(error.IllegalState, "invalid binding", .{}); } - self.constants.free(); - self.opcodes.free(); - env.discard(self.name); - - if (self.bindings.len != 0) { - for (self.bindings) |binding| { - if (binding) |value| { - env.discard(value); - } - } - - env.allocator.deallocate(self.bindings); - } - - self.bindings = &.{}; + return self.bindings[index]; } -pub fn make(env: *kym.RuntimeEnv, name: []const coral.io.Byte, environment: *const tree.Environment) kym.RuntimeError!Self { +fn get_constant(self: *const Self, env: *kym.RuntimeEnv, index: usize) kym.RuntimeError!*kym.RuntimeObj { + if (index >= self.constants.values.len) { + return env.raise(error.IllegalState, "invalid constant", .{}); + } + + return self.constants.values[index]; +} + +pub fn init(env: *kym.RuntimeEnv, name: []const coral.io.Byte, environment: *const tree.Environment) kym.RuntimeError!Self { var chunk = Self{ - .name = try env.new_symbol(name), - .opcodes = OpcodeList.make(env.allocator), - .constants = ConstList.make(env.allocator), - .lines = LineList.make(env.allocator), + .name = (try env.new_symbol(name)).pop().?, + .opcodes = OpcodeList.init(env.allocator), + .constants = ConstList.init(env.allocator), + .lines = LineList.init(env.allocator), .bindings = &.{}, .arity = environment.argument_count, .cursor = 0, @@ -1062,41 +914,81 @@ pub fn make(env: *kym.RuntimeEnv, name: []const coral.io.Byte, environment: *con try compiler.compile_environment(environment); + @import("../app.zig").log_info(""); + @import("../app.zig").log_info((try (try env.push(try chunk.dump(env))).to_string()).pop().?.is_string().?); + return chunk; } -fn syscall_import(env: *kym.RuntimeEnv, frame: *const kym.Frame) kym.RuntimeError!?*kym.RuntimeRef { - return env.import(file.Path.from(&.{try env.unwrap_string(try frame.expect_arg(env, 0))})); +fn syscall_import(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeObj { + const arg = (try env.arg_get(0)).pop() orelse { + return env.raise(error.BadOperation, "`@import` requires one argument to be a valid import path", .{}); + }; + + defer env.release(arg); + + return (try env.import(file.Path.from(&.{try env.expect_string(arg)}))).pop(); } -fn syscall_print(env: *kym.RuntimeEnv, frame: *const kym.Frame) kym.RuntimeError!?*kym.RuntimeRef { - env.print(try env.unwrap_string(try frame.expect_arg(env, 0))); +fn syscall_print(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeObj { + const string = (try (try env.arg_get(0)).to_string()).pop().?; + + defer env.release(string); + + env.print(string.is_string().?); return null; } -fn syscall_vec2(env: *kym.RuntimeEnv, frame: *const kym.Frame) kym.RuntimeError!?*kym.RuntimeRef { - const x = @as(f32, @floatCast(try env.unwrap_float(try frame.expect_arg(env, 0)))); +fn syscall_vec2(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeObj { + const x = @as(f32, get_x: { + const x = (try env.arg_get(0)).pop() orelse { + return env.raise(error.BadOperation, "a first argument is required to create a vector", .{}); + }; - if (frame.has_arg(env, 1)) |y| { - return env.new_vector2(x, @floatCast(try env.unwrap_float(y))); + defer env.release(x); + + break: get_x @floatCast(try env.expect_float(x)); + }); + + if ((try env.arg_get(1)).pop()) |y| { + defer env.release(y); + + return (try env.new_vector2(x, @floatCast(try env.expect_float(y)))).pop(); } - return env.new_vector2(x, x); + return (try env.new_vector2(x, x)).pop(); } -fn syscall_vec3(env: *kym.RuntimeEnv, frame: *const kym.Frame) kym.RuntimeError!?*kym.RuntimeRef { - const x = @as(f32, @floatCast(try env.unwrap_float(try frame.expect_arg(env, 0)))); +fn syscall_vec3(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeObj { + const x = @as(f32, get_x: { + const x = (try env.arg_get(0)).pop() orelse { + return env.raise(error.BadOperation, "a first argument is required to create a vector", .{}); + }; - if (frame.has_arg(env, 1)) |y| { - return env.new_vector3( - x, - @floatCast(try env.unwrap_float(y)), - @floatCast(try env.unwrap_float(try frame.expect_arg(env, 2))), - ); + defer env.release(x); + + break: get_x @floatCast(try env.expect_float(x)); + }); + + if ((try env.arg_get(1)).pop()) |y| { + defer env.release(y); + + const z = @as(f32, get_z: { + const z = (try env.arg_get(0)).pop() orelse { + return env.raise(error.BadOperation, + "a third argument is required to create a vector if a first and second exist", .{}); + }; + + defer env.release(z); + + break: get_z @floatCast(try env.expect_float(z)); + }); + + return (try env.new_vector3(x, @floatCast(try env.expect_float(y)), z)).pop(); } - return env.new_vector3(x, x, x); + return (try env.new_vector3(x, x, x)).pop(); } pub const typeinfo = &kym.Typeinfo{ @@ -1107,16 +999,16 @@ pub const typeinfo = &kym.Typeinfo{ .to_string = typeinfo_to_string, }; -fn typeinfo_call(env: *kym.RuntimeEnv, userdata: []coral.io.Byte, frame: *const kym.Frame) kym.RuntimeError!?*kym.RuntimeRef { - return @as(*Self, @ptrCast(@alignCast(userdata))).execute(env, frame); +fn typeinfo_call(env: *kym.RuntimeEnv, userdata: []coral.io.Byte) kym.RuntimeError!?*kym.RuntimeObj { + return @as(*Self, @ptrCast(@alignCast(userdata))).execute(env); } fn typeinfo_destruct(env: *kym.RuntimeEnv, userdata: []coral.io.Byte) void { - @as(*Self, @ptrCast(@alignCast(userdata))).free(env); + @as(*Self, @ptrCast(@alignCast(userdata))).deinit(env); } -fn typeinfo_to_string(env: *kym.RuntimeEnv, userdata: []coral.io.Byte) coral.io.AllocationError!*kym.RuntimeRef { - return env.to_string(@as(*Self, @ptrCast(@alignCast(userdata))).name); +fn typeinfo_to_string(env: *kym.RuntimeEnv, userdata: []coral.io.Byte) kym.RuntimeError!*kym.RuntimeObj { + return (try (try env.push(@as(*Self, @ptrCast(@alignCast(userdata))).name)).to_string()).pop().?; } pub fn write(self: *Self, line: tokens.Line, opcode: Opcode) coral.io.AllocationError!void { diff --git a/source/ona/kym/Table.zig b/source/ona/kym/Table.zig index aea1a72..3d02acf 100644 --- a/source/ona/kym/Table.zig +++ b/source/ona/kym/Table.zig @@ -5,41 +5,41 @@ const kym = @import("../kym.zig"); associative: RefTable, contiguous: RefList, -const RefList = coral.list.Stack(?*kym.RuntimeRef); +const RefList = coral.list.Stack(?*kym.RuntimeObj); -const RefTable = coral.map.Table(*kym.RuntimeRef, *kym.RuntimeRef, struct { - pub const hash = kym.RuntimeRef.hash; +const RefTable = coral.map.Table(*kym.RuntimeObj, *kym.RuntimeObj, struct { + pub const hash = kym.RuntimeObj.get_hash; - pub const equals = kym.RuntimeRef.equals; + pub const equals = kym.RuntimeObj.equals; }); const Self = @This(); -pub fn free(self: *Self, env: *kym.RuntimeEnv) void { +pub fn deinit(self: *Self, env: *kym.RuntimeEnv) void { { var field_iterable = self.associative.as_iterable(); while (field_iterable.next()) |entry| { - env.discard(entry.key); - env.discard(entry.value); + env.release(entry.key); + env.release(entry.value); } } - self.associative.free(); + self.associative.deinit(); while (self.contiguous.pop()) |value| { if (value) |ref| { - env.discard(ref); + env.release(ref); } } - self.contiguous.free(); + self.contiguous.deinit(); } -pub fn make(env: *kym.RuntimeEnv) Self { +pub fn init(env: *kym.RuntimeEnv) Self { return .{ - .associative = RefTable.make(env.allocator, .{}), - .contiguous = RefList.make(env.allocator), + .associative = RefTable.init(env.allocator, .{}), + .contiguous = RefList.init(env.allocator), }; } @@ -52,16 +52,16 @@ pub const typeinfo = &kym.Typeinfo{ }; fn typeinfo_destruct(env: *kym.RuntimeEnv, userdata: []coral.io.Byte) void { - @as(*Self, @ptrCast(@alignCast(userdata))).free(env); + @as(*Self, @ptrCast(@alignCast(userdata))).deinit(env); } -fn typeinfo_get(env: *kym.RuntimeEnv, userdata: []coral.io.Byte, index: *const kym.RuntimeRef) kym.RuntimeError!?*kym.RuntimeRef { +fn typeinfo_get(env: *kym.RuntimeEnv, userdata: []coral.io.Byte, index: *const kym.RuntimeObj) kym.RuntimeError!?*kym.RuntimeObj { const table = @as(*Self, @ptrCast(@alignCast(userdata))); const acquired_index = index.acquire(); - defer env.discard(acquired_index); + defer env.release(acquired_index); - if (acquired_index.as_fixed()) |fixed| { + if (acquired_index.is_fixed()) |fixed| { if (fixed < 0) { // TODO: Negative indexing. unreachable; @@ -79,13 +79,13 @@ fn typeinfo_get(env: *kym.RuntimeEnv, userdata: []coral.io.Byte, index: *const k return null; } -fn typeinfo_set(env: *kym.RuntimeEnv, userdata: []coral.io.Byte, index: *const kym.RuntimeRef, value: ?*const kym.RuntimeRef) kym.RuntimeError!void { +fn typeinfo_set(env: *kym.RuntimeEnv, userdata: []coral.io.Byte, index: *const kym.RuntimeObj, value: ?*const kym.RuntimeObj) kym.RuntimeError!void { const table = @as(*Self, @ptrCast(@alignCast(userdata))); const acquired_index = index.acquire(); - errdefer env.discard(acquired_index); + errdefer env.release(acquired_index); - if (acquired_index.as_fixed()) |fixed| { + if (acquired_index.is_fixed()) |fixed| { if (fixed < 0) { // TODO: Negative indexing. unreachable; @@ -95,7 +95,7 @@ fn typeinfo_set(env: *kym.RuntimeEnv, userdata: []coral.io.Byte, index: *const k const maybe_replacing = &table.contiguous.values[@intCast(fixed)]; if (maybe_replacing.*) |replacing| { - env.discard(replacing); + env.release(replacing); } maybe_replacing.* = if (value) |ref| ref.acquire() else null; @@ -106,17 +106,17 @@ fn typeinfo_set(env: *kym.RuntimeEnv, userdata: []coral.io.Byte, index: *const k const acquired_value = (value orelse { if (table.associative.remove(acquired_index)) |removed| { - env.discard(removed.key); - env.discard(removed.value); + env.release(removed.key); + env.release(removed.value); } return; }).acquire(); - errdefer env.discard(acquired_value); + errdefer env.release(acquired_value); if (try table.associative.replace(acquired_index, acquired_value)) |replaced| { - env.discard(replaced.key); - env.discard(replaced.value); + env.release(replaced.key); + env.release(replaced.value); } } diff --git a/source/ona/kym/tree.zig b/source/ona/kym/tree.zig index ee2ca11..bc0ec9e 100644 --- a/source/ona/kym/tree.zig +++ b/source/ona/kym/tree.zig @@ -192,17 +192,17 @@ pub const Root = struct { return coral.io.allocate_one(self.arena.as_allocator(), stmt); } - pub fn free(self: *Root) void { - self.error_messages.free(); - self.arena.free(); + pub fn deinit(self: *Root) void { + self.error_messages.deinit(); + self.arena.deinit(); } - pub fn make(allocator: coral.io.Allocator) coral.io.AllocationError!Root { + pub fn init(allocator: coral.io.Allocator) coral.io.AllocationError!Root { const arena_page_size = 4096; return .{ - .arena = coral.arena.Stacking.make(allocator, arena_page_size), - .error_messages = MessageList.make(allocator), + .arena = coral.arena.Stacking.init(allocator, arena_page_size), + .error_messages = MessageList.init(allocator), .environment = .{}, }; } diff --git a/source/ona/ona.zig b/source/ona/ona.zig index ef84c1f..fba6e55 100644 --- a/source/ona/ona.zig +++ b/source/ona/ona.zig @@ -23,7 +23,7 @@ pub fn run_app(file_access: file.Access) void { defer ext.SDL_Quit(); - var script_env = kym.RuntimeEnv.make(heap.allocator, .{ + var script_env = kym.RuntimeEnv.init(heap.allocator, 255, .{ .print = app.log_info, .print_error = app.log_fail, .import_access = file_access, @@ -31,7 +31,7 @@ pub fn run_app(file_access: file.Access) void { return app.log_fail("failed to initialize script runtime"); }; - defer script_env.free(); + defer script_env.deinit(); var manifest = app.Manifest{}; From dbba822c7ac26b783e564febb757a36131970480 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 12 Nov 2023 18:55:16 +0000 Subject: [PATCH 2/4] Add linear algebra utils for future graphics stuff --- source/ona/gfx.zig | 2 + source/ona/gfx/lina.zig | 181 +++++++++++++++++++++++++++++++++++++++ source/ona/kym/Chunk.zig | 3 - 3 files changed, 183 insertions(+), 3 deletions(-) create mode 100644 source/ona/gfx.zig create mode 100644 source/ona/gfx/lina.zig diff --git a/source/ona/gfx.zig b/source/ona/gfx.zig new file mode 100644 index 0000000..7bc3c66 --- /dev/null +++ b/source/ona/gfx.zig @@ -0,0 +1,2 @@ + +pub const lina = @import("./gfx/lina.zig"); diff --git a/source/ona/gfx/lina.zig b/source/ona/gfx/lina.zig new file mode 100644 index 0000000..8022321 --- /dev/null +++ b/source/ona/gfx/lina.zig @@ -0,0 +1,181 @@ + +pub const Vector2 = struct { + x: f32, + y: f32, + + pub const Scalars = [2]f32; + + pub fn equals(self: Vector2, vector: Vector2) bool { + return self.x == vector.x and self.y == vector.y; + } + + pub fn from_scalar(scalar: f32) Vector2 { + return .{ + .x = scalar, + .y = scalar, + }; + } + + pub fn from_scalars(scalars: Scalars) Vector2 { + return .{ + .x = scalars[0], + .y = scalars[1], + }; + } + + pub fn scalar_added(self: Vector2, scalar: f32) Vector2 { + return .{ + .x = self.x + scalar, + .y = self.y + scalar, + }; + } + + pub fn scalar_divided(self: Vector2, scalar: f32) Vector2 { + return .{ + .x = self.x / scalar, + .y = self.y / scalar, + }; + } + + pub fn scalar_multiplied(self: Vector2, scalar: f32) Vector2 { + return .{ + .x = self.x * scalar, + .y = self.y * scalar, + }; + } + + pub fn to_scalars(self: Vector2) Scalars { + return .{self.x, self.y}; + } + + pub fn scalar_subtracted(self: Vector2, scalar: f32) Vector2 { + return .{ + .x = self.x - scalar, + .y = self.y - scalar, + }; + } + + pub fn vector_added(self: Vector2, vector: Vector2) Vector2 { + return .{ + .x = self.x + vector.x, + .y = self.y + vector.y, + }; + } + + pub fn vector_divided(self: Vector2, vector: Vector2) Vector2 { + return .{ + .x = self.x / vector.x, + .y = self.y / vector.y, + }; + } + + pub fn vector_multiplied(self: Vector2, vector: Vector2) Vector2 { + return .{ + .x = self.x * vector.x, + .y = self.y * vector.y, + }; + } + + pub fn vector_subtracted(self: Vector2, vector: Vector2) Vector2 { + return .{ + .x = self.x - vector.x, + .y = self.y - vector.y, + }; + } +}; + +pub const Vector3 = struct { + x: f32, + y: f32, + z: f32, + + pub const Scalars = [3]f32; + + pub fn equals(self: Vector3, vector: Vector3) bool { + return self.x == vector.x and self.y == vector.y and self.z == vector.z; + } + + pub fn from_scalar(scalar: f32) Vector3 { + return .{ + .x = scalar, + .y = scalar, + .z = scalar, + }; + } + + pub fn from_scalars(scalars: Scalars) Vector3 { + return .{ + .x = scalars[0], + .y = scalars[1], + .z = scalars[2], + }; + } + + pub fn scalar_added(self: Vector3, scalar: f32) Vector3 { + return .{ + .x = self.x + scalar, + .y = self.y + scalar, + .z = self.z + scalar, + }; + } + + pub fn scalar_divided(self: Vector3, scalar: f32) Vector3 { + return .{ + .x = self.x / scalar, + .y = self.y / scalar, + .z = self.z / scalar, + }; + } + + pub fn scalar_multiplied(self: Vector3, scalar: f32) Vector3 { + return .{ + .x = self.x * scalar, + .y = self.y * scalar, + .z = self.z * scalar, + }; + } + + pub fn scalar_subtracted(self: Vector3, scalar: f32) Vector3 { + return .{ + .x = self.x - scalar, + .y = self.y - scalar, + .z = self.z - scalar, + }; + } + + pub fn to_scalars(self: Vector3) Scalars { + return .{self.x, self.y, self.z}; + } + + pub fn vector_added(self: Vector3, other: Vector3) Vector3 { + return .{ + .x = self.x + other.x, + .y = self.y + other.y, + .z = self.z + other.z, + }; + } + + pub fn vector_divided(self: Vector3, other: Vector3) Vector3 { + return .{ + .x = self.x / other.x, + .y = self.y / other.y, + .z = self.z / other.z, + }; + } + + pub fn vector_multiplied(self: Vector3, other: Vector3) Vector3 { + return .{ + .x = self.x * other.x, + .y = self.y * other.y, + .z = self.z * other.z, + }; + } + + pub fn vector_subtracted(self: Vector3, other: Vector3) Vector3 { + return .{ + .x = self.x - other.x, + .y = self.y - other.y, + .z = self.z - other.z, + }; + } +}; diff --git a/source/ona/kym/Chunk.zig b/source/ona/kym/Chunk.zig index 7ff4688..8bdf4de 100644 --- a/source/ona/kym/Chunk.zig +++ b/source/ona/kym/Chunk.zig @@ -914,9 +914,6 @@ pub fn init(env: *kym.RuntimeEnv, name: []const coral.io.Byte, environment: *con try compiler.compile_environment(environment); - @import("../app.zig").log_info(""); - @import("../app.zig").log_info((try (try env.push(try chunk.dump(env))).to_string()).pop().?.is_string().?); - return chunk; } From b0aa7166f92f22d43a1777ab23747eea6559df96 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 12 Nov 2023 20:02:47 +0000 Subject: [PATCH 3/4] Fill in missing documentation for Kym --- source/ona/kym.zig | 1452 ++++++++++++++++++++++++++------------ source/ona/kym/Chunk.zig | 78 +- source/ona/kym/Table.zig | 60 +- 3 files changed, 1059 insertions(+), 531 deletions(-) diff --git a/source/ona/kym.zig b/source/ona/kym.zig index b1ceea9..971fe23 100644 --- a/source/ona/kym.zig +++ b/source/ona/kym.zig @@ -6,6 +6,8 @@ const coral = @import("coral"); const file = @import("./file.zig"); +const gfx = @import("./gfx.zig"); + const tokens = @import("./kym/tokens.zig"); const tree = @import("./kym/tree.zig"); @@ -33,6 +35,14 @@ pub const Numeric = union (enum) { /// /// Runtime environment virtual machine state. /// +/// The environment operates on a mostly stack-based architecture, with nearly all functions "returning" computations by +/// writing them to the local values stack within. Functions may be chained together to perform sequential mutations on +/// data and either retrieved or discarded once the computation is done via [RuntimeEnv.pop] or [RuntimeEnv.discard] +/// respectively. +/// +/// *Note* that improperly cleaning up data pushed to the stack can result in accidental stack overflows, however also +/// note that all locals are always cleaned when exiting their call frame. +/// pub const RuntimeEnv = struct { allocator: coral.io.Allocator, options: Options, @@ -44,6 +54,16 @@ pub const RuntimeEnv = struct { callable: *RuntimeObj, locals_top: usize, arg_count: u8, + + const Self = @This(); + + fn args_of(self: Self, env: *RuntimeEnv) []?*RuntimeObj { + return env.locals.values[self.locals_top .. (self.locals_top + self.arg_count)]; + } + + fn locals_of(self: Self, env: *RuntimeEnv) []?*RuntimeObj { + return env.locals.values[self.locals_top ..]; + } }); const LocalList = coral.list.Stack(?*RuntimeObj); @@ -64,93 +84,23 @@ pub const RuntimeEnv = struct { const SymbolSet = coral.map.StringTable([:0]coral.io.Byte); - pub fn add_fixed(self: *RuntimeEnv, rhs_fixed: Fixed) RuntimeError!*RuntimeEnv { - const addable = try self.expect_object(self.pop()); - - defer self.release(addable); - - return switch (addable.internal().payload) { - .fixed => |lhs_fixed| if (coral.math.checked_add(lhs_fixed, rhs_fixed)) |result| - self.new_fixed(result) - else - self.new_float(@as(Float, @floatFromInt(lhs_fixed)) + @as(Float, @floatFromInt(rhs_fixed))), - - .float => |lhs_float| self.new_float(lhs_float + @as(Float, @floatFromInt(rhs_fixed))), - - .vector2 => |lhs_vector2| new: { - const rhs_scalar = @as(f32, @floatFromInt(rhs_fixed)); - - break: new self.new_vector2(lhs_vector2[0] + rhs_scalar, lhs_vector2[1] + rhs_scalar); - }, - - .vector3 => |lhs_vector3| new: { - const rhs_scalar = @as(f32, @floatFromInt(rhs_fixed)); - - break: new self.new_vector3(lhs_vector3[0] + rhs_scalar, lhs_vector3[1] + rhs_scalar, lhs_vector3[2] + rhs_scalar); - }, - - else => self.raise(error.TypeMismatch, "fixed types are not addable with {typename}", .{ - .typename = addable.get_typename(), - }), + /// + /// Attempts to push the argument located at `arg_index` to the top of `self`, pushing `null` instead if the given + /// argument does not exist or was provided as a nil value. + /// + /// Arguments are indexed according to the order they are passed, with `0` referring to the first argument position. + /// + /// A [RuntimeError] is returned if `self` is out of memory or the virtual machine is not inside a managed call + /// frame. + /// + /// `self` is returned for function chaining. + /// + pub fn arg_get(self: *RuntimeEnv, arg_index: Local) RuntimeError!*RuntimeEnv { + const frame = self.frames.peek() orelse { + return self.raise(error.IllegalState, "cannot get args outside of a call frame", .{}); }; - } - pub fn add_float(self: *RuntimeEnv, rhs_float: Float) RuntimeError!*RuntimeEnv { - const addable = try self.expect_object(self.pop()); - - defer self.release(addable); - - return switch (addable.internal().payload) { - .fixed => |lhs_fixed| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) + rhs_float), - .float => |lhs_float| self.new_float(lhs_float + lhs_float), - - .vector2 => |lhs_vector2| new: { - const rhs_scalar = @as(f32, @floatCast(rhs_float)); - - break: new self.new_vector2(lhs_vector2[0] + rhs_scalar, lhs_vector2[1] + rhs_scalar); - }, - - .vector3 => |lhs_vector3| new: { - const rhs_scalar = @as(f32, @floatCast(rhs_float)); - - break: new self.new_vector3(lhs_vector3[0] + rhs_scalar, lhs_vector3[1] + rhs_scalar, lhs_vector3[2] + rhs_scalar); - }, - - else => self.raise(error.TypeMismatch, "fixed types are not addable with {typename}", .{ - .typename = addable.get_typename(), - }), - }; - } - - pub fn add_vector2(self: *RuntimeEnv, rhs_vector2: Vector2) RuntimeError!*RuntimeEnv { - const addable = try self.expect_object(self.pop()); - - defer self.release(addable); - - return if (addable.is_vector2()) |lhs_vector2| - self.new_vector2(lhs_vector2[0] + rhs_vector2[0], lhs_vector2[1] + rhs_vector2[1]) - else - self.raise(error.TypeMismatch, "vector2 types are not addable with {typename}", .{ - .typename = addable.get_typename(), - }); - } - - pub fn add_vector3(self: *RuntimeEnv, rhs_vector3: Vector3) RuntimeError!*RuntimeEnv { - const addable = try self.expect_object(self.pop()); - - defer self.release(addable); - - return if (addable.is_vector3()) |lhs_vector3| - self.new_vector3(lhs_vector3[0] + rhs_vector3[0], lhs_vector3[1] + rhs_vector3[1], lhs_vector3[2] + rhs_vector3[2]) - else - self.raise(error.TypeMismatch, "vector3 types are not addable with {typename}", .{ - .typename = addable.get_typename(), - }); - } - - pub fn arg_get(self: *RuntimeEnv, arg_index: LocalIndex) RuntimeError!*RuntimeEnv { - const frame = &self.frames.values[self.frames.values.len - 1]; - const args = self.locals.values[frame.locals_top .. (frame.locals_top + frame.arg_count)]; + const args = frame.args_of(self); if (arg_index < args.len) { return self.push(args[arg_index]); @@ -161,7 +111,20 @@ pub const RuntimeEnv = struct { return self; } - pub fn call(self: *RuntimeEnv, local_arg_count: LocalIndex) RuntimeError!*RuntimeEnv { + /// + /// Attempts to pop the top-most value of `self` and call it with `local_arg_count` as the number of locals prior to + /// it in `self` that are intended to be arguments to it. Once the callable returns, the locals marked as arguments + /// are popped as well. + /// + /// A `local_arg_count` of `0` will call the function with no arguments and pop nothing other than the callable from + /// `self` during invocation. + /// + /// A [RuntimeError] is returned if `self` is out of memory, the top-most local is nil or not callable, or the + /// callable raises a runtime error during invocation. + /// + /// `self` is returned for function chaining. + /// + pub fn call(self: *RuntimeEnv, local_arg_count: Local) RuntimeError!*RuntimeEnv { const callable = try self.expect_object(self.pop()); defer self.release(callable); @@ -169,7 +132,7 @@ pub const RuntimeEnv = struct { const result = get_result: { try self.frames.push_one(.{ .locals_top = self.locals.values.len - local_arg_count, - .callable = callable.acquire(), + .callable = callable.internal().acquire(), .arg_count = local_arg_count, }); @@ -190,10 +153,14 @@ pub const RuntimeEnv = struct { break: get_result try switch (frame.callable.internal().payload) { .syscall => |syscall| syscall(self), - .dynamic => |dynamic| dynamic.typeinfo().call(self, dynamic.userdata()), + + .dynamic => |dynamic| dynamic.typeinfo().call(.{ + .userdata = dynamic.userdata(), + .env = self, + }), else => self.raise(error.TypeMismatch, "{typename} is not callable", .{ - .typename = frame.callable.get_typename(), + .typename = frame.callable.typename(), }), }; }; @@ -209,6 +176,15 @@ pub const RuntimeEnv = struct { return self; } + /// + /// Attempts to pop and compare the top-most local in `self` with `rhs_comparable`, pushing a true value if the + /// local is greater than `rhs_comparable`, otherwise pushing a false value. + /// + /// A [RuntimeError] is returned if `self` is out of memory, the top-most local is nil or not comparable, or + /// `rhs_comparable` is not comparable. + /// + /// `self` is returned for function chaining. + /// pub fn compare_greater(self: *RuntimeEnv, rhs_comparable: *RuntimeObj) RuntimeError!*RuntimeEnv { const lhs_comparable = try self.expect_object(self.pop()); @@ -220,7 +196,7 @@ pub const RuntimeEnv = struct { .float => |rhs_float| self.new_boolean(@as(Float, @floatFromInt(lhs_fixed)) > rhs_float), else => self.raise(error.TypeMismatch, "right-hand {typename} is not comparable", .{ - .typename = rhs_comparable.get_typename(), + .typename = rhs_comparable.typename(), }), }, @@ -229,16 +205,25 @@ pub const RuntimeEnv = struct { .fixed => |rhs_fixed| self.new_boolean(lhs_float > @as(Float, @floatFromInt(rhs_fixed))), else => self.raise(error.TypeMismatch, "right-hand {typename} is not comparable", .{ - .typename = rhs_comparable.get_typename(), + .typename = rhs_comparable.typename(), }), }, else => self.raise(error.TypeMismatch, "left-hand {typename} is not comparable", .{ - .typename = lhs_comparable.get_typename(), + .typename = lhs_comparable.typename(), }), }; } + /// + /// Attempts to pop and compare the top-most local in `self` with `rhs_comparable`, pushing a true value if the + /// local is greater than or equal to `rhs_comparable`, otherwise pushing a false value. + /// + /// A [RuntimeError] is returned if `self` is out of memory, the top-most local is nil or not comparable, or + /// `rhs_comparable` is not comparable. + /// + /// `self` is returned for function chaining. + /// pub fn compare_greater_equals(self: *RuntimeEnv, rhs_comparable: *RuntimeObj) RuntimeError!*RuntimeEnv { const lhs_comparable = try self.expect_object(self.pop()); @@ -250,7 +235,7 @@ pub const RuntimeEnv = struct { .float => |rhs_float| self.new_boolean(@as(Float, @floatFromInt(lhs_fixed)) >= rhs_float), else => self.raise(error.TypeMismatch, "right-hand {typename} is not comparable", .{ - .typename = rhs_comparable.get_typename(), + .typename = rhs_comparable.typename(), }), }, @@ -259,16 +244,25 @@ pub const RuntimeEnv = struct { .fixed => |rhs_fixed| self.new_boolean(lhs_float >= @as(Float, @floatFromInt(rhs_fixed))), else => self.raise(error.TypeMismatch, "right-hand {typename} is not comparable", .{ - .typename = rhs_comparable.get_typename(), + .typename = rhs_comparable.typename(), }), }, else => self.raise(error.TypeMismatch, "left-hand {typename} is not comparable", .{ - .typename = lhs_comparable.get_typename(), + .typename = lhs_comparable.typename(), }), }; } + /// + /// Attempts to pop and compare the top-most local in `self` with `rhs_comparable`, pushing a true value if the + /// local is less than `rhs_comparable`, otherwise pushing a false value. + /// + /// A [RuntimeError] is returned if `self` is out of memory, the top-most local is nil or not comparable, or + /// `rhs_comparable` is not comparable. + /// + /// `self` is returned for function chaining. + /// pub fn compare_less(self: *RuntimeEnv, rhs_comparable: *RuntimeObj) RuntimeError!*RuntimeEnv { const lhs_comparable = try self.expect_object(self.pop()); @@ -280,7 +274,7 @@ pub const RuntimeEnv = struct { .float => |rhs_float| self.new_boolean(@as(Float, @floatFromInt(lhs_fixed)) < rhs_float), else => return self.raise(error.TypeMismatch, "right-hand {typename} is not comparable", .{ - .typename = rhs_comparable.get_typename(), + .typename = rhs_comparable.typename(), }), }, @@ -289,16 +283,25 @@ pub const RuntimeEnv = struct { .fixed => |rhs_fixed| self.new_boolean(lhs_float < @as(Float, @floatFromInt(rhs_fixed))), else => return self.raise(error.TypeMismatch, "right-hand {typename} is not comparable", .{ - .typename = rhs_comparable.get_typename(), + .typename = rhs_comparable.typename(), }), }, else => return self.raise(error.TypeMismatch, "left-hand {typename} is not comparable", .{ - .typename = lhs_comparable.get_typename(), + .typename = lhs_comparable.typename(), }), }; } + /// + /// Attempts to pop and compare the top-most local in `self` with `rhs_comparable`, pushing a true value if the + /// local is less than or equal to `rhs_comparable`, otherwise pushing a false value. + /// + /// A [RuntimeError] is returned if `self` is out of memory, the top-most local is nil or not comparable, or + /// `rhs_comparable` is not comparable. + /// + /// `self` is returned for function chaining. + /// pub fn compare_less_equals(self: *RuntimeEnv, rhs_comparable: *RuntimeObj) RuntimeError!*RuntimeEnv { const lhs_comparable = try self.expect_object(self.pop()); @@ -310,7 +313,7 @@ pub const RuntimeEnv = struct { .float => |rhs_float| self.new_boolean(@as(Float, @floatFromInt(lhs_fixed)) <= rhs_float), else => return self.raise(error.TypeMismatch, "right-hand {typename} is not comparable", .{ - .typename = rhs_comparable.get_typename(), + .typename = rhs_comparable.typename(), }), }, @@ -319,17 +322,28 @@ pub const RuntimeEnv = struct { .fixed => |rhs_fixed| self.new_boolean(lhs_float <= @as(Float, @floatFromInt(rhs_fixed))), else => return self.raise(error.TypeMismatch, "right-hand {typename} is not comparable", .{ - .typename = rhs_comparable.get_typename(), + .typename = rhs_comparable.typename(), }), }, else => return self.raise(error.TypeMismatch, "left-hand {typename} is not comparable", .{ - .typename = lhs_comparable.get_typename(), + .typename = lhs_comparable.typename(), }), }; } - pub fn concat(self: *RuntimeEnv, local_concat_count: LocalIndex) RuntimeError!*RuntimeEnv { + /// + /// Attempts to pop `local_concat_count` number of locals from the stack and concatenate them together as one long + /// string object, pushing the result to the top of `self`. + /// + /// A `local_concat_count` of `0` will push an empty string object and pop nothing from `self`. + /// + /// A [RuntimeError] is returned if `self` is out of memory or one of the concatenated objects raises an error from + /// within it's to_string implementation. + /// + /// `self` is returned for function chaining. + /// + pub fn concat(self: *RuntimeEnv, local_concat_count: Local) RuntimeError!*RuntimeEnv { if (local_concat_count == 0) { return self.new_string(""); } @@ -403,108 +417,15 @@ pub const RuntimeEnv = struct { self.frames.deinit(); } + /// + /// Pops the top-most value of `self` and releases it. + /// pub fn discard(self: *RuntimeEnv) void { if (self.pop()) |popped| { self.release(popped); } } - pub fn div_fixed(self: *RuntimeEnv, rhs_fixed: Fixed) RuntimeError!*RuntimeEnv { - if (rhs_fixed == 0) { - return self.raise(error.BadOperation, "cannot divide by zero", .{}); - } - - const addable = try self.expect_object(self.pop()); - - defer self.release(addable); - - return switch (addable.internal().payload) { - .fixed => |lhs_fixed| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) / @as(Float, @floatFromInt(rhs_fixed))), - .float => |lhs_float| self.new_float(lhs_float / @as(Float, @floatFromInt(rhs_fixed))), - - .vector2 => |lhs_vector2| new: { - const rhs_scalar = @as(f32, @floatFromInt(rhs_fixed)); - - break: new self.new_vector2(lhs_vector2[0] / rhs_scalar, lhs_vector2[1] / rhs_scalar); - }, - - .vector3 => |lhs_vector3| new: { - const rhs_scalar = @as(f32, @floatFromInt(rhs_fixed)); - - break: new self.new_vector3(lhs_vector3[0] / rhs_scalar, lhs_vector3[1] / rhs_scalar, lhs_vector3[2] / rhs_scalar); - }, - - else => self.raise(error.TypeMismatch, "fixed types are not divisible with {typename}", .{ - .typename = addable.get_typename(), - }), - }; - } - - pub fn div_float(self: *RuntimeEnv, rhs_float: Float) RuntimeError!*RuntimeEnv { - if (rhs_float == 0) { - return self.raise(error.BadOperation, "cannot divide by zero", .{}); - } - - const addable = try self.expect_object(self.pop()); - - defer self.release(addable); - - return switch (addable.internal().payload) { - .fixed => |lhs_fixed| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) / rhs_float), - .float => |lhs_float| self.new_float(lhs_float / lhs_float), - - .vector2 => |lhs_vector2| new: { - const rhs_scalar = @as(f32, @floatCast(rhs_float)); - - break: new self.new_vector2(lhs_vector2[0] / rhs_scalar, lhs_vector2[1] / rhs_scalar); - }, - - .vector3 => |lhs_vector3| new: { - const rhs_scalar = @as(f32, @floatCast(rhs_float)); - - break: new self.new_vector3(lhs_vector3[0] / rhs_scalar, lhs_vector3[1] / rhs_scalar, lhs_vector3[2] / rhs_scalar); - }, - - else => self.raise(error.TypeMismatch, "fixed types are not divisible with {typename}", .{ - .typename = addable.get_typename(), - }), - }; - } - - pub fn div_vector2(self: *RuntimeEnv, rhs_vector2: Vector2) RuntimeError!*RuntimeEnv { - if (rhs_vector2[0] == 0 or rhs_vector2[1] == 0) { - return self.raise(error.BadOperation, "cannot divide by zero", .{}); - } - - const addable = try self.expect_object(self.pop()); - - defer self.release(addable); - - return if (addable.is_vector2()) |lhs_vector2| - self.new_vector2(lhs_vector2[0] / rhs_vector2[0], lhs_vector2[1] / rhs_vector2[1]) - else - self.raise(error.TypeMismatch, "vector2 types are not divisible with {typename}", .{ - .typename = addable.get_typename(), - }); - } - - pub fn div_vector3(self: *RuntimeEnv, rhs_vector3: Vector3) RuntimeError!*RuntimeEnv { - if (rhs_vector3[0] == 0 or rhs_vector3[1] == 0 or rhs_vector3[2] == 0) { - return self.raise(error.BadOperation, "cannot divide by zero", .{}); - } - - const addable = try self.expect_object(self.pop()); - - defer self.release(addable); - - return if (addable.is_vector3()) |lhs_vector3| - self.new_vector3(lhs_vector3[0] / rhs_vector3[0], lhs_vector3[1] / rhs_vector3[1], lhs_vector3[2] / rhs_vector3[2]) - else - self.raise(error.TypeMismatch, "vector3 types are not divisible with {typename}", .{ - .typename = addable.get_typename(), - }); - } - pub fn equals_nil(self: *RuntimeEnv) RuntimeError!*RuntimeEnv { if (self.pop()) |lhs_object| { defer self.release(lhs_object); @@ -527,7 +448,7 @@ pub const RuntimeEnv = struct { pub fn expect_dynamic(self: *RuntimeEnv, value: *RuntimeObj, typeinfo: *const Typeinfo) RuntimeError![]coral.io.Byte { return value.is_dynamic(typeinfo) orelse self.raise(error.TypeMismatch, "expected dynamic object, not {typename}", .{ - .typename = value.get_typename(), + .typename = value.typename(), }); } @@ -537,20 +458,20 @@ pub const RuntimeEnv = struct { .float => |float| float, else => self.raise(error.TypeMismatch, "expected float type, not {typename}", .{ - .typename = value.get_typename(), + .typename = value.typename(), }), }; } pub fn expect_fixed(self: *RuntimeEnv, value: *RuntimeObj) RuntimeError!Fixed { return value.is_fixed() orelse self.raise(error.TypeMismatch, "expected fixed type, not {typename}", .{ - .typename = value.get_typename(), + .typename = value.typename(), }); } pub fn expect_numeric(self: *RuntimeEnv, value: *RuntimeObj) RuntimeError!Numeric { return value.is_numeric() orelse self.raise(error.TypeMismatch, "expected numeric type, not {typename}", .{ - .typename = value.get_typename(), + .typename = value.typename(), }); } @@ -560,16 +481,240 @@ pub const RuntimeEnv = struct { pub fn expect_string(self: *RuntimeEnv, value: *RuntimeObj) RuntimeError![]const coral.io.Byte { return value.is_string() orelse self.raise(error.TypeMismatch, "expected string type, not {typename}", .{ - .typename = value.get_typename(), + .typename = value.typename(), }); } pub fn expect_symbol(self: *RuntimeEnv, value: *RuntimeObj) RuntimeError![*:0]const coral.io.Byte { return value.is_symbol() orelse self.raise(error.TypeMismatch, "expected symbol type, not {typename}", .{ - .typename = value.get_typename(), + .typename = value.typename(), }); } + /// + /// Attempts to pop the top-most value in `self` and add `rhs_fixed` to it, pushing the result. + /// + /// A [RuntimeError] is returned if `self` is out of memory or `rhs_fixed` is not addable with the top-most local. + /// + /// `self` is returned for function chaining. + /// + pub fn fixed_add(self: *RuntimeEnv, rhs_fixed: Fixed) RuntimeError!*RuntimeEnv { + const addable = try self.expect_object(self.pop()); + + defer self.release(addable); + + return switch (addable.internal().payload) { + .fixed => |lhs_fixed| if (coral.math.checked_add(lhs_fixed, rhs_fixed)) |result| + self.new_fixed(result) + else + self.new_float(@as(Float, @floatFromInt(lhs_fixed)) + @as(Float, @floatFromInt(rhs_fixed))), + + .float => |lhs_float| self.new_float(lhs_float + @as(Float, @floatFromInt(rhs_fixed))), + .vector2 => |lhs_vector2| self.new_vector2(lhs_vector2.scalar_added(@floatFromInt(rhs_fixed))), + .vector3 => |lhs_vector3| self.new_vector3(lhs_vector3.scalar_added(@floatFromInt(rhs_fixed))), + + else => self.raise(error.TypeMismatch, "fixed types are not addable with {typename}", .{ + .typename = addable.typename(), + }), + }; + } + + /// + /// Attempts to pop the top-most value in `self` and divide it with `rhs_fixed` to it, pushing the result. + /// + /// A [RuntimeError] is returned if `self` is out of memory or `rhs_fixed` is not divisible with the top-most local. + /// + /// `self` is returned for function chaining. + /// + pub fn fixed_divide(self: *RuntimeEnv, rhs_fixed: Fixed) RuntimeError!*RuntimeEnv { + if (rhs_fixed == 0) { + return self.raise(error.TypeMismatch, "cannot divide by zero", .{}); + } + + const addable = try self.expect_object(self.pop()); + + defer self.release(addable); + + return switch (addable.internal().payload) { + .fixed => |lhs_fixed| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) / @as(Float, @floatFromInt(rhs_fixed))), + .float => |lhs_float| self.new_float(lhs_float / @as(Float, @floatFromInt(rhs_fixed))), + .vector2 => |lhs_vector2| self.new_vector2(lhs_vector2.scalar_divided(@floatFromInt(rhs_fixed))), + .vector3 => |lhs_vector3| self.new_vector3(lhs_vector3.scalar_divided(@floatFromInt(rhs_fixed))), + + else => self.raise(error.TypeMismatch, "fixed types are not divisible with {typename}", .{ + .typename = addable.typename(), + }), + }; + } + + /// + /// Attempts to pop the top-most value in `self` and multiply it with `rhs_fixed` to it, pushing the result. + /// + /// A [RuntimeError] is returned if `self` is out of memory or `rhs_fixed` is not multiplicable with the top-most + /// local. + /// + /// `self` is returned for function chaining. + /// + pub fn fixed_multiply(self: *RuntimeEnv, rhs_fixed: Fixed) RuntimeError!*RuntimeEnv { + const addable = try self.expect_object(self.pop()); + + defer self.release(addable); + + return switch (addable.internal().payload) { + .fixed => |lhs_fixed| if (coral.math.checked_mul(lhs_fixed, rhs_fixed)) |result| + self.new_fixed(result) + else + self.new_float(@as(Float, @floatFromInt(lhs_fixed)) * @as(Float, @floatFromInt(rhs_fixed))), + + .float => |lhs_float| self.new_float(lhs_float + @as(Float, @floatFromInt(rhs_fixed))), + .vector2 => |lhs_vector2| self.new_vector2(lhs_vector2.scalar_multiplied(@floatFromInt(rhs_fixed))), + .vector3 => |lhs_vector3| self.new_vector3(lhs_vector3.scalar_multiplied(@floatFromInt(rhs_fixed))), + + else => self.raise(error.TypeMismatch, "fixed types are not multiplicable with {typename}", .{ + .typename = addable.typename(), + }), + }; + } + + /// + /// Attempts to pop the top-most value in `self` and subtract it with `rhs_fixed`, pushing the result. + /// + /// A [RuntimeError] is returned if `self` is out of memory or `rhs_fixed` is not subtractable with the top-most + /// local. + /// + /// `self` is returned for function chaining. + /// + pub fn fixed_subtract(self: *RuntimeEnv, rhs_fixed: Fixed) RuntimeError!*RuntimeEnv { + const addable = try self.expect_object(self.pop()); + + defer self.release(addable); + + return switch (addable.internal().payload) { + .fixed => |lhs_fixed| if (coral.math.checked_sub(lhs_fixed, rhs_fixed)) |result| + self.new_fixed(result) + else + self.new_float(@as(Float, @floatFromInt(lhs_fixed)) - @as(Float, @floatFromInt(rhs_fixed))), + + .float => |lhs_float| self.new_float(lhs_float + @as(Float, @floatFromInt(rhs_fixed))), + .vector2 => |lhs_vector2| self.new_vector2(lhs_vector2.scalar_subtracted(@floatFromInt(rhs_fixed))), + .vector3 => |lhs_vector3| self.new_vector3(lhs_vector3.scalar_subtracted(@floatFromInt(rhs_fixed))), + + else => self.raise(error.TypeMismatch, "fixed types are not multiplicable with {typename}", .{ + .typename = addable.typename(), + }), + }; + } + + /// + /// Attempts to pop the top-most value in `self` and add it with `rhs_float`, pushing the result. + /// + /// A [RuntimeError] is returned if `self` is out of memory or `rhs_float` is not addable with the top-most local. + /// + /// `self` is returned for function chaining. + /// + pub fn float_add(self: *RuntimeEnv, rhs_float: Float) RuntimeError!*RuntimeEnv { + const addable = try self.expect_object(self.pop()); + + defer self.release(addable); + + return switch (addable.internal().payload) { + .fixed => |lhs_fixed| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) + rhs_float), + .float => |lhs_float| self.new_float(lhs_float + lhs_float), + .vector2 => |lhs_vector2| self.new_vector2(lhs_vector2.scalar_added(@floatCast(rhs_float))), + .vector3 => |lhs_vector3| self.new_vector3(lhs_vector3.scalar_added(@floatCast(rhs_float))), + + else => self.raise(error.TypeMismatch, "fixed types are not addable with {typename}", .{ + .typename = addable.typename(), + }), + }; + } + + /// + /// Attempts to pop the top-most value in `self` and divide it with `rhs_float` to it, pushing the result. + /// + /// A [RuntimeError] is returned if `self` is out of memory or `rhs_float` is not divisible with the top-most local. + /// + /// `self` is returned for function chaining. + /// + pub fn float_divide(self: *RuntimeEnv, rhs_float: Float) RuntimeError!*RuntimeEnv { + if (rhs_float == 0) { + return self.raise(error.TypeMismatch, "cannot divide by zero", .{}); + } + + const addable = try self.expect_object(self.pop()); + + defer self.release(addable); + + return switch (addable.internal().payload) { + .fixed => |lhs_fixed| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) / rhs_float), + .float => |lhs_float| self.new_float(lhs_float / lhs_float), + .vector2 => |lhs_vector2| self.new_vector2(lhs_vector2.scalar_divided(@floatCast(rhs_float))), + .vector3 => |lhs_vector3| self.new_vector3(lhs_vector3.scalar_divided(@floatCast(rhs_float))), + + else => self.raise(error.TypeMismatch, "fixed types are not divisible with {typename}", .{ + .typename = addable.typename(), + }), + }; + } + + /// + /// Attempts to pop the top-most value in `self` and multiply it with `rhs_float`, pushing the result. + /// + /// A [RuntimeError] is returned if `self` is out of memory or `rhs_float` is not multiplicable with the top-most + /// local. + /// + /// `self` is returned for function chaining. + /// + pub fn float_multiply(self: *RuntimeEnv, rhs_float: Float) RuntimeError!*RuntimeEnv { + const addable = try self.expect_object(self.pop()); + + defer self.release(addable); + + return switch (addable.internal().payload) { + .fixed => |lhs_fixed| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) * rhs_float), + .float => |lhs_float| self.new_float(lhs_float * lhs_float), + .vector2 => |lhs_vector2| self.new_vector2(lhs_vector2.scalar_multiplied(@floatCast(rhs_float))), + .vector3 => |lhs_vector3| self.new_vector3(lhs_vector3.scalar_multiplied(@floatCast(rhs_float))), + + else => self.raise(error.TypeMismatch, "fixed types are not multiplicable with {typename}", .{ + .typename = addable.typename(), + }), + }; + } + + /// + /// Attempts to pop the top-most value in `self` and subtract it with `rhs_float`, pushing the result. + /// + /// A [RuntimeError] is returned if `self` is out of memory or `rhs_float` is not subtractable with the top-most + /// local. + /// + /// `self` is returned for function chaining. + /// + pub fn float_subtract(self: *RuntimeEnv, rhs_float: Float) RuntimeError!*RuntimeEnv { + const addable = try self.expect_object(self.pop()); + + defer self.release(addable); + + return switch (addable.internal().payload) { + .fixed => |lhs_fixed| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) - rhs_float), + .float => |lhs_float| self.new_float(lhs_float - lhs_float), + .vector2 => |lhs_vector2| self.new_vector2(lhs_vector2.scalar_subtracted(@floatCast(rhs_float))), + .vector3 => |lhs_vector3| self.new_vector3(lhs_vector3.scalar_subtracted(@floatCast(rhs_float))), + + else => self.raise(error.TypeMismatch, "fixed types are not multiplicable with {typename}", .{ + .typename = addable.typename(), + }), + }; + } + + /// + /// Attempts to pop the top-most value and index into it with `index`, pushing the retrieved value. + /// + /// A [RuntimeError] is returned if `self` is out of memory, the top-most local is nil or not indexable, or the + /// object-specific indexing operation raised an error. + /// + /// `self` is returned for function chaining. + /// pub fn index_get(self: *RuntimeEnv, index: *RuntimeObj) RuntimeError!*RuntimeEnv { const vector_max = 3; const indexable = try self.expect_object(self.pop()); @@ -590,13 +735,18 @@ pub const RuntimeEnv = struct { } swizzle_buffer[swizzle_count] = switch (swizzle_symbol[swizzle_count]) { - 'x' => vector2[0], - 'y' => vector2[1], + 'x' => vector2.x, + 'y' => vector2.y, 0 => return 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]), + + 2 => self.new_vector2(.{ + .x = swizzle_buffer[0], + .y = swizzle_buffer[1], + }), + + 3 => self.new_vector3(Vector3.from_scalars(swizzle_buffer)), else => unreachable, }, @@ -620,14 +770,19 @@ pub const RuntimeEnv = struct { } swizzle_buffer[swizzle_count] = switch (swizzle_symbol[swizzle_count]) { - 'x' => vector3[0], - 'y' => vector3[1], - 'z' => vector3[2], + 'x' => vector3.x, + 'y' => vector3.y, + 'z' => vector3.z, 0 => return 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]), + + 2 => self.new_vector2(.{ + .x = swizzle_buffer[0], + .y = swizzle_buffer[1], + }), + + 3 => self.new_vector3(Vector3.from_scalars(swizzle_buffer)), else => unreachable, }, @@ -639,39 +794,75 @@ pub const RuntimeEnv = struct { }, .dynamic => |dynamic| { - const value = try dynamic.typeinfo().get(self, dynamic.userdata(), index); + return self.push(get_value: { + try self.locals.push_one(index); - errdefer { - if (value) |object| { - self.release(object); - } - } + defer coral.debug.assert(self.locals.drop(1)); - try self.locals.push_one(value); - - return self; + break: get_value try dynamic.typeinfo().get(.{ + .userdata = dynamic.userdata(), + .env = self, + }); + }); }, else => return self.raise(error.TypeMismatch, "{typename} is not get-indexable", .{ - .typename = indexable.get_typename(), + .typename = indexable.typename(), }), } } + /// + /// Attempts to pop the top-most value and index into it with `index`, inserting `value` into it. + /// + /// A [RuntimeError] is returned if `self` is out of memory, the top-most local is nil or not indexable, or the + /// object-specific indexing operation raised an error. + /// pub fn index_set(self: *RuntimeEnv, index: *RuntimeObj, value: ?*RuntimeObj) RuntimeError!void { const indexable = try self.expect_object(self.pop()); defer self.release(indexable); try switch (indexable.internal().payload) { - .dynamic => |dynamic| dynamic.typeinfo().set(self, dynamic.userdata(), index, value), + .dynamic => |dynamic| set_value: { + try self.locals.push_one(value); + + defer coral.debug.assert(self.locals.pop() != null); + + try self.locals.push_one(index); + + defer coral.debug.assert(self.locals.pop() != null); + + break: set_value dynamic.typeinfo().set(.{ + .userdata = dynamic.userdata(), + .env = self, + }); + }, else => self.raise(error.TypeMismatch, "{typename} is not set-indexable", .{ - .typename = indexable.get_typename(), + .typename = indexable.typename(), }), }; } + /// + /// Attempts to import a script from the import [file.Access] (specified during [RuntimeEnv.init]) from `file_path`. + /// + /// If the loaded script is a plain text file, it will be read, parsed, and compiled into an optimized format before + /// being executed. + /// + /// If the loaded script is already compiled, it is immediately executed. + /// + /// After completing execution, the return value is pushed to the top of `self`. + /// + /// A [RuntimeError] is returned if `self` is out of memory, no file at `file_path` could be found by the runtime, + /// the script is a source file and contains invalid syntax, or the imported script raised an error during + /// execution. + /// + /// Any syntax errors are reported using [print_error] prior to raising a runtime error. + /// + /// `self` is returned for function chaining. + /// pub fn import(self: *RuntimeEnv, file_path: file.Path) RuntimeError!*RuntimeEnv { { var callable = new_chunk: { @@ -695,7 +886,7 @@ pub const RuntimeEnv = struct { root.parse(&stream) catch |parse_error| { for (root.error_messages.values) |error_message| { - self.print(error_message); + self.print_error(error_message); } return self.raise(parse_error, "failed to parse `{name}`", .{.name = file_name}); @@ -745,117 +936,95 @@ pub const RuntimeEnv = struct { }; } - const LocalIndex = u8; + const Local = u8; - const local_max = coral.math.max_int(@typeInfo(LocalIndex).Int); + const local_max = coral.math.max_int(@typeInfo(Local).Int); - pub fn local_get(self: *RuntimeEnv, index: LocalIndex) RuntimeError!*RuntimeEnv { - const frame = &self.frames.values[self.frames.values.len - 1]; + /// + /// Attempts to push the local located at `index` to the top of `self`. + /// + /// Locals are indexed according to the order they are declared, with `0` referring to the first declared arg, let, + /// or var in the current frame. + /// + /// A [RuntimeError] is returned if `self` is out of memory, `index` does not refer to a valid local, or the + /// virtual-machine is not in a managed call frame. + /// + /// `self` is returned for function chaining. + /// + pub fn local_get(self: *RuntimeEnv, index: Local) RuntimeError!*RuntimeEnv { + const frame = self.frames.peek() orelse { + return self.raise(error.IllegalState, "cannot get locals outside of a call frame", .{}); + }; - return self.push(self.locals.values[frame.locals_top + index]); + const locals = frame.locals_of(self); + + if (index >= locals.len) { + return self.raise(error.IllegalState, "invalid local get", .{}); + } + + return self.push(locals[index]); } - pub fn local_set(self: *RuntimeEnv, index: LocalIndex, value: ?*RuntimeObj) void { - const frame = &self.frames.values[self.frames.values.len - 1]; - const local = &self.locals.values[frame.locals_top + index]; + /// + /// Attempts to set the local located at `index` to `value`. + /// + /// Locals are indexed according to the order they are declared, with `0` referring to the first declared arg, let, + /// or var in the current frame. + /// + /// A [RuntimeError] if`index` does not refer to a valid local, or the virtual-machine is not in a managed call + /// frame. + /// + /// `self` is returned for function chaining. + /// + pub fn local_set(self: *RuntimeEnv, index: Local, value: ?*RuntimeObj) RuntimeError!void { + const frame = self.frames.peek() orelse { + return self.raise(error.IllegalState, "cannot get locals outside of a call frame", .{}); + }; + + const locals = frame.locals_of(self); + + if (index >= locals.len) { + return self.raise(error.IllegalState, "invalid local get", .{}); + } + + const local = &locals[index]; if (local.*) |previous_local| { self.release(previous_local); } - local.* = if (value) |object| object.acquire() else null; + local.* = if (value) |object| object.internal().acquire() else null; } - pub fn local_count(self: *RuntimeEnv) LocalIndex { - const frame = &self.frames.values[self.frames.values.len - 1]; - - return @intCast(self.locals.values[frame.locals_top ..].len); - } - - pub fn mul_fixed(self: *RuntimeEnv, rhs_fixed: Fixed) RuntimeError!*RuntimeEnv { - const addable = try self.expect_object(self.pop()); - - defer self.release(addable); - - return switch (addable.internal().payload) { - .fixed => |lhs_fixed| if (coral.math.checked_mul(lhs_fixed, rhs_fixed)) |result| - self.new_fixed(result) - else - self.new_float(@as(Float, @floatFromInt(lhs_fixed)) * @as(Float, @floatFromInt(rhs_fixed))), - - .float => |lhs_float| self.new_float(lhs_float + @as(Float, @floatFromInt(rhs_fixed))), - - .vector2 => |lhs_vector2| new: { - const rhs_scalar = @as(f32, @floatFromInt(rhs_fixed)); - - break: new self.new_vector2(lhs_vector2[0] * rhs_scalar, lhs_vector2[1] * rhs_scalar); - }, - - .vector3 => |lhs_vector3| new: { - const rhs_scalar = @as(f32, @floatFromInt(rhs_fixed)); - - break: new self.new_vector3(lhs_vector3[0] * rhs_scalar, lhs_vector3[1] * rhs_scalar, lhs_vector3[2] * rhs_scalar); - }, - - else => self.raise(error.TypeMismatch, "fixed types are not multiplicable with {typename}", .{ - .typename = addable.get_typename(), - }), + /// + /// Attempts to push a copy of the top-most local in `self`. + /// + /// A [RuntimeError] is returned if `self` is out of memory, the virtual-machine is not in a managed call frame, or + /// there are no locals in the frame. + /// + /// `self` is returned for function chaining. + /// + pub fn local_top(self: *RuntimeEnv) RuntimeError!*RuntimeEnv { + const frame = self.frames.peek() orelse { + return self.raise(error.IllegalState, "cannot get locals outside of a call frame", .{}); }; + + const locals = frame.locals_of(self); + + if (locals.len != 0) { + return self.raise(error.IllegalState, "invalid local top", .{}); + } + + return self.push(locals[locals.len - 1]); } - pub fn mul_float(self: *RuntimeEnv, rhs_float: Float) RuntimeError!*RuntimeEnv { - const addable = try self.expect_object(self.pop()); - - defer self.release(addable); - - return switch (addable.internal().payload) { - .fixed => |lhs_fixed| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) * rhs_float), - .float => |lhs_float| self.new_float(lhs_float * lhs_float), - - .vector2 => |lhs_vector2| new: { - const rhs_scalar = @as(f32, @floatCast(rhs_float)); - - break: new self.new_vector2(lhs_vector2[0] * rhs_scalar, lhs_vector2[1] * rhs_scalar); - }, - - .vector3 => |lhs_vector3| new: { - const rhs_scalar = @as(f32, @floatCast(rhs_float)); - - break: new self.new_vector3(lhs_vector3[0] * rhs_scalar, lhs_vector3[1] + rhs_scalar, lhs_vector3[2] * rhs_scalar); - }, - - else => self.raise(error.TypeMismatch, "fixed types are not multiplicable with {typename}", .{ - .typename = addable.get_typename(), - }), - }; - } - - pub fn mul_vector2(self: *RuntimeEnv, rhs_vector2: Vector2) RuntimeError!*RuntimeEnv { - const addable = try self.expect_object(self.pop()); - - defer self.release(addable); - - return if (addable.is_vector2()) |lhs_vector2| - self.new_vector2(lhs_vector2[0] * rhs_vector2[0], lhs_vector2[1] * rhs_vector2[1]) - else - self.raise(error.TypeMismatch, "vector2 types are not multiplicable with {typename}", .{ - .typename = addable.get_typename(), - }); - } - - pub fn mul_vector3(self: *RuntimeEnv, rhs_vector3: Vector3) RuntimeError!*RuntimeEnv { - const addable = try self.expect_object(self.pop()); - - defer self.release(addable); - - return if (addable.is_vector3()) |lhs_vector3| - self.new_vector3(lhs_vector3[0] * rhs_vector3[0], lhs_vector3[1] * rhs_vector3[1], lhs_vector3[2] * rhs_vector3[2]) - else - self.raise(error.TypeMismatch, "vector3 types are not multiplicable with {typename}", .{ - .typename = addable.get_typename(), - }); - } - + /// + /// Pops the top-most local from `self`, negates it, and pushes the result. + /// + /// A [RuntimeError] is returned if `self` is out of memory or the top-most local is nil or not negatable. + /// + /// `self` is returned for function chaining. + /// pub fn neg(self: *RuntimeEnv) RuntimeError!*RuntimeEnv { const negatable = try self.expect_object(self.pop()); @@ -866,11 +1035,18 @@ pub const RuntimeEnv = struct { .float => |float| self.new_float(-float), else => self.raise(error.TypeMismatch, "{typename} is not scalar negatable", .{ - .typename = negatable.get_typename(), + .typename = negatable.typename(), }), }; } + /// + /// Attempts to create a new true or false object according to `value`, pushing it to the top of `self`. + /// + /// A [RuntimeError] is returned if `self` is out of memory. + /// + /// `self` is returned for function chaining. + /// pub fn new_boolean(self: *RuntimeEnv, value: bool) RuntimeError!*RuntimeEnv { const boolean = try RuntimeObj.allocate(self.allocator, .{ .ref_count = 1, @@ -884,10 +1060,17 @@ pub const RuntimeEnv = struct { return self; } + /// + /// Attempts to create a new boxed object holding a reference to `value`, pushing it to the top of `self`. + /// + /// A [RuntimeError] is returned if `self` is out of memory. + /// + /// `self` is returned for function chaining. + /// pub fn new_boxed(self: *RuntimeEnv, value: ?*RuntimeObj) RuntimeError!*RuntimeEnv { const boxed = try RuntimeObj.allocate(self.allocator, .{ .ref_count = 1, - .payload = .{.boxed = if (value) |ref| ref.acquire() else null}, + .payload = .{.boxed = if (value) |object| object.internal().acquire() else null}, }); errdefer self.release(boxed); @@ -897,6 +1080,15 @@ pub const RuntimeEnv = struct { return self; } + /// + /// Attempts to create a new dynamic object from `userdata` and `typeinfo`, pushing it to the top of `self`. + /// + /// *Note* that the number of bytes in `userdata` must match the size described in `typeinfo` exactly. + /// + /// A [RuntimeError] is returned if `self` is out of memory. + /// + /// `self` is returned for function chaining. + /// pub fn new_dynamic(self: *RuntimeEnv, userdata: []const coral.io.Byte, typeinfo: *const Typeinfo) RuntimeError!*RuntimeEnv { coral.debug.assert(userdata.len == typeinfo.size); @@ -921,6 +1113,13 @@ pub const RuntimeEnv = struct { return self; } + /// + /// Attempts to create a new fixed-size number object from `value`, pushing it to the top of `self`. + /// + /// A [RuntimeError] is returned if `self` is out of memory. + /// + /// `self` is returned for function chaining. + /// pub fn new_fixed(self: *RuntimeEnv, value: Fixed) RuntimeError!*RuntimeEnv { const syscall = try RuntimeObj.allocate(self.allocator, .{ .ref_count = 1, @@ -934,6 +1133,13 @@ pub const RuntimeEnv = struct { return self; } + /// + /// Attempts to create a new floating point number object from `value`, pushing it to the top of `self`. + /// + /// A [RuntimeError] is returned if `self` is out of memory. + /// + /// `self` is returned for function chaining. + /// pub fn new_float(self: *RuntimeEnv, value: Float) RuntimeError!*RuntimeEnv { const syscall = try RuntimeObj.allocate(self.allocator, .{ .ref_count = 1, @@ -947,6 +1153,13 @@ pub const RuntimeEnv = struct { return self; } + /// + /// Attempts to create a new byte string object from `value`, pushing it to the top of `self`. + /// + /// A [RuntimeError] is returned if `self` is out of memory. + /// + /// `self` is returned for function chaining. + /// pub fn new_string(self: *RuntimeEnv, value: []const coral.io.Byte) RuntimeError!*RuntimeEnv { if (value.len > coral.math.max_int(@typeInfo(Fixed).Int)) { return error.OutOfMemory; @@ -976,6 +1189,13 @@ pub const RuntimeEnv = struct { return self; } + /// + /// Attempts to create a new system-native function object from `value`, pushing it to the top of `self`. + /// + /// A [RuntimeError] is returned if `self` is out of memory. + /// + /// `self` is returned for function chaining. + /// pub fn new_syscall(self: *RuntimeEnv, value: *const Syscall) RuntimeError!*RuntimeEnv { const syscall = try RuntimeObj.allocate(self.allocator, .{ .ref_count = 1, @@ -989,6 +1209,13 @@ pub const RuntimeEnv = struct { return self; } + /// + /// Attempts to create a new empty table object, pushing it to the top of `self`. + /// + /// A [RuntimeError] is returned if `self` is out of memory. + /// + /// `self` is returned for function chaining. + /// pub fn new_table(self: *RuntimeEnv) RuntimeError!*RuntimeEnv { var table = Table.init(self); @@ -997,10 +1224,17 @@ pub const RuntimeEnv = struct { return self.new_dynamic(coral.io.bytes_of(&table), Table.typeinfo); } - pub fn new_vector2(self: *RuntimeEnv, x: f32, y: f32) RuntimeError!*RuntimeEnv { + /// + /// Attempts to create a new 2-component vector object from `value`, pushing it to the top of `self`. + /// + /// A [RuntimeError] is returned if `self` is out of memory. + /// + /// `self` is returned for function chaining. + /// + pub fn new_vector2(self: *RuntimeEnv, value: Vector2) RuntimeError!*RuntimeEnv { const vector2 = try RuntimeObj.allocate(self.allocator, .{ .ref_count = 1, - .payload = .{.vector2 = .{x, y}}, + .payload = .{.vector2 = value}, }); errdefer self.release(vector2); @@ -1010,10 +1244,17 @@ pub const RuntimeEnv = struct { return self; } - pub fn new_vector3(self: *RuntimeEnv, x: f32, y: f32, z: f32) RuntimeError!*RuntimeEnv { + /// + /// Attempts to create a new 3-component vector object from `value`, pushing it to the top of `self`. + /// + /// A [RuntimeError] is returned if `self` is out of memory. + /// + /// `self` is returned for function chaining. + /// + pub fn new_vector3(self: *RuntimeEnv, value: Vector3) RuntimeError!*RuntimeEnv { const vector3 = try RuntimeObj.allocate(self.allocator, .{ .ref_count = 1, - .payload = .{.vector3 = .{x, y, z}}, + .payload = .{.vector3 = value}, }); errdefer self.release(vector3); @@ -1023,6 +1264,13 @@ pub const RuntimeEnv = struct { return self; } + /// + /// Attempts to create a new symbol object from `value`, pushing it to the top of `self`. + /// + /// A [RuntimeError] is returned if `self` is out of memory. + /// + /// `self` is returned for function chaining. + /// pub fn new_symbol(self: *RuntimeEnv, value: []const coral.io.Byte) RuntimeError!*RuntimeEnv { const symbol = try RuntimeObj.allocate(self.allocator, .{ .payload = .{ @@ -1047,6 +1295,12 @@ pub const RuntimeEnv = struct { return self; } + /// + /// Pops the top-most value of `self`, returning the value. + /// + /// *Note* any returned non-null pointer must be released via [RuntimeEnv.release] or else the resources belonging + /// to the objects will never be freed by the runtime. + /// pub fn pop(self: *RuntimeEnv) ?*RuntimeObj { const popped = self.locals.pop(); @@ -1055,21 +1309,34 @@ pub const RuntimeEnv = struct { return popped.?; } + /// + /// Prints `buffer` to the printing operation specified in the options of `self`. + /// pub fn print(self: *RuntimeEnv, buffer: []const coral.io.Byte) void { if (self.options.print) |op| { op(buffer); } } + /// + /// Prints `buffer` to the error printing operation specified in the options of `self. + /// pub fn print_error(self: *RuntimeEnv, buffer: []const coral.io.Byte) void { if (self.options.print_error) |op| { op(buffer); } } + /// + /// Attempts to push `value` to the top of `self`. + /// + /// A [RuntimeError] is returned if `self` is out of memory. + /// + /// `self` is returned for function chaining. + /// pub fn push(self: *RuntimeEnv, value: ?*RuntimeObj) RuntimeError!*RuntimeEnv { if (value) |object| { - const acquired = object.acquire(); + const acquired = object.internal().acquire(); errdefer self.release(acquired); @@ -1081,6 +1348,12 @@ pub const RuntimeEnv = struct { return self; } + /// + /// Creates stack-unwinding runtime error from `error_value` with `format` and `args` as the formatted error message + /// presented with the type of error raised and a full stack-trace. + /// + /// The `error_value` passed is returned so that the error may be raised and returned in a single line. + /// pub fn raise(self: *RuntimeEnv, error_value: RuntimeError, comptime format: []const coral.io.Byte, args: anytype) RuntimeError { const formatted_message = try coral.utf8.alloc_formatted(self.allocator, format, args); @@ -1135,6 +1408,10 @@ pub const RuntimeEnv = struct { return error_value; } + /// + /// Releases `value` from `self`, decrementing the reference count (if applicable), and releasing all resources + /// belonging to the object once the reference count reaches zero. + /// pub fn release(self: *RuntimeEnv, value: *RuntimeObj) void { coral.debug.assert(value.internal().ref_count != 0); @@ -1157,7 +1434,10 @@ pub const RuntimeEnv = struct { .dynamic => |dynamic| { if (dynamic.typeinfo().destruct) |destruct| { - destruct(self, dynamic.userdata()); + destruct(.{ + .userdata = dynamic.userdata(), + .env = self, + }); } self.allocator.deallocate(dynamic.unpack()); @@ -1168,90 +1448,14 @@ pub const RuntimeEnv = struct { } } - pub fn sub_fixed(self: *RuntimeEnv, rhs_fixed: Fixed) RuntimeError!*RuntimeEnv { - const addable = try self.expect_object(self.pop()); - - defer self.release(addable); - - return switch (addable.internal().payload) { - .fixed => |lhs_fixed| if (coral.math.checked_sub(lhs_fixed, rhs_fixed)) |result| - self.new_fixed(result) - else - self.new_float(@as(Float, @floatFromInt(lhs_fixed)) - @as(Float, @floatFromInt(rhs_fixed))), - - .float => |lhs_float| self.new_float(lhs_float + @as(Float, @floatFromInt(rhs_fixed))), - - .vector2 => |lhs_vector2| new: { - const rhs_scalar = @as(f32, @floatFromInt(rhs_fixed)); - - break: new self.new_vector2(lhs_vector2[0] - rhs_scalar, lhs_vector2[1] - rhs_scalar); - }, - - .vector3 => |lhs_vector3| new: { - const rhs_scalar = @as(f32, @floatFromInt(rhs_fixed)); - - break: new self.new_vector3(lhs_vector3[0] - rhs_scalar, lhs_vector3[1] - rhs_scalar, lhs_vector3[2] - rhs_scalar); - }, - - else => self.raise(error.TypeMismatch, "fixed types are not multiplicable with {typename}", .{ - .typename = addable.get_typename(), - }), - }; - } - - pub fn sub_float(self: *RuntimeEnv, rhs_float: Float) RuntimeError!*RuntimeEnv { - const addable = try self.expect_object(self.pop()); - - defer self.release(addable); - - return switch (addable.internal().payload) { - .fixed => |lhs_fixed| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) - rhs_float), - .float => |lhs_float| self.new_float(lhs_float - lhs_float), - - .vector2 => |lhs_vector2| new: { - const rhs_scalar = @as(f32, @floatCast(rhs_float)); - - break: new self.new_vector2(lhs_vector2[0] - rhs_scalar, lhs_vector2[1] - rhs_scalar); - }, - - .vector3 => |lhs_vector3| new: { - const rhs_scalar = @as(f32, @floatCast(rhs_float)); - - break: new self.new_vector3(lhs_vector3[0] - rhs_scalar, lhs_vector3[1] - rhs_scalar, lhs_vector3[2] - rhs_scalar); - }, - - else => self.raise(error.TypeMismatch, "fixed types are not multiplicable with {typename}", .{ - .typename = addable.get_typename(), - }), - }; - } - - pub fn sub_vector2(self: *RuntimeEnv, rhs_vector2: Vector2) RuntimeError!*RuntimeEnv { - const addable = try self.expect_object(self.pop()); - - defer self.release(addable); - - return if (addable.is_vector2()) |lhs_vector2| - self.new_vector2(lhs_vector2[0] - rhs_vector2[0], lhs_vector2[1] - rhs_vector2[1]) - else - self.raise(error.TypeMismatch, "vector2 types are not multiplicable with {typename}", .{ - .typename = addable.get_typename(), - }); - } - - pub fn sub_vector3(self: *RuntimeEnv, rhs_vector3: Vector3) RuntimeError!*RuntimeEnv { - const addable = try self.expect_object(self.pop()); - - defer self.release(addable); - - return if (addable.is_vector3()) |lhs_vector3| - self.new_vector3(lhs_vector3[0] - rhs_vector3[0], lhs_vector3[1] - rhs_vector3[1], lhs_vector3[2] - rhs_vector3[2]) - else - self.raise(error.TypeMismatch, "vector3 types are not multiplicable with {typename}", .{ - .typename = addable.get_typename(), - }); - } - + /// + /// Attempts to pop the top-most value of `self`, convert it to a string object representation, and push the result. + /// + /// A [RuntimeError] is returned if `self` is out of memory or the to_string implementation of the top-most value of + /// `self` raised an error. + /// + /// `self` is returned for function chaining. + /// pub fn to_string(self: *RuntimeEnv) RuntimeError!*RuntimeEnv { const decimal_format = coral.utf8.DecimalFormat.default; const value = try self.expect_object(self.pop()); @@ -1286,7 +1490,7 @@ pub const RuntimeEnv = struct { .symbol => |symbol| self.new_string(coral.io.slice_sentineled(@as(coral.io.Byte, 0), symbol)), .string => acquire: { - try self.locals.push_one(value.acquire()); + try self.locals.push_one(value.internal().acquire()); break: acquire self; }, @@ -1294,36 +1498,26 @@ pub const RuntimeEnv = struct { .vector2 => |vector2| convert: { var string = [_:0]coral.io.Byte{0} ** 64; var buffer = coral.io.FixedBuffer{.bytes = &string}; + const length = coral.utf8.print_formatted(buffer.as_writer(), "@vec2({x}, {y})", vector2).?; - const length = coral.utf8.print_formatted(buffer.as_writer(), "@vec2({x}, {y})", .{ - .x = vector2[0], - .y = vector2[1], - }); - - coral.debug.assert(length != null); - - break: convert self.new_string(string[0 .. length.?]); + break: convert self.new_string(string[0 .. length]); }, .vector3 => |vector3| convert: { var string = [_:0]coral.io.Byte{0} ** 96; var buffer = coral.io.FixedBuffer{.bytes = &string}; + const length = coral.utf8.print_formatted(buffer.as_writer(), "@vec3({x}, {y}, {z})", vector3).?; - const length = coral.utf8.print_formatted(buffer.as_writer(), "@vec3({x}, {y}, {z})", .{ - .x = vector3[0], - .y = vector3[1], - .z = vector3[2], - }); - - coral.debug.assert(length != null); - - break: convert self.new_string(string[0 .. length.?]); + break: convert self.new_string(string[0 .. length]); }, .syscall => self.new_string("syscall"), .dynamic => |dynamic| dynamic_to_string: { - const string = try dynamic.typeinfo().to_string(self, dynamic.userdata()); + const string = try dynamic.typeinfo().to_string(.{ + .userdata = dynamic.userdata(), + .env = self, + }); errdefer self.release(string); @@ -1334,6 +1528,187 @@ pub const RuntimeEnv = struct { }; } + /// + /// Attempts to pop the top-most value in `self` and add it with `rhs_vector2`, pushing the result. + /// + /// A [RuntimeError] is returned if `self` is out of memory or `rhs_vector2` is not addable with the top-most local. + /// + /// `self` is returned for function chaining. + /// + pub fn vector2_add(self: *RuntimeEnv, rhs_vector2: Vector2) RuntimeError!*RuntimeEnv { + const addable = try self.expect_object(self.pop()); + + defer self.release(addable); + + return if (addable.is_vector2()) |lhs_vector2| + self.new_vector2(lhs_vector2.vector_added(rhs_vector2)) + else + self.raise(error.TypeMismatch, "vector2 types are not addable with {typename}", .{ + .typename = addable.typename(), + }); + } + + /// + /// Attempts to pop the top-most value in `self` and divide it with `rhs_vector2`, pushing the result. + /// + /// A [RuntimeError] is returned if `self` is out of memory or `rhs_vector2` is not divisible with the top-most + /// local. + /// + /// `self` is returned for function chaining. + /// + pub fn vector2_divide(self: *RuntimeEnv, rhs_vector2: Vector2) RuntimeError!*RuntimeEnv { + if (rhs_vector2.x == 0 or rhs_vector2.y == 0) { + return self.raise(error.TypeMismatch, "cannot divide by zero", .{}); + } + + const addable = try self.expect_object(self.pop()); + + defer self.release(addable); + + return if (addable.is_vector2()) |lhs_vector2| + self.new_vector2(lhs_vector2.vector_divided(rhs_vector2)) + else + self.raise(error.TypeMismatch, "vector2 types are not divisible with {typename}", .{ + .typename = addable.typename(), + }); + } + + /// + /// Attempts to pop the top-most value in `self` and multiply it with `rhs_vector2`, pushing the result. + /// + /// A [RuntimeError] is returned if `self` is out of memory or `rhs_vector2` is not multiplicable with the top-most + /// local. + /// + /// `self` is returned for function chaining. + /// + pub fn vector2_multiply(self: *RuntimeEnv, rhs_vector2: Vector2) RuntimeError!*RuntimeEnv { + const addable = try self.expect_object(self.pop()); + + defer self.release(addable); + + return if (addable.is_vector2()) |lhs_vector2| + self.new_vector2(lhs_vector2.vector_multiplied(rhs_vector2)) + else + self.raise(error.TypeMismatch, "vector2 types are not multiplicable with {typename}", .{ + .typename = addable.typename(), + }); + } + + /// + /// Attempts to pop the top-most value in `self` and subtract it with `rhs_vector2`, pushing the result. + /// + /// A [RuntimeError] is returned if `self` is out of memory or `rhs_vector2` is not subtractable with the top-most + /// local. + /// + /// `self` is returned for function chaining. + /// + pub fn vector2_subtract(self: *RuntimeEnv, rhs_vector2: Vector2) RuntimeError!*RuntimeEnv { + const addable = try self.expect_object(self.pop()); + + defer self.release(addable); + + return if (addable.is_vector2()) |lhs_vector2| + self.new_vector2(lhs_vector2.vector_subtracted(rhs_vector2)) + else + self.raise(error.TypeMismatch, "vector2 types are not multiplicable with {typename}", .{ + .typename = addable.typename(), + }); + } + + /// + /// Attempts to pop the top-most value in `self` and add it with `rhs_vector3`, pushing the result. + /// + /// A [RuntimeError] is returned if `self` is out of memory or `rhs_vector3` is not addable with the top-most local. + /// + /// `self` is returned for function chaining. + /// + pub fn vector3_add(self: *RuntimeEnv, rhs_vector3: Vector3) RuntimeError!*RuntimeEnv { + const addable = try self.expect_object(self.pop()); + + defer self.release(addable); + + return if (addable.is_vector3()) |lhs_vector3| + self.new_vector3(lhs_vector3.vector_added(rhs_vector3)) + else + self.raise(error.TypeMismatch, "vector3 types are not addable with {typename}", .{ + .typename = addable.typename(), + }); + } + + /// + /// Attempts to pop the top-most value in `self` and divide it with `rhs_vector3`, pushing the result. + /// + /// A [RuntimeError] is returned if `self` is out of memory or `rhs_vector3` is not divisible with the top-most + /// local. + /// + /// `self` is returned for function chaining. + /// + pub fn vector3_divide(self: *RuntimeEnv, rhs_vector3: Vector3) RuntimeError!*RuntimeEnv { + if (rhs_vector3.x == 0 or rhs_vector3.y == 0 or rhs_vector3.z == 0) { + return self.raise(error.TypeMismatch, "cannot divide by zero", .{}); + } + + const addable = try self.expect_object(self.pop()); + + defer self.release(addable); + + return if (addable.is_vector3()) |lhs_vector3| + self.new_vector3(lhs_vector3.vector_divided(rhs_vector3)) + else + self.raise(error.TypeMismatch, "vector3 types are not divisible with {typename}", .{ + .typename = addable.typename(), + }); + } + + /// + /// Attempts to pop the top-most value in `self` and multiply it with `rhs_vector3`, pushing the result. + /// + /// A [RuntimeError] is returned if `self` is out of memory or `rhs_vector3` is not multiplicable with the top-most + /// local. + /// + /// `self` is returned for function chaining. + /// + pub fn vector3_multiply(self: *RuntimeEnv, rhs_vector3: Vector3) RuntimeError!*RuntimeEnv { + const addable = try self.expect_object(self.pop()); + + defer self.release(addable); + + return if (addable.is_vector3()) |lhs_vector3| + self.new_vector3(lhs_vector3.vector_multiplied(rhs_vector3)) + else + self.raise(error.TypeMismatch, "vector3 types are not multiplicable with {typename}", .{ + .typename = addable.typename(), + }); + } + + /// + /// Attempts to pop the top-most value in `self` and subtract it with `rhs_vector3`, pushing the result. + /// + /// A [RuntimeError] is returned if `self` is out of memory or `rhs_vector3` is not subtractable with the top-most + /// local. + /// + /// `self` is returned for function chaining. + /// + pub fn vector3_subtract(self: *RuntimeEnv, rhs_vector3: Vector3) RuntimeError!*RuntimeEnv { + const addable = try self.expect_object(self.pop()); + + defer self.release(addable); + + return if (addable.is_vector3()) |lhs_vector3| + self.new_vector3(lhs_vector3.vector_subtracted(rhs_vector3)) + else + self.raise(error.TypeMismatch, "vector3 types are not multiplicable with {typename}", .{ + .typename = addable.typename(), + }); + } + + /// + /// Attempts to pop the top-most value in `self`, pushing the unboxed value. + /// + /// A [RuntimeError] is returned if `self` is out of memory or the top-most value is nil or not unboxable. + /// + /// `self` is returned for function chaining. + /// pub fn boxed_get(self: *RuntimeEnv) RuntimeError!*RuntimeEnv { const unboxable = try self.expect_object(self.pop()); @@ -1343,11 +1718,16 @@ pub const RuntimeEnv = struct { .boxed => |boxed| self.push(boxed), else => self.raise(error.TypeMismatch, "{typename} is not unboxable", .{ - .typename = unboxable.get_typename(), + .typename = unboxable.typename(), }), }; } + /// + /// Attempts to pop the top-most value in `self`, unboxing it, and replacing the contents with `value`. + /// + /// A [RuntimeError] is returned if the top-most value is nil or not unboxable. + /// pub fn boxed_set(self: *RuntimeEnv, value: ?*RuntimeObj) RuntimeError!void { const unboxable = try self.expect_object(self.pop()); @@ -1359,16 +1739,19 @@ pub const RuntimeEnv = struct { self.release(unboxed_object); } - unboxed_value.* = if (value) |object| object.acquire() else null; + unboxed_value.* = if (value) |object| object.internal().acquire() else null; }, else => return self.raise(error.TypeMismatch, "{typename} is not unboxable", .{ - .typename = unboxable.get_typename(), + .typename = unboxable.typename(), }), } } }; +/// +/// Archetypes of errors that may occur in the virtual machine during run-time. +/// pub const RuntimeError = coral.io.AllocationError || error { IllegalState, TypeMismatch, @@ -1376,6 +1759,9 @@ pub const RuntimeError = coral.io.AllocationError || error { BadSyntax, }; +/// +/// Runtime-polymorphic data type for representing all data types supported by the virtual machine. +/// pub const RuntimeObj = opaque { const Internal = struct { ref_count: u16, @@ -1427,16 +1813,14 @@ pub const RuntimeObj = opaque { } }, }, + + fn acquire(self: *Internal) *RuntimeObj { + self.ref_count += 1; + + return @ptrCast(self); + } }; - pub fn acquire(self: *const RuntimeObj) *RuntimeObj { - const self_object = self.internal(); - - self_object.ref_count += 1; - - return@ptrCast(self_object); - } - fn allocate(allocator: coral.io.Allocator, data: Internal) coral.io.AllocationError!*RuntimeObj { return @ptrCast(try coral.io.allocate_one(allocator, data)); } @@ -1445,6 +1829,9 @@ pub const RuntimeObj = opaque { return @constCast(@ptrCast(@alignCast(self))); } + /// + /// Returns `true` if the value of `self` is equivalent to `other`, otherwise `false`. + /// pub fn equals(self: *RuntimeObj, other: *RuntimeObj) bool { return switch (self.internal().payload) { .false => other.internal().payload == .false, @@ -1469,13 +1856,13 @@ pub const RuntimeObj = opaque { .boxed => |boxed| if (boxed) |object| self.equals(object) else false, - .vector2 => |lhs_vector| switch (other.internal().payload) { - .vector2 => |rhs_vector| lhs_vector[0] == rhs_vector[0] and lhs_vector[1] == rhs_vector[1], + .vector2 => |lhs_vector2| switch (other.internal().payload) { + .vector2 => |rhs_vector2| lhs_vector2.equals(rhs_vector2), else => false, }, - .vector3 => |lhs_vector| switch (other.internal().payload) { - .vector3 => |rhs_vector| lhs_vector[0] == rhs_vector[0] and lhs_vector[1] == rhs_vector[1] and lhs_vector[2] == rhs_vector[2], + .vector3 => |lhs_vector3| switch (other.internal().payload) { + .vector3 => |rhs_vector3| lhs_vector3.equals(rhs_vector3), else => false, }, @@ -1499,15 +1886,18 @@ pub const RuntimeObj = opaque { }; } - pub fn get_hash(self: *RuntimeObj) usize { + /// + /// Computes a hash for `self` based on the held type and value. + /// + pub fn hash(self: *RuntimeObj) usize { return switch (self.internal().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)), + .vector2 => |vector| @bitCast(vector.to_scalars()), + .vector3 => |vector| coral.io.jenkins_hash(@typeInfo(usize).Int, coral.io.bytes_of(&vector.to_scalars())), .syscall => |syscall| @intFromPtr(syscall), .boxed => |boxed| @intFromPtr(boxed), .string => |string| coral.io.djb2_hash(@typeInfo(usize).Int, string.unpack()), @@ -1515,7 +1905,10 @@ pub const RuntimeObj = opaque { }; } - pub fn get_typename(self: *RuntimeObj) []const coral.io.Byte { + /// + /// Returns the name of the type held in `self`. + /// + pub fn typename(self: *RuntimeObj) []const coral.io.Byte { return switch (self.internal().payload) { .false => "false", .true => "true", @@ -1531,17 +1924,28 @@ pub const RuntimeObj = opaque { }; } - pub fn is_dynamic(self: *RuntimeObj, typeinfo: *const Typeinfo) ?[]u8 { + /// + /// Checks if `self` is a dynamic object with type information matching `typeinfo`, returning the userdata buffer if + /// it is or `null` if it is not. + /// + pub fn is_dynamic(self: *RuntimeObj, typeinfo: *const Typeinfo) ?[]coral.io.Byte { return switch (self.internal().payload) { .dynamic => |dynamic| if (dynamic.typeinfo() == typeinfo) dynamic.userdata() else null, else => null, }; } + /// + /// Checks if `self` is a false object (i.e. any object that evaluates to false), returning `true` if it is or + /// `false` if it is not. + /// pub fn is_false(self: *RuntimeObj) bool { return !self.is_true(); } + /// + /// Checks if `self` is a fixed number object, returning the value if it is or `null` if it is not. + /// pub fn is_fixed(self: *RuntimeObj) ?Fixed { return switch (self.internal().payload) { .fixed => |fixed| fixed, @@ -1549,6 +1953,10 @@ pub const RuntimeObj = opaque { }; } + /// + /// Checks if `self` is a numeric object (i.e. any object that behaves like a number), returning the supertype for + /// further inspection if it is or `null` if it is not. + /// pub fn is_numeric(self: *RuntimeObj) ?Numeric { return switch (self.internal().payload) { .fixed => |fixed| .{.fixed = fixed}, @@ -1559,6 +1967,9 @@ pub const RuntimeObj = opaque { }; } + /// + /// Checks if `self` is a string text object, returning the value if it is or `null` if it is not. + /// pub fn is_string(self: *RuntimeObj) ?[]const coral.io.Byte { return switch (self.internal().payload) { .string => |string| get: { @@ -1572,6 +1983,9 @@ pub const RuntimeObj = opaque { }; } + /// + /// Checks if `self` is a symbo text object, returning the value if it is or `null` if it is not. + /// pub fn is_symbol(self: *RuntimeObj) ?[*:0]const coral.io.Byte { return switch (self.internal().payload) { .symbol => |symbol| symbol, @@ -1579,6 +1993,10 @@ pub const RuntimeObj = opaque { }; } + /// + /// Checks if `self` is a true object (i.e. any object that evaluates to true), returning `true` if it is and + /// `false` if it is not. + /// pub fn is_true(self: *RuntimeObj) bool { return switch (self.internal().payload) { .false => false, @@ -1595,6 +2013,9 @@ pub const RuntimeObj = opaque { }; } + /// + /// Checks if `self` is a 2-component vector number object, returning the value if it is or `null` if it is not. + /// pub fn is_vector2(self: *RuntimeObj) ?Vector2 { return switch (self.internal().payload) { .vector2 => |vector2| vector2, @@ -1602,6 +2023,9 @@ pub const RuntimeObj = opaque { }; } + /// + /// Checks if `self` is a 3-component vector number object, returning the value if it is or `null` if it is not. + /// pub fn is_vector3(self: *RuntimeObj) ?Vector3 { return switch (self.internal().payload) { .vector3 => |vector3| vector3, @@ -1610,38 +2034,124 @@ pub const RuntimeObj = opaque { } }; +/// +/// Bridge function type between the virtual machine and native code. +/// pub const Syscall = fn (env: *RuntimeEnv) RuntimeError!?*RuntimeObj; +/// +/// Type information for dynamically created object types +/// pub const Typeinfo = struct { name: []const coral.io.Byte, size: usize, - destruct: ?*const fn (env: *RuntimeEnv, userdata: []coral.io.Byte) void = null, - to_string: *const fn (env: *RuntimeEnv, userdata: []coral.io.Byte) RuntimeError!*RuntimeObj = default_to_string, - call: *const fn (env: *RuntimeEnv, userdata: []coral.io.Byte) RuntimeError!?*RuntimeObj = default_call, - get: *const fn (env: *RuntimeEnv, userdata: []coral.io.Byte, index: *RuntimeObj) RuntimeError!?*RuntimeObj = default_get, - set: *const fn (env: *RuntimeEnv, userdata: []coral.io.Byte, value: *RuntimeObj, value: ?*RuntimeObj) RuntimeError!void = default_set, + destruct: ?*const fn (context: DestructContext) void = null, + to_string: *const fn (context: ToStringContext) RuntimeError!*RuntimeObj = default_to_string, + call: *const fn (context: CallContext) RuntimeError!?*RuntimeObj = default_call, + get: *const fn (context: GetContext) RuntimeError!?*RuntimeObj = default_get, + set: *const fn (context: SetContext) RuntimeError!void = default_set, - fn default_call(env: *RuntimeEnv, _: []coral.io.Byte) RuntimeError!?*RuntimeObj { - return env.raise(error.BadOperation, "this dynamic object is not callable", .{}); + /// + /// Context used for calling objects. + /// + pub const CallContext = struct { + env: *RuntimeEnv, + userdata: []coral.io.Byte, + }; + + /// + /// Context used with objects that have special destruction semantics + /// + pub const DestructContext = struct { + env: *RuntimeEnv, + userdata: []coral.io.Byte, + }; + + /// + /// Context used for getting indices of objects. + /// + pub const GetContext = struct { + env: *RuntimeEnv, + userdata: []coral.io.Byte, + + /// + /// TODO: + /// + pub fn get_index(self: *const GetContext) *RuntimeObj { + coral.debug.assert(self.env.locals.values.len > 0); + + return self.env.locals.values[self.env.locals.values.len - 1].?.internal().acquire(); + } + }; + + /// + /// Context used for setting indices of objects to new values. + /// + pub const SetContext = struct { + env: *RuntimeEnv, + userdata: []coral.io.Byte, + + /// + /// TODO: + /// + pub fn get_index(self: *const SetContext) *RuntimeObj { + coral.debug.assert(self.env.locals.values.len > 0); + + return self.env.locals.values[self.env.locals.values.len - 1].?.internal().acquire(); + } + + /// + /// TODO: + /// + pub fn get_value(self: *const SetContext) ?*RuntimeObj { + coral.debug.assert(self.env.locals.values.len > 1); + + if (self.env.locals.values[self.env.locals.values.len - 2]) |value| { + return value.internal().acquire(); + } + + return null; + } + }; + + /// + /// Context used for converting objects to string representations. + /// + pub const ToStringContext = struct { + env: *RuntimeEnv, + userdata: []coral.io.Byte, + }; + + fn default_call(context: CallContext) RuntimeError!?*RuntimeObj { + return context.env.raise(error.BadOperation, "this dynamic object is not callable", .{}); } - fn default_get(env: *RuntimeEnv, _: []coral.io.Byte, _: *RuntimeObj) RuntimeError!?*RuntimeObj { - return env.raise(error.BadOperation, "this dynamic object is not get-indexable", .{}); + fn default_get(context: GetContext) RuntimeError!?*RuntimeObj { + return context.env.raise(error.BadOperation, "this dynamic object is not get-indexable", .{}); } - fn default_set(env: *RuntimeEnv, _: []coral.io.Byte, _: *RuntimeObj, _: ?*RuntimeObj) RuntimeError!void { - return env.raise(error.BadOperation, "this dynamic object is not set-indexable", .{}); + fn default_set(context: SetContext) RuntimeError!void { + return context.env.raise(error.BadOperation, "this dynamic object is not set-indexable", .{}); } - fn default_to_string(env: *RuntimeEnv, _: []coral.io.Byte) RuntimeError!*RuntimeObj { - return (try env.new_string("{}")).pop().?; + fn default_to_string(context: ToStringContext) RuntimeError!*RuntimeObj { + return (try context.env.new_string("{}")).pop().?; } }; -pub const Vector2 = [2]f32; +/// +/// 2-component vector type. +/// +pub const Vector2 = gfx.lina.Vector2; -pub const Vector3 = [3]f32; +/// +/// 3-component vector type. +/// +pub const Vector3 = gfx.lina.Vector3; +/// +/// Higher-level wrapper for [RuntimeEnv.index_get] that makes it easier to index [Fixed] keys of objects. +/// pub fn get_at(env: *RuntimeEnv, indexable: *RuntimeObj, index: Fixed) RuntimeError!?*RuntimeObj { const index_number = (try env.new_fixed(index)).pop(); @@ -1650,6 +2160,9 @@ pub fn get_at(env: *RuntimeEnv, indexable: *RuntimeObj, index: Fixed) RuntimeErr return (try (try env.push(indexable)).index_get(index_number)).pop(); } +/// +/// Higher-level wrapper for [RuntimeEnv.index_get] that makes it easier to index field keys of objects. +/// pub fn get_field(env: *RuntimeEnv, indexable: *RuntimeObj, field: []const coral.io.Byte) RuntimeError!?*RuntimeObj { const field_symbol = (try env.new_symbol(field)).pop().?; @@ -1658,6 +2171,9 @@ pub fn get_field(env: *RuntimeEnv, indexable: *RuntimeObj, field: []const coral. return (try (try env.push(indexable)).index_get(field_symbol)).pop(); } +/// +/// Higher-level wrapper for [RuntimeEnv.index_get] that makes it easier to index string keys of objects. +/// pub fn get_key(env: *RuntimeEnv, indexable: *RuntimeObj, key: []const coral.io.Byte) RuntimeError!?*RuntimeObj { const key_string = (try env.new_string(key)).pop(); diff --git a/source/ona/kym/Chunk.zig b/source/ona/kym/Chunk.zig index 8bdf4de..4496bcb 100644 --- a/source/ona/kym/Chunk.zig +++ b/source/ona/kym/Chunk.zig @@ -632,7 +632,7 @@ pub fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.Runtime .push_false => _ = try env.new_boolean(false), .push_const => |push_const| _ = try env.push(try self.get_constant(env, push_const)), .push_local => |push_local| _ = try env.local_get(push_local), - .push_top => _ = try env.local_get(env.local_count() - 1), + .push_top => _ = try env.local_top(), .push_table => |push_table| { const table = (try env.new_table()).pop().?; @@ -708,7 +708,7 @@ pub fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.Runtime .vec3 => syscall_vec3, }), - .set_local => |local_set| _ = env.local_set(local_set, env.pop()), + .set_local => |local_set| _ = try env.local_set(local_set, env.pop()), .get_box => _ = try env.boxed_get(), .set_box => { @@ -765,10 +765,10 @@ pub fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.Runtime defer env.release(addable); _ = switch (try env.expect_numeric(addable)) { - .fixed => |fixed| try env.add_fixed(fixed), - .float => |float| try env.add_float(float), - .vector2 => |vector2| try env.add_vector2(vector2), - .vector3 => |vector3| try env.add_vector3(vector3), + .fixed => |fixed| try env.fixed_add(fixed), + .float => |float| try env.float_add(float), + .vector2 => |vector2| try env.vector2_add(vector2), + .vector3 => |vector3| try env.vector3_add(vector3), }; }, @@ -778,10 +778,10 @@ pub fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.Runtime defer env.release(subtractable); _ = switch (try env.expect_numeric(subtractable)) { - .fixed => |fixed| try env.sub_fixed(fixed), - .float => |float| try env.sub_float(float), - .vector2 => |vector2| try env.sub_vector2(vector2), - .vector3 => |vector3| try env.sub_vector3(vector3), + .fixed => |fixed| try env.fixed_subtract(fixed), + .float => |float| try env.float_subtract(float), + .vector2 => |vector2| try env.vector2_subtract(vector2), + .vector3 => |vector3| try env.vector3_subtract(vector3), }; }, @@ -791,10 +791,10 @@ pub fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.Runtime defer env.release(multiplicable); _ = switch (try env.expect_numeric(multiplicable)) { - .fixed => |fixed| try env.mul_fixed(fixed), - .float => |float| try env.mul_float(float), - .vector2 => |vector2| try env.mul_vector2(vector2), - .vector3 => |vector3| try env.mul_vector3(vector3), + .fixed => |fixed| try env.fixed_multiply(fixed), + .float => |float| try env.float_multiply(float), + .vector2 => |vector2| try env.vector2_multiply(vector2), + .vector3 => |vector3| try env.vector3_multiply(vector3), }; }, @@ -804,10 +804,10 @@ pub fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.Runtime defer env.release(divisible); _ = switch (try env.expect_numeric(divisible)) { - .fixed => |fixed| try env.div_fixed(fixed), - .float => |float| try env.div_float(float), - .vector2 => |vector2| try env.div_vector2(vector2), - .vector3 => |vector3| try env.div_vector3(vector3), + .fixed => |fixed| try env.fixed_divide(fixed), + .float => |float| try env.float_divide(float), + .vector2 => |vector2| try env.vector2_divide(vector2), + .vector3 => |vector3| try env.vector3_divide(vector3), }; }, @@ -951,10 +951,13 @@ fn syscall_vec2(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeObj { if ((try env.arg_get(1)).pop()) |y| { defer env.release(y); - return (try env.new_vector2(x, @floatCast(try env.expect_float(y)))).pop(); + return (try env.new_vector2(.{ + .y = @floatCast(try env.expect_float(y)), + .x = x, + })).pop(); } - return (try env.new_vector2(x, x)).pop(); + return (try env.new_vector2(kym.Vector2.from_scalar(x))).pop(); } fn syscall_vec3(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeObj { @@ -971,21 +974,24 @@ fn syscall_vec3(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeObj { if ((try env.arg_get(1)).pop()) |y| { defer env.release(y); - const z = @as(f32, get_z: { - const z = (try env.arg_get(0)).pop() orelse { - return env.raise(error.BadOperation, - "a third argument is required to create a vector if a first and second exist", .{}); - }; + return (try env.new_vector3(.{ + .z = @as(f32, get_z: { + const z = (try env.arg_get(0)).pop() orelse { + return env.raise(error.BadOperation, + "a third argument is required to create a vector if a first and second exist", .{}); + }; - defer env.release(z); + defer env.release(z); - break: get_z @floatCast(try env.expect_float(z)); - }); + break: get_z @floatCast(try env.expect_float(z)); + }), - return (try env.new_vector3(x, @floatCast(try env.expect_float(y)), z)).pop(); + .y = @floatCast(try env.expect_float(y)), + .x = x, + })).pop(); } - return (try env.new_vector3(x, x, x)).pop(); + return (try env.new_vector3(kym.Vector3.from_scalar(x))).pop(); } pub const typeinfo = &kym.Typeinfo{ @@ -996,16 +1002,16 @@ pub const typeinfo = &kym.Typeinfo{ .to_string = typeinfo_to_string, }; -fn typeinfo_call(env: *kym.RuntimeEnv, userdata: []coral.io.Byte) kym.RuntimeError!?*kym.RuntimeObj { - return @as(*Self, @ptrCast(@alignCast(userdata))).execute(env); +fn typeinfo_call(context: kym.Typeinfo.CallContext) kym.RuntimeError!?*kym.RuntimeObj { + return @as(*Self, @ptrCast(@alignCast(context.userdata))).execute(context.env); } -fn typeinfo_destruct(env: *kym.RuntimeEnv, userdata: []coral.io.Byte) void { - @as(*Self, @ptrCast(@alignCast(userdata))).deinit(env); +fn typeinfo_destruct(context: kym.Typeinfo.DestructContext) void { + @as(*Self, @ptrCast(@alignCast(context.userdata))).deinit(context.env); } -fn typeinfo_to_string(env: *kym.RuntimeEnv, userdata: []coral.io.Byte) kym.RuntimeError!*kym.RuntimeObj { - return (try (try env.push(@as(*Self, @ptrCast(@alignCast(userdata))).name)).to_string()).pop().?; +fn typeinfo_to_string(context: kym.Typeinfo.ToStringContext) kym.RuntimeError!*kym.RuntimeObj { + return (try (try context.env.push(@as(*Self, @ptrCast(@alignCast(context.userdata))).name)).to_string()).pop().?; } pub fn write(self: *Self, line: tokens.Line, opcode: Opcode) coral.io.AllocationError!void { diff --git a/source/ona/kym/Table.zig b/source/ona/kym/Table.zig index 3d02acf..fad9327 100644 --- a/source/ona/kym/Table.zig +++ b/source/ona/kym/Table.zig @@ -8,7 +8,7 @@ contiguous: RefList, const RefList = coral.list.Stack(?*kym.RuntimeObj); const RefTable = coral.map.Table(*kym.RuntimeObj, *kym.RuntimeObj, struct { - pub const hash = kym.RuntimeObj.get_hash; + pub const hash = kym.RuntimeObj.hash; pub const equals = kym.RuntimeObj.equals; }); @@ -51,41 +51,41 @@ pub const typeinfo = &kym.Typeinfo{ .set = typeinfo_set, }; -fn typeinfo_destruct(env: *kym.RuntimeEnv, userdata: []coral.io.Byte) void { - @as(*Self, @ptrCast(@alignCast(userdata))).deinit(env); +fn typeinfo_destruct(context: kym.Typeinfo.DestructContext) void { + @as(*Self, @ptrCast(@alignCast(context.userdata))).deinit(context.env); } -fn typeinfo_get(env: *kym.RuntimeEnv, userdata: []coral.io.Byte, index: *const kym.RuntimeObj) kym.RuntimeError!?*kym.RuntimeObj { - const table = @as(*Self, @ptrCast(@alignCast(userdata))); - const acquired_index = index.acquire(); +fn typeinfo_get(context: kym.Typeinfo.GetContext) kym.RuntimeError!?*kym.RuntimeObj { + const table = @as(*Self, @ptrCast(@alignCast(context.userdata))); + const index = context.get_index(); - defer env.release(acquired_index); + defer context.env.release(index); - if (acquired_index.is_fixed()) |fixed| { + if (index.is_fixed()) |fixed| { if (fixed < 0) { // TODO: Negative indexing. unreachable; } if (fixed < table.contiguous.values.len) { - return (table.contiguous.values[@intCast(fixed)] orelse return null).acquire(); + return table.contiguous.values[@intCast(fixed)]; } } - if (table.associative.lookup(acquired_index)) |value| { - return value.acquire(); + if (table.associative.lookup(index)) |value| { + return value; } return null; } -fn typeinfo_set(env: *kym.RuntimeEnv, userdata: []coral.io.Byte, index: *const kym.RuntimeObj, value: ?*const kym.RuntimeObj) kym.RuntimeError!void { - const table = @as(*Self, @ptrCast(@alignCast(userdata))); - const acquired_index = index.acquire(); +fn typeinfo_set(context: kym.Typeinfo.SetContext) kym.RuntimeError!void { + const table = @as(*Self, @ptrCast(@alignCast(context.userdata))); + const index = context.get_index(); - errdefer env.release(acquired_index); + errdefer context.env.release(index); - if (acquired_index.is_fixed()) |fixed| { + if (index.is_fixed()) |fixed| { if (fixed < 0) { // TODO: Negative indexing. unreachable; @@ -95,28 +95,34 @@ fn typeinfo_set(env: *kym.RuntimeEnv, userdata: []coral.io.Byte, index: *const k const maybe_replacing = &table.contiguous.values[@intCast(fixed)]; if (maybe_replacing.*) |replacing| { - env.release(replacing); + context.env.release(replacing); } - maybe_replacing.* = if (value) |ref| ref.acquire() else null; + if (context.get_value()) |value| { + errdefer context.env.release(value); + + maybe_replacing.* = value; + } else { + maybe_replacing.* = null; + } return; } } - const acquired_value = (value orelse { - if (table.associative.remove(acquired_index)) |removed| { - env.release(removed.key); - env.release(removed.value); + const value = context.get_value() orelse { + if (table.associative.remove(index)) |removed| { + context.env.release(removed.key); + context.env.release(removed.value); } return; - }).acquire(); + }; - errdefer env.release(acquired_value); + errdefer context.env.release(value); - if (try table.associative.replace(acquired_index, acquired_value)) |replaced| { - env.release(replaced.key); - env.release(replaced.value); + if (try table.associative.replace(index, value)) |replaced| { + context.env.release(replaced.key); + context.env.release(replaced.value); } } From 70746b4680f37b15e1756dd7a792941e5c44763f Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 12 Nov 2023 20:12:04 +0000 Subject: [PATCH 4/4] Unify calling interface for object accesses in Kym --- source/ona/kym.zig | 38 +++++++++++++++++++++++++------------- source/ona/kym/Table.zig | 8 ++++---- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/source/ona/kym.zig b/source/ona/kym.zig index 971fe23..09fae63 100644 --- a/source/ona/kym.zig +++ b/source/ona/kym.zig @@ -2075,12 +2075,18 @@ pub const Typeinfo = struct { userdata: []coral.io.Byte, /// - /// TODO: + /// Pushes the index object of the get operation to the top of the stack. /// - pub fn get_index(self: *const GetContext) *RuntimeObj { + /// *Note* the index is guaranteed to be a non-`null` value. + /// + /// A [RuntimeError] is returned if the virtual machine is out of memory. + /// + /// The [RuntimeEnv] is returned for function chaining. + /// + pub fn push_index(self: *const GetContext) RuntimeError!*RuntimeEnv { coral.debug.assert(self.env.locals.values.len > 0); - return self.env.locals.values[self.env.locals.values.len - 1].?.internal().acquire(); + return self.env.push(self.env.locals.values[self.env.locals.values.len - 1]); } }; @@ -2092,25 +2098,31 @@ pub const Typeinfo = struct { userdata: []coral.io.Byte, /// - /// TODO: + /// Pushes the index object of the set operation to the top of the stack. /// - pub fn get_index(self: *const SetContext) *RuntimeObj { + /// *Note* the index is guaranteed to be a non-`null` value. + /// + /// A [RuntimeError] is returned if the virtual machine is out of memory. + /// + /// The [RuntimeEnv] is returned for function chaining. + /// + pub fn push_index(self: *const SetContext) RuntimeError!*RuntimeEnv { coral.debug.assert(self.env.locals.values.len > 0); - return self.env.locals.values[self.env.locals.values.len - 1].?.internal().acquire(); + return self.env.push(self.env.locals.values[self.env.locals.values.len - 1]); } /// - /// TODO: + /// Pushes the value of the set operation to the top of the stack. /// - pub fn get_value(self: *const SetContext) ?*RuntimeObj { + /// A [RuntimeError] is returned if the virtual machine is out of memory. + /// + /// The [RuntimeEnv] is returned for function chaining. + /// + pub fn push_value(self: *const SetContext) RuntimeError!*RuntimeEnv { coral.debug.assert(self.env.locals.values.len > 1); - if (self.env.locals.values[self.env.locals.values.len - 2]) |value| { - return value.internal().acquire(); - } - - return null; + return self.env.push(self.env.locals.values[self.env.locals.values.len - 2]); } }; diff --git a/source/ona/kym/Table.zig b/source/ona/kym/Table.zig index fad9327..26b5d11 100644 --- a/source/ona/kym/Table.zig +++ b/source/ona/kym/Table.zig @@ -57,7 +57,7 @@ fn typeinfo_destruct(context: kym.Typeinfo.DestructContext) void { fn typeinfo_get(context: kym.Typeinfo.GetContext) kym.RuntimeError!?*kym.RuntimeObj { const table = @as(*Self, @ptrCast(@alignCast(context.userdata))); - const index = context.get_index(); + const index = (try context.push_index()).pop().?; defer context.env.release(index); @@ -81,7 +81,7 @@ fn typeinfo_get(context: kym.Typeinfo.GetContext) kym.RuntimeError!?*kym.Runtime fn typeinfo_set(context: kym.Typeinfo.SetContext) kym.RuntimeError!void { const table = @as(*Self, @ptrCast(@alignCast(context.userdata))); - const index = context.get_index(); + const index = (try context.push_index()).pop().?; errdefer context.env.release(index); @@ -98,7 +98,7 @@ fn typeinfo_set(context: kym.Typeinfo.SetContext) kym.RuntimeError!void { context.env.release(replacing); } - if (context.get_value()) |value| { + if ((try context.push_value()).pop()) |value| { errdefer context.env.release(value); maybe_replacing.* = value; @@ -110,7 +110,7 @@ fn typeinfo_set(context: kym.Typeinfo.SetContext) kym.RuntimeError!void { } } - const value = context.get_value() orelse { + const value = (try context.push_value()).pop() orelse { if (table.associative.remove(index)) |removed| { context.env.release(removed.key); context.env.release(removed.value);