From e5c18e4a14730f7de228a68d3a080ee9d4bcaded Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 12 Nov 2023 13:09:30 +0000 Subject: [PATCH] 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{};