From b0aa7166f92f22d43a1777ab23747eea6559df96 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 12 Nov 2023 20:02:47 +0000 Subject: [PATCH] Fill in missing documentation for Kym --- source/ona/kym.zig | 1452 ++++++++++++++++++++++++++------------ source/ona/kym/Chunk.zig | 78 +- source/ona/kym/Table.zig | 60 +- 3 files changed, 1059 insertions(+), 531 deletions(-) diff --git a/source/ona/kym.zig b/source/ona/kym.zig index b1ceea9..971fe23 100644 --- a/source/ona/kym.zig +++ b/source/ona/kym.zig @@ -6,6 +6,8 @@ const coral = @import("coral"); const file = @import("./file.zig"); +const gfx = @import("./gfx.zig"); + const tokens = @import("./kym/tokens.zig"); const tree = @import("./kym/tree.zig"); @@ -33,6 +35,14 @@ pub const Numeric = union (enum) { /// /// Runtime environment virtual machine state. /// +/// The environment operates on a mostly stack-based architecture, with nearly all functions "returning" computations by +/// writing them to the local values stack within. Functions may be chained together to perform sequential mutations on +/// data and either retrieved or discarded once the computation is done via [RuntimeEnv.pop] or [RuntimeEnv.discard] +/// respectively. +/// +/// *Note* that improperly cleaning up data pushed to the stack can result in accidental stack overflows, however also +/// note that all locals are always cleaned when exiting their call frame. +/// pub const RuntimeEnv = struct { allocator: coral.io.Allocator, options: Options, @@ -44,6 +54,16 @@ pub const RuntimeEnv = struct { callable: *RuntimeObj, locals_top: usize, arg_count: u8, + + const Self = @This(); + + fn args_of(self: Self, env: *RuntimeEnv) []?*RuntimeObj { + return env.locals.values[self.locals_top .. (self.locals_top + self.arg_count)]; + } + + fn locals_of(self: Self, env: *RuntimeEnv) []?*RuntimeObj { + return env.locals.values[self.locals_top ..]; + } }); const LocalList = coral.list.Stack(?*RuntimeObj); @@ -64,93 +84,23 @@ pub const RuntimeEnv = struct { const SymbolSet = coral.map.StringTable([:0]coral.io.Byte); - pub fn add_fixed(self: *RuntimeEnv, rhs_fixed: Fixed) RuntimeError!*RuntimeEnv { - const addable = try self.expect_object(self.pop()); - - defer self.release(addable); - - return switch (addable.internal().payload) { - .fixed => |lhs_fixed| if (coral.math.checked_add(lhs_fixed, rhs_fixed)) |result| - self.new_fixed(result) - else - self.new_float(@as(Float, @floatFromInt(lhs_fixed)) + @as(Float, @floatFromInt(rhs_fixed))), - - .float => |lhs_float| self.new_float(lhs_float + @as(Float, @floatFromInt(rhs_fixed))), - - .vector2 => |lhs_vector2| new: { - const rhs_scalar = @as(f32, @floatFromInt(rhs_fixed)); - - break: new self.new_vector2(lhs_vector2[0] + rhs_scalar, lhs_vector2[1] + rhs_scalar); - }, - - .vector3 => |lhs_vector3| new: { - const rhs_scalar = @as(f32, @floatFromInt(rhs_fixed)); - - break: new self.new_vector3(lhs_vector3[0] + rhs_scalar, lhs_vector3[1] + rhs_scalar, lhs_vector3[2] + rhs_scalar); - }, - - else => self.raise(error.TypeMismatch, "fixed types are not addable with {typename}", .{ - .typename = addable.get_typename(), - }), + /// + /// Attempts to push the argument located at `arg_index` to the top of `self`, pushing `null` instead if the given + /// argument does not exist or was provided as a nil value. + /// + /// Arguments are indexed according to the order they are passed, with `0` referring to the first argument position. + /// + /// A [RuntimeError] is returned if `self` is out of memory or the virtual machine is not inside a managed call + /// frame. + /// + /// `self` is returned for function chaining. + /// + pub fn arg_get(self: *RuntimeEnv, arg_index: Local) RuntimeError!*RuntimeEnv { + const frame = self.frames.peek() orelse { + return self.raise(error.IllegalState, "cannot get args outside of a call frame", .{}); }; - } - pub fn add_float(self: *RuntimeEnv, rhs_float: Float) RuntimeError!*RuntimeEnv { - const addable = try self.expect_object(self.pop()); - - defer self.release(addable); - - return switch (addable.internal().payload) { - .fixed => |lhs_fixed| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) + rhs_float), - .float => |lhs_float| self.new_float(lhs_float + lhs_float), - - .vector2 => |lhs_vector2| new: { - const rhs_scalar = @as(f32, @floatCast(rhs_float)); - - break: new self.new_vector2(lhs_vector2[0] + rhs_scalar, lhs_vector2[1] + rhs_scalar); - }, - - .vector3 => |lhs_vector3| new: { - const rhs_scalar = @as(f32, @floatCast(rhs_float)); - - break: new self.new_vector3(lhs_vector3[0] + rhs_scalar, lhs_vector3[1] + rhs_scalar, lhs_vector3[2] + rhs_scalar); - }, - - else => self.raise(error.TypeMismatch, "fixed types are not addable with {typename}", .{ - .typename = addable.get_typename(), - }), - }; - } - - pub fn add_vector2(self: *RuntimeEnv, rhs_vector2: Vector2) RuntimeError!*RuntimeEnv { - const addable = try self.expect_object(self.pop()); - - defer self.release(addable); - - return if (addable.is_vector2()) |lhs_vector2| - self.new_vector2(lhs_vector2[0] + rhs_vector2[0], lhs_vector2[1] + rhs_vector2[1]) - else - self.raise(error.TypeMismatch, "vector2 types are not addable with {typename}", .{ - .typename = addable.get_typename(), - }); - } - - pub fn add_vector3(self: *RuntimeEnv, rhs_vector3: Vector3) RuntimeError!*RuntimeEnv { - const addable = try self.expect_object(self.pop()); - - defer self.release(addable); - - return if (addable.is_vector3()) |lhs_vector3| - self.new_vector3(lhs_vector3[0] + rhs_vector3[0], lhs_vector3[1] + rhs_vector3[1], lhs_vector3[2] + rhs_vector3[2]) - else - self.raise(error.TypeMismatch, "vector3 types are not addable with {typename}", .{ - .typename = addable.get_typename(), - }); - } - - pub fn arg_get(self: *RuntimeEnv, arg_index: LocalIndex) RuntimeError!*RuntimeEnv { - const frame = &self.frames.values[self.frames.values.len - 1]; - const args = self.locals.values[frame.locals_top .. (frame.locals_top + frame.arg_count)]; + const args = frame.args_of(self); if (arg_index < args.len) { return self.push(args[arg_index]); @@ -161,7 +111,20 @@ pub const RuntimeEnv = struct { return self; } - pub fn call(self: *RuntimeEnv, local_arg_count: LocalIndex) RuntimeError!*RuntimeEnv { + /// + /// Attempts to pop the top-most value of `self` and call it with `local_arg_count` as the number of locals prior to + /// it in `self` that are intended to be arguments to it. Once the callable returns, the locals marked as arguments + /// are popped as well. + /// + /// A `local_arg_count` of `0` will call the function with no arguments and pop nothing other than the callable from + /// `self` during invocation. + /// + /// A [RuntimeError] is returned if `self` is out of memory, the top-most local is nil or not callable, or the + /// callable raises a runtime error during invocation. + /// + /// `self` is returned for function chaining. + /// + pub fn call(self: *RuntimeEnv, local_arg_count: Local) RuntimeError!*RuntimeEnv { const callable = try self.expect_object(self.pop()); defer self.release(callable); @@ -169,7 +132,7 @@ pub const RuntimeEnv = struct { const result = get_result: { try self.frames.push_one(.{ .locals_top = self.locals.values.len - local_arg_count, - .callable = callable.acquire(), + .callable = callable.internal().acquire(), .arg_count = local_arg_count, }); @@ -190,10 +153,14 @@ pub const RuntimeEnv = struct { break: get_result try switch (frame.callable.internal().payload) { .syscall => |syscall| syscall(self), - .dynamic => |dynamic| dynamic.typeinfo().call(self, dynamic.userdata()), + + .dynamic => |dynamic| dynamic.typeinfo().call(.{ + .userdata = dynamic.userdata(), + .env = self, + }), else => self.raise(error.TypeMismatch, "{typename} is not callable", .{ - .typename = frame.callable.get_typename(), + .typename = frame.callable.typename(), }), }; }; @@ -209,6 +176,15 @@ pub const RuntimeEnv = struct { return self; } + /// + /// Attempts to pop and compare the top-most local in `self` with `rhs_comparable`, pushing a true value if the + /// local is greater than `rhs_comparable`, otherwise pushing a false value. + /// + /// A [RuntimeError] is returned if `self` is out of memory, the top-most local is nil or not comparable, or + /// `rhs_comparable` is not comparable. + /// + /// `self` is returned for function chaining. + /// pub fn compare_greater(self: *RuntimeEnv, rhs_comparable: *RuntimeObj) RuntimeError!*RuntimeEnv { const lhs_comparable = try self.expect_object(self.pop()); @@ -220,7 +196,7 @@ pub const RuntimeEnv = struct { .float => |rhs_float| self.new_boolean(@as(Float, @floatFromInt(lhs_fixed)) > rhs_float), else => self.raise(error.TypeMismatch, "right-hand {typename} is not comparable", .{ - .typename = rhs_comparable.get_typename(), + .typename = rhs_comparable.typename(), }), }, @@ -229,16 +205,25 @@ pub const RuntimeEnv = struct { .fixed => |rhs_fixed| self.new_boolean(lhs_float > @as(Float, @floatFromInt(rhs_fixed))), else => self.raise(error.TypeMismatch, "right-hand {typename} is not comparable", .{ - .typename = rhs_comparable.get_typename(), + .typename = rhs_comparable.typename(), }), }, else => self.raise(error.TypeMismatch, "left-hand {typename} is not comparable", .{ - .typename = lhs_comparable.get_typename(), + .typename = lhs_comparable.typename(), }), }; } + /// + /// Attempts to pop and compare the top-most local in `self` with `rhs_comparable`, pushing a true value if the + /// local is greater than or equal to `rhs_comparable`, otherwise pushing a false value. + /// + /// A [RuntimeError] is returned if `self` is out of memory, the top-most local is nil or not comparable, or + /// `rhs_comparable` is not comparable. + /// + /// `self` is returned for function chaining. + /// pub fn compare_greater_equals(self: *RuntimeEnv, rhs_comparable: *RuntimeObj) RuntimeError!*RuntimeEnv { const lhs_comparable = try self.expect_object(self.pop()); @@ -250,7 +235,7 @@ pub const RuntimeEnv = struct { .float => |rhs_float| self.new_boolean(@as(Float, @floatFromInt(lhs_fixed)) >= rhs_float), else => self.raise(error.TypeMismatch, "right-hand {typename} is not comparable", .{ - .typename = rhs_comparable.get_typename(), + .typename = rhs_comparable.typename(), }), }, @@ -259,16 +244,25 @@ pub const RuntimeEnv = struct { .fixed => |rhs_fixed| self.new_boolean(lhs_float >= @as(Float, @floatFromInt(rhs_fixed))), else => self.raise(error.TypeMismatch, "right-hand {typename} is not comparable", .{ - .typename = rhs_comparable.get_typename(), + .typename = rhs_comparable.typename(), }), }, else => self.raise(error.TypeMismatch, "left-hand {typename} is not comparable", .{ - .typename = lhs_comparable.get_typename(), + .typename = lhs_comparable.typename(), }), }; } + /// + /// Attempts to pop and compare the top-most local in `self` with `rhs_comparable`, pushing a true value if the + /// local is less than `rhs_comparable`, otherwise pushing a false value. + /// + /// A [RuntimeError] is returned if `self` is out of memory, the top-most local is nil or not comparable, or + /// `rhs_comparable` is not comparable. + /// + /// `self` is returned for function chaining. + /// pub fn compare_less(self: *RuntimeEnv, rhs_comparable: *RuntimeObj) RuntimeError!*RuntimeEnv { const lhs_comparable = try self.expect_object(self.pop()); @@ -280,7 +274,7 @@ pub const RuntimeEnv = struct { .float => |rhs_float| self.new_boolean(@as(Float, @floatFromInt(lhs_fixed)) < rhs_float), else => return self.raise(error.TypeMismatch, "right-hand {typename} is not comparable", .{ - .typename = rhs_comparable.get_typename(), + .typename = rhs_comparable.typename(), }), }, @@ -289,16 +283,25 @@ pub const RuntimeEnv = struct { .fixed => |rhs_fixed| self.new_boolean(lhs_float < @as(Float, @floatFromInt(rhs_fixed))), else => return self.raise(error.TypeMismatch, "right-hand {typename} is not comparable", .{ - .typename = rhs_comparable.get_typename(), + .typename = rhs_comparable.typename(), }), }, else => return self.raise(error.TypeMismatch, "left-hand {typename} is not comparable", .{ - .typename = lhs_comparable.get_typename(), + .typename = lhs_comparable.typename(), }), }; } + /// + /// Attempts to pop and compare the top-most local in `self` with `rhs_comparable`, pushing a true value if the + /// local is less than or equal to `rhs_comparable`, otherwise pushing a false value. + /// + /// A [RuntimeError] is returned if `self` is out of memory, the top-most local is nil or not comparable, or + /// `rhs_comparable` is not comparable. + /// + /// `self` is returned for function chaining. + /// pub fn compare_less_equals(self: *RuntimeEnv, rhs_comparable: *RuntimeObj) RuntimeError!*RuntimeEnv { const lhs_comparable = try self.expect_object(self.pop()); @@ -310,7 +313,7 @@ pub const RuntimeEnv = struct { .float => |rhs_float| self.new_boolean(@as(Float, @floatFromInt(lhs_fixed)) <= rhs_float), else => return self.raise(error.TypeMismatch, "right-hand {typename} is not comparable", .{ - .typename = rhs_comparable.get_typename(), + .typename = rhs_comparable.typename(), }), }, @@ -319,17 +322,28 @@ pub const RuntimeEnv = struct { .fixed => |rhs_fixed| self.new_boolean(lhs_float <= @as(Float, @floatFromInt(rhs_fixed))), else => return self.raise(error.TypeMismatch, "right-hand {typename} is not comparable", .{ - .typename = rhs_comparable.get_typename(), + .typename = rhs_comparable.typename(), }), }, else => return self.raise(error.TypeMismatch, "left-hand {typename} is not comparable", .{ - .typename = lhs_comparable.get_typename(), + .typename = lhs_comparable.typename(), }), }; } - pub fn concat(self: *RuntimeEnv, local_concat_count: LocalIndex) RuntimeError!*RuntimeEnv { + /// + /// Attempts to pop `local_concat_count` number of locals from the stack and concatenate them together as one long + /// string object, pushing the result to the top of `self`. + /// + /// A `local_concat_count` of `0` will push an empty string object and pop nothing from `self`. + /// + /// A [RuntimeError] is returned if `self` is out of memory or one of the concatenated objects raises an error from + /// within it's to_string implementation. + /// + /// `self` is returned for function chaining. + /// + pub fn concat(self: *RuntimeEnv, local_concat_count: Local) RuntimeError!*RuntimeEnv { if (local_concat_count == 0) { return self.new_string(""); } @@ -403,108 +417,15 @@ pub const RuntimeEnv = struct { self.frames.deinit(); } + /// + /// Pops the top-most value of `self` and releases it. + /// pub fn discard(self: *RuntimeEnv) void { if (self.pop()) |popped| { self.release(popped); } } - pub fn div_fixed(self: *RuntimeEnv, rhs_fixed: Fixed) RuntimeError!*RuntimeEnv { - if (rhs_fixed == 0) { - return self.raise(error.BadOperation, "cannot divide by zero", .{}); - } - - const addable = try self.expect_object(self.pop()); - - defer self.release(addable); - - return switch (addable.internal().payload) { - .fixed => |lhs_fixed| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) / @as(Float, @floatFromInt(rhs_fixed))), - .float => |lhs_float| self.new_float(lhs_float / @as(Float, @floatFromInt(rhs_fixed))), - - .vector2 => |lhs_vector2| new: { - const rhs_scalar = @as(f32, @floatFromInt(rhs_fixed)); - - break: new self.new_vector2(lhs_vector2[0] / rhs_scalar, lhs_vector2[1] / rhs_scalar); - }, - - .vector3 => |lhs_vector3| new: { - const rhs_scalar = @as(f32, @floatFromInt(rhs_fixed)); - - break: new self.new_vector3(lhs_vector3[0] / rhs_scalar, lhs_vector3[1] / rhs_scalar, lhs_vector3[2] / rhs_scalar); - }, - - else => self.raise(error.TypeMismatch, "fixed types are not divisible with {typename}", .{ - .typename = addable.get_typename(), - }), - }; - } - - pub fn div_float(self: *RuntimeEnv, rhs_float: Float) RuntimeError!*RuntimeEnv { - if (rhs_float == 0) { - return self.raise(error.BadOperation, "cannot divide by zero", .{}); - } - - const addable = try self.expect_object(self.pop()); - - defer self.release(addable); - - return switch (addable.internal().payload) { - .fixed => |lhs_fixed| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) / rhs_float), - .float => |lhs_float| self.new_float(lhs_float / lhs_float), - - .vector2 => |lhs_vector2| new: { - const rhs_scalar = @as(f32, @floatCast(rhs_float)); - - break: new self.new_vector2(lhs_vector2[0] / rhs_scalar, lhs_vector2[1] / rhs_scalar); - }, - - .vector3 => |lhs_vector3| new: { - const rhs_scalar = @as(f32, @floatCast(rhs_float)); - - break: new self.new_vector3(lhs_vector3[0] / rhs_scalar, lhs_vector3[1] / rhs_scalar, lhs_vector3[2] / rhs_scalar); - }, - - else => self.raise(error.TypeMismatch, "fixed types are not divisible with {typename}", .{ - .typename = addable.get_typename(), - }), - }; - } - - pub fn div_vector2(self: *RuntimeEnv, rhs_vector2: Vector2) RuntimeError!*RuntimeEnv { - if (rhs_vector2[0] == 0 or rhs_vector2[1] == 0) { - return self.raise(error.BadOperation, "cannot divide by zero", .{}); - } - - const addable = try self.expect_object(self.pop()); - - defer self.release(addable); - - return if (addable.is_vector2()) |lhs_vector2| - self.new_vector2(lhs_vector2[0] / rhs_vector2[0], lhs_vector2[1] / rhs_vector2[1]) - else - self.raise(error.TypeMismatch, "vector2 types are not divisible with {typename}", .{ - .typename = addable.get_typename(), - }); - } - - pub fn div_vector3(self: *RuntimeEnv, rhs_vector3: Vector3) RuntimeError!*RuntimeEnv { - if (rhs_vector3[0] == 0 or rhs_vector3[1] == 0 or rhs_vector3[2] == 0) { - return self.raise(error.BadOperation, "cannot divide by zero", .{}); - } - - const addable = try self.expect_object(self.pop()); - - defer self.release(addable); - - return if (addable.is_vector3()) |lhs_vector3| - self.new_vector3(lhs_vector3[0] / rhs_vector3[0], lhs_vector3[1] / rhs_vector3[1], lhs_vector3[2] / rhs_vector3[2]) - else - self.raise(error.TypeMismatch, "vector3 types are not divisible with {typename}", .{ - .typename = addable.get_typename(), - }); - } - pub fn equals_nil(self: *RuntimeEnv) RuntimeError!*RuntimeEnv { if (self.pop()) |lhs_object| { defer self.release(lhs_object); @@ -527,7 +448,7 @@ pub const RuntimeEnv = struct { pub fn expect_dynamic(self: *RuntimeEnv, value: *RuntimeObj, typeinfo: *const Typeinfo) RuntimeError![]coral.io.Byte { return value.is_dynamic(typeinfo) orelse self.raise(error.TypeMismatch, "expected dynamic object, not {typename}", .{ - .typename = value.get_typename(), + .typename = value.typename(), }); } @@ -537,20 +458,20 @@ pub const RuntimeEnv = struct { .float => |float| float, else => self.raise(error.TypeMismatch, "expected float type, not {typename}", .{ - .typename = value.get_typename(), + .typename = value.typename(), }), }; } pub fn expect_fixed(self: *RuntimeEnv, value: *RuntimeObj) RuntimeError!Fixed { return value.is_fixed() orelse self.raise(error.TypeMismatch, "expected fixed type, not {typename}", .{ - .typename = value.get_typename(), + .typename = value.typename(), }); } pub fn expect_numeric(self: *RuntimeEnv, value: *RuntimeObj) RuntimeError!Numeric { return value.is_numeric() orelse self.raise(error.TypeMismatch, "expected numeric type, not {typename}", .{ - .typename = value.get_typename(), + .typename = value.typename(), }); } @@ -560,16 +481,240 @@ pub const RuntimeEnv = struct { pub fn expect_string(self: *RuntimeEnv, value: *RuntimeObj) RuntimeError![]const coral.io.Byte { return value.is_string() orelse self.raise(error.TypeMismatch, "expected string type, not {typename}", .{ - .typename = value.get_typename(), + .typename = value.typename(), }); } pub fn expect_symbol(self: *RuntimeEnv, value: *RuntimeObj) RuntimeError![*:0]const coral.io.Byte { return value.is_symbol() orelse self.raise(error.TypeMismatch, "expected symbol type, not {typename}", .{ - .typename = value.get_typename(), + .typename = value.typename(), }); } + /// + /// Attempts to pop the top-most value in `self` and add `rhs_fixed` to it, pushing the result. + /// + /// A [RuntimeError] is returned if `self` is out of memory or `rhs_fixed` is not addable with the top-most local. + /// + /// `self` is returned for function chaining. + /// + pub fn fixed_add(self: *RuntimeEnv, rhs_fixed: Fixed) RuntimeError!*RuntimeEnv { + const addable = try self.expect_object(self.pop()); + + defer self.release(addable); + + return switch (addable.internal().payload) { + .fixed => |lhs_fixed| if (coral.math.checked_add(lhs_fixed, rhs_fixed)) |result| + self.new_fixed(result) + else + self.new_float(@as(Float, @floatFromInt(lhs_fixed)) + @as(Float, @floatFromInt(rhs_fixed))), + + .float => |lhs_float| self.new_float(lhs_float + @as(Float, @floatFromInt(rhs_fixed))), + .vector2 => |lhs_vector2| self.new_vector2(lhs_vector2.scalar_added(@floatFromInt(rhs_fixed))), + .vector3 => |lhs_vector3| self.new_vector3(lhs_vector3.scalar_added(@floatFromInt(rhs_fixed))), + + else => self.raise(error.TypeMismatch, "fixed types are not addable with {typename}", .{ + .typename = addable.typename(), + }), + }; + } + + /// + /// Attempts to pop the top-most value in `self` and divide it with `rhs_fixed` to it, pushing the result. + /// + /// A [RuntimeError] is returned if `self` is out of memory or `rhs_fixed` is not divisible with the top-most local. + /// + /// `self` is returned for function chaining. + /// + pub fn fixed_divide(self: *RuntimeEnv, rhs_fixed: Fixed) RuntimeError!*RuntimeEnv { + if (rhs_fixed == 0) { + return self.raise(error.TypeMismatch, "cannot divide by zero", .{}); + } + + const addable = try self.expect_object(self.pop()); + + defer self.release(addable); + + return switch (addable.internal().payload) { + .fixed => |lhs_fixed| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) / @as(Float, @floatFromInt(rhs_fixed))), + .float => |lhs_float| self.new_float(lhs_float / @as(Float, @floatFromInt(rhs_fixed))), + .vector2 => |lhs_vector2| self.new_vector2(lhs_vector2.scalar_divided(@floatFromInt(rhs_fixed))), + .vector3 => |lhs_vector3| self.new_vector3(lhs_vector3.scalar_divided(@floatFromInt(rhs_fixed))), + + else => self.raise(error.TypeMismatch, "fixed types are not divisible with {typename}", .{ + .typename = addable.typename(), + }), + }; + } + + /// + /// Attempts to pop the top-most value in `self` and multiply it with `rhs_fixed` to it, pushing the result. + /// + /// A [RuntimeError] is returned if `self` is out of memory or `rhs_fixed` is not multiplicable with the top-most + /// local. + /// + /// `self` is returned for function chaining. + /// + pub fn fixed_multiply(self: *RuntimeEnv, rhs_fixed: Fixed) RuntimeError!*RuntimeEnv { + const addable = try self.expect_object(self.pop()); + + defer self.release(addable); + + return switch (addable.internal().payload) { + .fixed => |lhs_fixed| if (coral.math.checked_mul(lhs_fixed, rhs_fixed)) |result| + self.new_fixed(result) + else + self.new_float(@as(Float, @floatFromInt(lhs_fixed)) * @as(Float, @floatFromInt(rhs_fixed))), + + .float => |lhs_float| self.new_float(lhs_float + @as(Float, @floatFromInt(rhs_fixed))), + .vector2 => |lhs_vector2| self.new_vector2(lhs_vector2.scalar_multiplied(@floatFromInt(rhs_fixed))), + .vector3 => |lhs_vector3| self.new_vector3(lhs_vector3.scalar_multiplied(@floatFromInt(rhs_fixed))), + + else => self.raise(error.TypeMismatch, "fixed types are not multiplicable with {typename}", .{ + .typename = addable.typename(), + }), + }; + } + + /// + /// Attempts to pop the top-most value in `self` and subtract it with `rhs_fixed`, pushing the result. + /// + /// A [RuntimeError] is returned if `self` is out of memory or `rhs_fixed` is not subtractable with the top-most + /// local. + /// + /// `self` is returned for function chaining. + /// + pub fn fixed_subtract(self: *RuntimeEnv, rhs_fixed: Fixed) RuntimeError!*RuntimeEnv { + const addable = try self.expect_object(self.pop()); + + defer self.release(addable); + + return switch (addable.internal().payload) { + .fixed => |lhs_fixed| if (coral.math.checked_sub(lhs_fixed, rhs_fixed)) |result| + self.new_fixed(result) + else + self.new_float(@as(Float, @floatFromInt(lhs_fixed)) - @as(Float, @floatFromInt(rhs_fixed))), + + .float => |lhs_float| self.new_float(lhs_float + @as(Float, @floatFromInt(rhs_fixed))), + .vector2 => |lhs_vector2| self.new_vector2(lhs_vector2.scalar_subtracted(@floatFromInt(rhs_fixed))), + .vector3 => |lhs_vector3| self.new_vector3(lhs_vector3.scalar_subtracted(@floatFromInt(rhs_fixed))), + + else => self.raise(error.TypeMismatch, "fixed types are not multiplicable with {typename}", .{ + .typename = addable.typename(), + }), + }; + } + + /// + /// Attempts to pop the top-most value in `self` and add it with `rhs_float`, pushing the result. + /// + /// A [RuntimeError] is returned if `self` is out of memory or `rhs_float` is not addable with the top-most local. + /// + /// `self` is returned for function chaining. + /// + pub fn float_add(self: *RuntimeEnv, rhs_float: Float) RuntimeError!*RuntimeEnv { + const addable = try self.expect_object(self.pop()); + + defer self.release(addable); + + return switch (addable.internal().payload) { + .fixed => |lhs_fixed| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) + rhs_float), + .float => |lhs_float| self.new_float(lhs_float + lhs_float), + .vector2 => |lhs_vector2| self.new_vector2(lhs_vector2.scalar_added(@floatCast(rhs_float))), + .vector3 => |lhs_vector3| self.new_vector3(lhs_vector3.scalar_added(@floatCast(rhs_float))), + + else => self.raise(error.TypeMismatch, "fixed types are not addable with {typename}", .{ + .typename = addable.typename(), + }), + }; + } + + /// + /// Attempts to pop the top-most value in `self` and divide it with `rhs_float` to it, pushing the result. + /// + /// A [RuntimeError] is returned if `self` is out of memory or `rhs_float` is not divisible with the top-most local. + /// + /// `self` is returned for function chaining. + /// + pub fn float_divide(self: *RuntimeEnv, rhs_float: Float) RuntimeError!*RuntimeEnv { + if (rhs_float == 0) { + return self.raise(error.TypeMismatch, "cannot divide by zero", .{}); + } + + const addable = try self.expect_object(self.pop()); + + defer self.release(addable); + + return switch (addable.internal().payload) { + .fixed => |lhs_fixed| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) / rhs_float), + .float => |lhs_float| self.new_float(lhs_float / lhs_float), + .vector2 => |lhs_vector2| self.new_vector2(lhs_vector2.scalar_divided(@floatCast(rhs_float))), + .vector3 => |lhs_vector3| self.new_vector3(lhs_vector3.scalar_divided(@floatCast(rhs_float))), + + else => self.raise(error.TypeMismatch, "fixed types are not divisible with {typename}", .{ + .typename = addable.typename(), + }), + }; + } + + /// + /// Attempts to pop the top-most value in `self` and multiply it with `rhs_float`, pushing the result. + /// + /// A [RuntimeError] is returned if `self` is out of memory or `rhs_float` is not multiplicable with the top-most + /// local. + /// + /// `self` is returned for function chaining. + /// + pub fn float_multiply(self: *RuntimeEnv, rhs_float: Float) RuntimeError!*RuntimeEnv { + const addable = try self.expect_object(self.pop()); + + defer self.release(addable); + + return switch (addable.internal().payload) { + .fixed => |lhs_fixed| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) * rhs_float), + .float => |lhs_float| self.new_float(lhs_float * lhs_float), + .vector2 => |lhs_vector2| self.new_vector2(lhs_vector2.scalar_multiplied(@floatCast(rhs_float))), + .vector3 => |lhs_vector3| self.new_vector3(lhs_vector3.scalar_multiplied(@floatCast(rhs_float))), + + else => self.raise(error.TypeMismatch, "fixed types are not multiplicable with {typename}", .{ + .typename = addable.typename(), + }), + }; + } + + /// + /// Attempts to pop the top-most value in `self` and subtract it with `rhs_float`, pushing the result. + /// + /// A [RuntimeError] is returned if `self` is out of memory or `rhs_float` is not subtractable with the top-most + /// local. + /// + /// `self` is returned for function chaining. + /// + pub fn float_subtract(self: *RuntimeEnv, rhs_float: Float) RuntimeError!*RuntimeEnv { + const addable = try self.expect_object(self.pop()); + + defer self.release(addable); + + return switch (addable.internal().payload) { + .fixed => |lhs_fixed| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) - rhs_float), + .float => |lhs_float| self.new_float(lhs_float - lhs_float), + .vector2 => |lhs_vector2| self.new_vector2(lhs_vector2.scalar_subtracted(@floatCast(rhs_float))), + .vector3 => |lhs_vector3| self.new_vector3(lhs_vector3.scalar_subtracted(@floatCast(rhs_float))), + + else => self.raise(error.TypeMismatch, "fixed types are not multiplicable with {typename}", .{ + .typename = addable.typename(), + }), + }; + } + + /// + /// Attempts to pop the top-most value and index into it with `index`, pushing the retrieved value. + /// + /// A [RuntimeError] is returned if `self` is out of memory, the top-most local is nil or not indexable, or the + /// object-specific indexing operation raised an error. + /// + /// `self` is returned for function chaining. + /// pub fn index_get(self: *RuntimeEnv, index: *RuntimeObj) RuntimeError!*RuntimeEnv { const vector_max = 3; const indexable = try self.expect_object(self.pop()); @@ -590,13 +735,18 @@ pub const RuntimeEnv = struct { } swizzle_buffer[swizzle_count] = switch (swizzle_symbol[swizzle_count]) { - 'x' => vector2[0], - 'y' => vector2[1], + 'x' => vector2.x, + 'y' => vector2.y, 0 => return switch (swizzle_count) { 1 => self.new_float(swizzle_buffer[0]), - 2 => self.new_vector2(swizzle_buffer[0], swizzle_buffer[1]), - 3 => self.new_vector3(swizzle_buffer[0], swizzle_buffer[1], swizzle_buffer[2]), + + 2 => self.new_vector2(.{ + .x = swizzle_buffer[0], + .y = swizzle_buffer[1], + }), + + 3 => self.new_vector3(Vector3.from_scalars(swizzle_buffer)), else => unreachable, }, @@ -620,14 +770,19 @@ pub const RuntimeEnv = struct { } swizzle_buffer[swizzle_count] = switch (swizzle_symbol[swizzle_count]) { - 'x' => vector3[0], - 'y' => vector3[1], - 'z' => vector3[2], + 'x' => vector3.x, + 'y' => vector3.y, + 'z' => vector3.z, 0 => return switch (swizzle_count) { 1 => self.new_float(swizzle_buffer[0]), - 2 => self.new_vector2(swizzle_buffer[0], swizzle_buffer[1]), - 3 => self.new_vector3(swizzle_buffer[0], swizzle_buffer[1], swizzle_buffer[2]), + + 2 => self.new_vector2(.{ + .x = swizzle_buffer[0], + .y = swizzle_buffer[1], + }), + + 3 => self.new_vector3(Vector3.from_scalars(swizzle_buffer)), else => unreachable, }, @@ -639,39 +794,75 @@ pub const RuntimeEnv = struct { }, .dynamic => |dynamic| { - const value = try dynamic.typeinfo().get(self, dynamic.userdata(), index); + return self.push(get_value: { + try self.locals.push_one(index); - errdefer { - if (value) |object| { - self.release(object); - } - } + defer coral.debug.assert(self.locals.drop(1)); - try self.locals.push_one(value); - - return self; + break: get_value try dynamic.typeinfo().get(.{ + .userdata = dynamic.userdata(), + .env = self, + }); + }); }, else => return self.raise(error.TypeMismatch, "{typename} is not get-indexable", .{ - .typename = indexable.get_typename(), + .typename = indexable.typename(), }), } } + /// + /// Attempts to pop the top-most value and index into it with `index`, inserting `value` into it. + /// + /// A [RuntimeError] is returned if `self` is out of memory, the top-most local is nil or not indexable, or the + /// object-specific indexing operation raised an error. + /// pub fn index_set(self: *RuntimeEnv, index: *RuntimeObj, value: ?*RuntimeObj) RuntimeError!void { const indexable = try self.expect_object(self.pop()); defer self.release(indexable); try switch (indexable.internal().payload) { - .dynamic => |dynamic| dynamic.typeinfo().set(self, dynamic.userdata(), index, value), + .dynamic => |dynamic| set_value: { + try self.locals.push_one(value); + + defer coral.debug.assert(self.locals.pop() != null); + + try self.locals.push_one(index); + + defer coral.debug.assert(self.locals.pop() != null); + + break: set_value dynamic.typeinfo().set(.{ + .userdata = dynamic.userdata(), + .env = self, + }); + }, else => self.raise(error.TypeMismatch, "{typename} is not set-indexable", .{ - .typename = indexable.get_typename(), + .typename = indexable.typename(), }), }; } + /// + /// Attempts to import a script from the import [file.Access] (specified during [RuntimeEnv.init]) from `file_path`. + /// + /// If the loaded script is a plain text file, it will be read, parsed, and compiled into an optimized format before + /// being executed. + /// + /// If the loaded script is already compiled, it is immediately executed. + /// + /// After completing execution, the return value is pushed to the top of `self`. + /// + /// A [RuntimeError] is returned if `self` is out of memory, no file at `file_path` could be found by the runtime, + /// the script is a source file and contains invalid syntax, or the imported script raised an error during + /// execution. + /// + /// Any syntax errors are reported using [print_error] prior to raising a runtime error. + /// + /// `self` is returned for function chaining. + /// pub fn import(self: *RuntimeEnv, file_path: file.Path) RuntimeError!*RuntimeEnv { { var callable = new_chunk: { @@ -695,7 +886,7 @@ pub const RuntimeEnv = struct { root.parse(&stream) catch |parse_error| { for (root.error_messages.values) |error_message| { - self.print(error_message); + self.print_error(error_message); } return self.raise(parse_error, "failed to parse `{name}`", .{.name = file_name}); @@ -745,117 +936,95 @@ pub const RuntimeEnv = struct { }; } - const LocalIndex = u8; + const Local = u8; - const local_max = coral.math.max_int(@typeInfo(LocalIndex).Int); + const local_max = coral.math.max_int(@typeInfo(Local).Int); - pub fn local_get(self: *RuntimeEnv, index: LocalIndex) RuntimeError!*RuntimeEnv { - const frame = &self.frames.values[self.frames.values.len - 1]; + /// + /// Attempts to push the local located at `index` to the top of `self`. + /// + /// Locals are indexed according to the order they are declared, with `0` referring to the first declared arg, let, + /// or var in the current frame. + /// + /// A [RuntimeError] is returned if `self` is out of memory, `index` does not refer to a valid local, or the + /// virtual-machine is not in a managed call frame. + /// + /// `self` is returned for function chaining. + /// + pub fn local_get(self: *RuntimeEnv, index: Local) RuntimeError!*RuntimeEnv { + const frame = self.frames.peek() orelse { + return self.raise(error.IllegalState, "cannot get locals outside of a call frame", .{}); + }; - return self.push(self.locals.values[frame.locals_top + index]); + const locals = frame.locals_of(self); + + if (index >= locals.len) { + return self.raise(error.IllegalState, "invalid local get", .{}); + } + + return self.push(locals[index]); } - pub fn local_set(self: *RuntimeEnv, index: LocalIndex, value: ?*RuntimeObj) void { - const frame = &self.frames.values[self.frames.values.len - 1]; - const local = &self.locals.values[frame.locals_top + index]; + /// + /// Attempts to set the local located at `index` to `value`. + /// + /// Locals are indexed according to the order they are declared, with `0` referring to the first declared arg, let, + /// or var in the current frame. + /// + /// A [RuntimeError] if`index` does not refer to a valid local, or the virtual-machine is not in a managed call + /// frame. + /// + /// `self` is returned for function chaining. + /// + pub fn local_set(self: *RuntimeEnv, index: Local, value: ?*RuntimeObj) RuntimeError!void { + const frame = self.frames.peek() orelse { + return self.raise(error.IllegalState, "cannot get locals outside of a call frame", .{}); + }; + + const locals = frame.locals_of(self); + + if (index >= locals.len) { + return self.raise(error.IllegalState, "invalid local get", .{}); + } + + const local = &locals[index]; if (local.*) |previous_local| { self.release(previous_local); } - local.* = if (value) |object| object.acquire() else null; + local.* = if (value) |object| object.internal().acquire() else null; } - pub fn local_count(self: *RuntimeEnv) LocalIndex { - const frame = &self.frames.values[self.frames.values.len - 1]; - - return @intCast(self.locals.values[frame.locals_top ..].len); - } - - pub fn mul_fixed(self: *RuntimeEnv, rhs_fixed: Fixed) RuntimeError!*RuntimeEnv { - const addable = try self.expect_object(self.pop()); - - defer self.release(addable); - - return switch (addable.internal().payload) { - .fixed => |lhs_fixed| if (coral.math.checked_mul(lhs_fixed, rhs_fixed)) |result| - self.new_fixed(result) - else - self.new_float(@as(Float, @floatFromInt(lhs_fixed)) * @as(Float, @floatFromInt(rhs_fixed))), - - .float => |lhs_float| self.new_float(lhs_float + @as(Float, @floatFromInt(rhs_fixed))), - - .vector2 => |lhs_vector2| new: { - const rhs_scalar = @as(f32, @floatFromInt(rhs_fixed)); - - break: new self.new_vector2(lhs_vector2[0] * rhs_scalar, lhs_vector2[1] * rhs_scalar); - }, - - .vector3 => |lhs_vector3| new: { - const rhs_scalar = @as(f32, @floatFromInt(rhs_fixed)); - - break: new self.new_vector3(lhs_vector3[0] * rhs_scalar, lhs_vector3[1] * rhs_scalar, lhs_vector3[2] * rhs_scalar); - }, - - else => self.raise(error.TypeMismatch, "fixed types are not multiplicable with {typename}", .{ - .typename = addable.get_typename(), - }), + /// + /// Attempts to push a copy of the top-most local in `self`. + /// + /// A [RuntimeError] is returned if `self` is out of memory, the virtual-machine is not in a managed call frame, or + /// there are no locals in the frame. + /// + /// `self` is returned for function chaining. + /// + pub fn local_top(self: *RuntimeEnv) RuntimeError!*RuntimeEnv { + const frame = self.frames.peek() orelse { + return self.raise(error.IllegalState, "cannot get locals outside of a call frame", .{}); }; + + const locals = frame.locals_of(self); + + if (locals.len != 0) { + return self.raise(error.IllegalState, "invalid local top", .{}); + } + + return self.push(locals[locals.len - 1]); } - pub fn mul_float(self: *RuntimeEnv, rhs_float: Float) RuntimeError!*RuntimeEnv { - const addable = try self.expect_object(self.pop()); - - defer self.release(addable); - - return switch (addable.internal().payload) { - .fixed => |lhs_fixed| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) * rhs_float), - .float => |lhs_float| self.new_float(lhs_float * lhs_float), - - .vector2 => |lhs_vector2| new: { - const rhs_scalar = @as(f32, @floatCast(rhs_float)); - - break: new self.new_vector2(lhs_vector2[0] * rhs_scalar, lhs_vector2[1] * rhs_scalar); - }, - - .vector3 => |lhs_vector3| new: { - const rhs_scalar = @as(f32, @floatCast(rhs_float)); - - break: new self.new_vector3(lhs_vector3[0] * rhs_scalar, lhs_vector3[1] + rhs_scalar, lhs_vector3[2] * rhs_scalar); - }, - - else => self.raise(error.TypeMismatch, "fixed types are not multiplicable with {typename}", .{ - .typename = addable.get_typename(), - }), - }; - } - - pub fn mul_vector2(self: *RuntimeEnv, rhs_vector2: Vector2) RuntimeError!*RuntimeEnv { - const addable = try self.expect_object(self.pop()); - - defer self.release(addable); - - return if (addable.is_vector2()) |lhs_vector2| - self.new_vector2(lhs_vector2[0] * rhs_vector2[0], lhs_vector2[1] * rhs_vector2[1]) - else - self.raise(error.TypeMismatch, "vector2 types are not multiplicable with {typename}", .{ - .typename = addable.get_typename(), - }); - } - - pub fn mul_vector3(self: *RuntimeEnv, rhs_vector3: Vector3) RuntimeError!*RuntimeEnv { - const addable = try self.expect_object(self.pop()); - - defer self.release(addable); - - return if (addable.is_vector3()) |lhs_vector3| - self.new_vector3(lhs_vector3[0] * rhs_vector3[0], lhs_vector3[1] * rhs_vector3[1], lhs_vector3[2] * rhs_vector3[2]) - else - self.raise(error.TypeMismatch, "vector3 types are not multiplicable with {typename}", .{ - .typename = addable.get_typename(), - }); - } - + /// + /// Pops the top-most local from `self`, negates it, and pushes the result. + /// + /// A [RuntimeError] is returned if `self` is out of memory or the top-most local is nil or not negatable. + /// + /// `self` is returned for function chaining. + /// pub fn neg(self: *RuntimeEnv) RuntimeError!*RuntimeEnv { const negatable = try self.expect_object(self.pop()); @@ -866,11 +1035,18 @@ pub const RuntimeEnv = struct { .float => |float| self.new_float(-float), else => self.raise(error.TypeMismatch, "{typename} is not scalar negatable", .{ - .typename = negatable.get_typename(), + .typename = negatable.typename(), }), }; } + /// + /// Attempts to create a new true or false object according to `value`, pushing it to the top of `self`. + /// + /// A [RuntimeError] is returned if `self` is out of memory. + /// + /// `self` is returned for function chaining. + /// pub fn new_boolean(self: *RuntimeEnv, value: bool) RuntimeError!*RuntimeEnv { const boolean = try RuntimeObj.allocate(self.allocator, .{ .ref_count = 1, @@ -884,10 +1060,17 @@ pub const RuntimeEnv = struct { return self; } + /// + /// Attempts to create a new boxed object holding a reference to `value`, pushing it to the top of `self`. + /// + /// A [RuntimeError] is returned if `self` is out of memory. + /// + /// `self` is returned for function chaining. + /// pub fn new_boxed(self: *RuntimeEnv, value: ?*RuntimeObj) RuntimeError!*RuntimeEnv { const boxed = try RuntimeObj.allocate(self.allocator, .{ .ref_count = 1, - .payload = .{.boxed = if (value) |ref| ref.acquire() else null}, + .payload = .{.boxed = if (value) |object| object.internal().acquire() else null}, }); errdefer self.release(boxed); @@ -897,6 +1080,15 @@ pub const RuntimeEnv = struct { return self; } + /// + /// Attempts to create a new dynamic object from `userdata` and `typeinfo`, pushing it to the top of `self`. + /// + /// *Note* that the number of bytes in `userdata` must match the size described in `typeinfo` exactly. + /// + /// A [RuntimeError] is returned if `self` is out of memory. + /// + /// `self` is returned for function chaining. + /// pub fn new_dynamic(self: *RuntimeEnv, userdata: []const coral.io.Byte, typeinfo: *const Typeinfo) RuntimeError!*RuntimeEnv { coral.debug.assert(userdata.len == typeinfo.size); @@ -921,6 +1113,13 @@ pub const RuntimeEnv = struct { return self; } + /// + /// Attempts to create a new fixed-size number object from `value`, pushing it to the top of `self`. + /// + /// A [RuntimeError] is returned if `self` is out of memory. + /// + /// `self` is returned for function chaining. + /// pub fn new_fixed(self: *RuntimeEnv, value: Fixed) RuntimeError!*RuntimeEnv { const syscall = try RuntimeObj.allocate(self.allocator, .{ .ref_count = 1, @@ -934,6 +1133,13 @@ pub const RuntimeEnv = struct { return self; } + /// + /// Attempts to create a new floating point number object from `value`, pushing it to the top of `self`. + /// + /// A [RuntimeError] is returned if `self` is out of memory. + /// + /// `self` is returned for function chaining. + /// pub fn new_float(self: *RuntimeEnv, value: Float) RuntimeError!*RuntimeEnv { const syscall = try RuntimeObj.allocate(self.allocator, .{ .ref_count = 1, @@ -947,6 +1153,13 @@ pub const RuntimeEnv = struct { return self; } + /// + /// Attempts to create a new byte string object from `value`, pushing it to the top of `self`. + /// + /// A [RuntimeError] is returned if `self` is out of memory. + /// + /// `self` is returned for function chaining. + /// pub fn new_string(self: *RuntimeEnv, value: []const coral.io.Byte) RuntimeError!*RuntimeEnv { if (value.len > coral.math.max_int(@typeInfo(Fixed).Int)) { return error.OutOfMemory; @@ -976,6 +1189,13 @@ pub const RuntimeEnv = struct { return self; } + /// + /// Attempts to create a new system-native function object from `value`, pushing it to the top of `self`. + /// + /// A [RuntimeError] is returned if `self` is out of memory. + /// + /// `self` is returned for function chaining. + /// pub fn new_syscall(self: *RuntimeEnv, value: *const Syscall) RuntimeError!*RuntimeEnv { const syscall = try RuntimeObj.allocate(self.allocator, .{ .ref_count = 1, @@ -989,6 +1209,13 @@ pub const RuntimeEnv = struct { return self; } + /// + /// Attempts to create a new empty table object, pushing it to the top of `self`. + /// + /// A [RuntimeError] is returned if `self` is out of memory. + /// + /// `self` is returned for function chaining. + /// pub fn new_table(self: *RuntimeEnv) RuntimeError!*RuntimeEnv { var table = Table.init(self); @@ -997,10 +1224,17 @@ pub const RuntimeEnv = struct { return self.new_dynamic(coral.io.bytes_of(&table), Table.typeinfo); } - pub fn new_vector2(self: *RuntimeEnv, x: f32, y: f32) RuntimeError!*RuntimeEnv { + /// + /// Attempts to create a new 2-component vector object from `value`, pushing it to the top of `self`. + /// + /// A [RuntimeError] is returned if `self` is out of memory. + /// + /// `self` is returned for function chaining. + /// + pub fn new_vector2(self: *RuntimeEnv, value: Vector2) RuntimeError!*RuntimeEnv { const vector2 = try RuntimeObj.allocate(self.allocator, .{ .ref_count = 1, - .payload = .{.vector2 = .{x, y}}, + .payload = .{.vector2 = value}, }); errdefer self.release(vector2); @@ -1010,10 +1244,17 @@ pub const RuntimeEnv = struct { return self; } - pub fn new_vector3(self: *RuntimeEnv, x: f32, y: f32, z: f32) RuntimeError!*RuntimeEnv { + /// + /// Attempts to create a new 3-component vector object from `value`, pushing it to the top of `self`. + /// + /// A [RuntimeError] is returned if `self` is out of memory. + /// + /// `self` is returned for function chaining. + /// + pub fn new_vector3(self: *RuntimeEnv, value: Vector3) RuntimeError!*RuntimeEnv { const vector3 = try RuntimeObj.allocate(self.allocator, .{ .ref_count = 1, - .payload = .{.vector3 = .{x, y, z}}, + .payload = .{.vector3 = value}, }); errdefer self.release(vector3); @@ -1023,6 +1264,13 @@ pub const RuntimeEnv = struct { return self; } + /// + /// Attempts to create a new symbol object from `value`, pushing it to the top of `self`. + /// + /// A [RuntimeError] is returned if `self` is out of memory. + /// + /// `self` is returned for function chaining. + /// pub fn new_symbol(self: *RuntimeEnv, value: []const coral.io.Byte) RuntimeError!*RuntimeEnv { const symbol = try RuntimeObj.allocate(self.allocator, .{ .payload = .{ @@ -1047,6 +1295,12 @@ pub const RuntimeEnv = struct { return self; } + /// + /// Pops the top-most value of `self`, returning the value. + /// + /// *Note* any returned non-null pointer must be released via [RuntimeEnv.release] or else the resources belonging + /// to the objects will never be freed by the runtime. + /// pub fn pop(self: *RuntimeEnv) ?*RuntimeObj { const popped = self.locals.pop(); @@ -1055,21 +1309,34 @@ pub const RuntimeEnv = struct { return popped.?; } + /// + /// Prints `buffer` to the printing operation specified in the options of `self`. + /// pub fn print(self: *RuntimeEnv, buffer: []const coral.io.Byte) void { if (self.options.print) |op| { op(buffer); } } + /// + /// Prints `buffer` to the error printing operation specified in the options of `self. + /// pub fn print_error(self: *RuntimeEnv, buffer: []const coral.io.Byte) void { if (self.options.print_error) |op| { op(buffer); } } + /// + /// Attempts to push `value` to the top of `self`. + /// + /// A [RuntimeError] is returned if `self` is out of memory. + /// + /// `self` is returned for function chaining. + /// pub fn push(self: *RuntimeEnv, value: ?*RuntimeObj) RuntimeError!*RuntimeEnv { if (value) |object| { - const acquired = object.acquire(); + const acquired = object.internal().acquire(); errdefer self.release(acquired); @@ -1081,6 +1348,12 @@ pub const RuntimeEnv = struct { return self; } + /// + /// Creates stack-unwinding runtime error from `error_value` with `format` and `args` as the formatted error message + /// presented with the type of error raised and a full stack-trace. + /// + /// The `error_value` passed is returned so that the error may be raised and returned in a single line. + /// pub fn raise(self: *RuntimeEnv, error_value: RuntimeError, comptime format: []const coral.io.Byte, args: anytype) RuntimeError { const formatted_message = try coral.utf8.alloc_formatted(self.allocator, format, args); @@ -1135,6 +1408,10 @@ pub const RuntimeEnv = struct { return error_value; } + /// + /// Releases `value` from `self`, decrementing the reference count (if applicable), and releasing all resources + /// belonging to the object once the reference count reaches zero. + /// pub fn release(self: *RuntimeEnv, value: *RuntimeObj) void { coral.debug.assert(value.internal().ref_count != 0); @@ -1157,7 +1434,10 @@ pub const RuntimeEnv = struct { .dynamic => |dynamic| { if (dynamic.typeinfo().destruct) |destruct| { - destruct(self, dynamic.userdata()); + destruct(.{ + .userdata = dynamic.userdata(), + .env = self, + }); } self.allocator.deallocate(dynamic.unpack()); @@ -1168,90 +1448,14 @@ pub const RuntimeEnv = struct { } } - pub fn sub_fixed(self: *RuntimeEnv, rhs_fixed: Fixed) RuntimeError!*RuntimeEnv { - const addable = try self.expect_object(self.pop()); - - defer self.release(addable); - - return switch (addable.internal().payload) { - .fixed => |lhs_fixed| if (coral.math.checked_sub(lhs_fixed, rhs_fixed)) |result| - self.new_fixed(result) - else - self.new_float(@as(Float, @floatFromInt(lhs_fixed)) - @as(Float, @floatFromInt(rhs_fixed))), - - .float => |lhs_float| self.new_float(lhs_float + @as(Float, @floatFromInt(rhs_fixed))), - - .vector2 => |lhs_vector2| new: { - const rhs_scalar = @as(f32, @floatFromInt(rhs_fixed)); - - break: new self.new_vector2(lhs_vector2[0] - rhs_scalar, lhs_vector2[1] - rhs_scalar); - }, - - .vector3 => |lhs_vector3| new: { - const rhs_scalar = @as(f32, @floatFromInt(rhs_fixed)); - - break: new self.new_vector3(lhs_vector3[0] - rhs_scalar, lhs_vector3[1] - rhs_scalar, lhs_vector3[2] - rhs_scalar); - }, - - else => self.raise(error.TypeMismatch, "fixed types are not multiplicable with {typename}", .{ - .typename = addable.get_typename(), - }), - }; - } - - pub fn sub_float(self: *RuntimeEnv, rhs_float: Float) RuntimeError!*RuntimeEnv { - const addable = try self.expect_object(self.pop()); - - defer self.release(addable); - - return switch (addable.internal().payload) { - .fixed => |lhs_fixed| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) - rhs_float), - .float => |lhs_float| self.new_float(lhs_float - lhs_float), - - .vector2 => |lhs_vector2| new: { - const rhs_scalar = @as(f32, @floatCast(rhs_float)); - - break: new self.new_vector2(lhs_vector2[0] - rhs_scalar, lhs_vector2[1] - rhs_scalar); - }, - - .vector3 => |lhs_vector3| new: { - const rhs_scalar = @as(f32, @floatCast(rhs_float)); - - break: new self.new_vector3(lhs_vector3[0] - rhs_scalar, lhs_vector3[1] - rhs_scalar, lhs_vector3[2] - rhs_scalar); - }, - - else => self.raise(error.TypeMismatch, "fixed types are not multiplicable with {typename}", .{ - .typename = addable.get_typename(), - }), - }; - } - - pub fn sub_vector2(self: *RuntimeEnv, rhs_vector2: Vector2) RuntimeError!*RuntimeEnv { - const addable = try self.expect_object(self.pop()); - - defer self.release(addable); - - return if (addable.is_vector2()) |lhs_vector2| - self.new_vector2(lhs_vector2[0] - rhs_vector2[0], lhs_vector2[1] - rhs_vector2[1]) - else - self.raise(error.TypeMismatch, "vector2 types are not multiplicable with {typename}", .{ - .typename = addable.get_typename(), - }); - } - - pub fn sub_vector3(self: *RuntimeEnv, rhs_vector3: Vector3) RuntimeError!*RuntimeEnv { - const addable = try self.expect_object(self.pop()); - - defer self.release(addable); - - return if (addable.is_vector3()) |lhs_vector3| - self.new_vector3(lhs_vector3[0] - rhs_vector3[0], lhs_vector3[1] - rhs_vector3[1], lhs_vector3[2] - rhs_vector3[2]) - else - self.raise(error.TypeMismatch, "vector3 types are not multiplicable with {typename}", .{ - .typename = addable.get_typename(), - }); - } - + /// + /// Attempts to pop the top-most value of `self`, convert it to a string object representation, and push the result. + /// + /// A [RuntimeError] is returned if `self` is out of memory or the to_string implementation of the top-most value of + /// `self` raised an error. + /// + /// `self` is returned for function chaining. + /// pub fn to_string(self: *RuntimeEnv) RuntimeError!*RuntimeEnv { const decimal_format = coral.utf8.DecimalFormat.default; const value = try self.expect_object(self.pop()); @@ -1286,7 +1490,7 @@ pub const RuntimeEnv = struct { .symbol => |symbol| self.new_string(coral.io.slice_sentineled(@as(coral.io.Byte, 0), symbol)), .string => acquire: { - try self.locals.push_one(value.acquire()); + try self.locals.push_one(value.internal().acquire()); break: acquire self; }, @@ -1294,36 +1498,26 @@ pub const RuntimeEnv = struct { .vector2 => |vector2| convert: { var string = [_:0]coral.io.Byte{0} ** 64; var buffer = coral.io.FixedBuffer{.bytes = &string}; + const length = coral.utf8.print_formatted(buffer.as_writer(), "@vec2({x}, {y})", vector2).?; - const length = coral.utf8.print_formatted(buffer.as_writer(), "@vec2({x}, {y})", .{ - .x = vector2[0], - .y = vector2[1], - }); - - coral.debug.assert(length != null); - - break: convert self.new_string(string[0 .. length.?]); + break: convert self.new_string(string[0 .. length]); }, .vector3 => |vector3| convert: { var string = [_:0]coral.io.Byte{0} ** 96; var buffer = coral.io.FixedBuffer{.bytes = &string}; + const length = coral.utf8.print_formatted(buffer.as_writer(), "@vec3({x}, {y}, {z})", vector3).?; - const length = coral.utf8.print_formatted(buffer.as_writer(), "@vec3({x}, {y}, {z})", .{ - .x = vector3[0], - .y = vector3[1], - .z = vector3[2], - }); - - coral.debug.assert(length != null); - - break: convert self.new_string(string[0 .. length.?]); + break: convert self.new_string(string[0 .. length]); }, .syscall => self.new_string("syscall"), .dynamic => |dynamic| dynamic_to_string: { - const string = try dynamic.typeinfo().to_string(self, dynamic.userdata()); + const string = try dynamic.typeinfo().to_string(.{ + .userdata = dynamic.userdata(), + .env = self, + }); errdefer self.release(string); @@ -1334,6 +1528,187 @@ pub const RuntimeEnv = struct { }; } + /// + /// Attempts to pop the top-most value in `self` and add it with `rhs_vector2`, pushing the result. + /// + /// A [RuntimeError] is returned if `self` is out of memory or `rhs_vector2` is not addable with the top-most local. + /// + /// `self` is returned for function chaining. + /// + pub fn vector2_add(self: *RuntimeEnv, rhs_vector2: Vector2) RuntimeError!*RuntimeEnv { + const addable = try self.expect_object(self.pop()); + + defer self.release(addable); + + return if (addable.is_vector2()) |lhs_vector2| + self.new_vector2(lhs_vector2.vector_added(rhs_vector2)) + else + self.raise(error.TypeMismatch, "vector2 types are not addable with {typename}", .{ + .typename = addable.typename(), + }); + } + + /// + /// Attempts to pop the top-most value in `self` and divide it with `rhs_vector2`, pushing the result. + /// + /// A [RuntimeError] is returned if `self` is out of memory or `rhs_vector2` is not divisible with the top-most + /// local. + /// + /// `self` is returned for function chaining. + /// + pub fn vector2_divide(self: *RuntimeEnv, rhs_vector2: Vector2) RuntimeError!*RuntimeEnv { + if (rhs_vector2.x == 0 or rhs_vector2.y == 0) { + return self.raise(error.TypeMismatch, "cannot divide by zero", .{}); + } + + const addable = try self.expect_object(self.pop()); + + defer self.release(addable); + + return if (addable.is_vector2()) |lhs_vector2| + self.new_vector2(lhs_vector2.vector_divided(rhs_vector2)) + else + self.raise(error.TypeMismatch, "vector2 types are not divisible with {typename}", .{ + .typename = addable.typename(), + }); + } + + /// + /// Attempts to pop the top-most value in `self` and multiply it with `rhs_vector2`, pushing the result. + /// + /// A [RuntimeError] is returned if `self` is out of memory or `rhs_vector2` is not multiplicable with the top-most + /// local. + /// + /// `self` is returned for function chaining. + /// + pub fn vector2_multiply(self: *RuntimeEnv, rhs_vector2: Vector2) RuntimeError!*RuntimeEnv { + const addable = try self.expect_object(self.pop()); + + defer self.release(addable); + + return if (addable.is_vector2()) |lhs_vector2| + self.new_vector2(lhs_vector2.vector_multiplied(rhs_vector2)) + else + self.raise(error.TypeMismatch, "vector2 types are not multiplicable with {typename}", .{ + .typename = addable.typename(), + }); + } + + /// + /// Attempts to pop the top-most value in `self` and subtract it with `rhs_vector2`, pushing the result. + /// + /// A [RuntimeError] is returned if `self` is out of memory or `rhs_vector2` is not subtractable with the top-most + /// local. + /// + /// `self` is returned for function chaining. + /// + pub fn vector2_subtract(self: *RuntimeEnv, rhs_vector2: Vector2) RuntimeError!*RuntimeEnv { + const addable = try self.expect_object(self.pop()); + + defer self.release(addable); + + return if (addable.is_vector2()) |lhs_vector2| + self.new_vector2(lhs_vector2.vector_subtracted(rhs_vector2)) + else + self.raise(error.TypeMismatch, "vector2 types are not multiplicable with {typename}", .{ + .typename = addable.typename(), + }); + } + + /// + /// Attempts to pop the top-most value in `self` and add it with `rhs_vector3`, pushing the result. + /// + /// A [RuntimeError] is returned if `self` is out of memory or `rhs_vector3` is not addable with the top-most local. + /// + /// `self` is returned for function chaining. + /// + pub fn vector3_add(self: *RuntimeEnv, rhs_vector3: Vector3) RuntimeError!*RuntimeEnv { + const addable = try self.expect_object(self.pop()); + + defer self.release(addable); + + return if (addable.is_vector3()) |lhs_vector3| + self.new_vector3(lhs_vector3.vector_added(rhs_vector3)) + else + self.raise(error.TypeMismatch, "vector3 types are not addable with {typename}", .{ + .typename = addable.typename(), + }); + } + + /// + /// Attempts to pop the top-most value in `self` and divide it with `rhs_vector3`, pushing the result. + /// + /// A [RuntimeError] is returned if `self` is out of memory or `rhs_vector3` is not divisible with the top-most + /// local. + /// + /// `self` is returned for function chaining. + /// + pub fn vector3_divide(self: *RuntimeEnv, rhs_vector3: Vector3) RuntimeError!*RuntimeEnv { + if (rhs_vector3.x == 0 or rhs_vector3.y == 0 or rhs_vector3.z == 0) { + return self.raise(error.TypeMismatch, "cannot divide by zero", .{}); + } + + const addable = try self.expect_object(self.pop()); + + defer self.release(addable); + + return if (addable.is_vector3()) |lhs_vector3| + self.new_vector3(lhs_vector3.vector_divided(rhs_vector3)) + else + self.raise(error.TypeMismatch, "vector3 types are not divisible with {typename}", .{ + .typename = addable.typename(), + }); + } + + /// + /// Attempts to pop the top-most value in `self` and multiply it with `rhs_vector3`, pushing the result. + /// + /// A [RuntimeError] is returned if `self` is out of memory or `rhs_vector3` is not multiplicable with the top-most + /// local. + /// + /// `self` is returned for function chaining. + /// + pub fn vector3_multiply(self: *RuntimeEnv, rhs_vector3: Vector3) RuntimeError!*RuntimeEnv { + const addable = try self.expect_object(self.pop()); + + defer self.release(addable); + + return if (addable.is_vector3()) |lhs_vector3| + self.new_vector3(lhs_vector3.vector_multiplied(rhs_vector3)) + else + self.raise(error.TypeMismatch, "vector3 types are not multiplicable with {typename}", .{ + .typename = addable.typename(), + }); + } + + /// + /// Attempts to pop the top-most value in `self` and subtract it with `rhs_vector3`, pushing the result. + /// + /// A [RuntimeError] is returned if `self` is out of memory or `rhs_vector3` is not subtractable with the top-most + /// local. + /// + /// `self` is returned for function chaining. + /// + pub fn vector3_subtract(self: *RuntimeEnv, rhs_vector3: Vector3) RuntimeError!*RuntimeEnv { + const addable = try self.expect_object(self.pop()); + + defer self.release(addable); + + return if (addable.is_vector3()) |lhs_vector3| + self.new_vector3(lhs_vector3.vector_subtracted(rhs_vector3)) + else + self.raise(error.TypeMismatch, "vector3 types are not multiplicable with {typename}", .{ + .typename = addable.typename(), + }); + } + + /// + /// Attempts to pop the top-most value in `self`, pushing the unboxed value. + /// + /// A [RuntimeError] is returned if `self` is out of memory or the top-most value is nil or not unboxable. + /// + /// `self` is returned for function chaining. + /// pub fn boxed_get(self: *RuntimeEnv) RuntimeError!*RuntimeEnv { const unboxable = try self.expect_object(self.pop()); @@ -1343,11 +1718,16 @@ pub const RuntimeEnv = struct { .boxed => |boxed| self.push(boxed), else => self.raise(error.TypeMismatch, "{typename} is not unboxable", .{ - .typename = unboxable.get_typename(), + .typename = unboxable.typename(), }), }; } + /// + /// Attempts to pop the top-most value in `self`, unboxing it, and replacing the contents with `value`. + /// + /// A [RuntimeError] is returned if the top-most value is nil or not unboxable. + /// pub fn boxed_set(self: *RuntimeEnv, value: ?*RuntimeObj) RuntimeError!void { const unboxable = try self.expect_object(self.pop()); @@ -1359,16 +1739,19 @@ pub const RuntimeEnv = struct { self.release(unboxed_object); } - unboxed_value.* = if (value) |object| object.acquire() else null; + unboxed_value.* = if (value) |object| object.internal().acquire() else null; }, else => return self.raise(error.TypeMismatch, "{typename} is not unboxable", .{ - .typename = unboxable.get_typename(), + .typename = unboxable.typename(), }), } } }; +/// +/// Archetypes of errors that may occur in the virtual machine during run-time. +/// pub const RuntimeError = coral.io.AllocationError || error { IllegalState, TypeMismatch, @@ -1376,6 +1759,9 @@ pub const RuntimeError = coral.io.AllocationError || error { BadSyntax, }; +/// +/// Runtime-polymorphic data type for representing all data types supported by the virtual machine. +/// pub const RuntimeObj = opaque { const Internal = struct { ref_count: u16, @@ -1427,16 +1813,14 @@ pub const RuntimeObj = opaque { } }, }, + + fn acquire(self: *Internal) *RuntimeObj { + self.ref_count += 1; + + return @ptrCast(self); + } }; - pub fn acquire(self: *const RuntimeObj) *RuntimeObj { - const self_object = self.internal(); - - self_object.ref_count += 1; - - return@ptrCast(self_object); - } - fn allocate(allocator: coral.io.Allocator, data: Internal) coral.io.AllocationError!*RuntimeObj { return @ptrCast(try coral.io.allocate_one(allocator, data)); } @@ -1445,6 +1829,9 @@ pub const RuntimeObj = opaque { return @constCast(@ptrCast(@alignCast(self))); } + /// + /// Returns `true` if the value of `self` is equivalent to `other`, otherwise `false`. + /// pub fn equals(self: *RuntimeObj, other: *RuntimeObj) bool { return switch (self.internal().payload) { .false => other.internal().payload == .false, @@ -1469,13 +1856,13 @@ pub const RuntimeObj = opaque { .boxed => |boxed| if (boxed) |object| self.equals(object) else false, - .vector2 => |lhs_vector| switch (other.internal().payload) { - .vector2 => |rhs_vector| lhs_vector[0] == rhs_vector[0] and lhs_vector[1] == rhs_vector[1], + .vector2 => |lhs_vector2| switch (other.internal().payload) { + .vector2 => |rhs_vector2| lhs_vector2.equals(rhs_vector2), else => false, }, - .vector3 => |lhs_vector| switch (other.internal().payload) { - .vector3 => |rhs_vector| lhs_vector[0] == rhs_vector[0] and lhs_vector[1] == rhs_vector[1] and lhs_vector[2] == rhs_vector[2], + .vector3 => |lhs_vector3| switch (other.internal().payload) { + .vector3 => |rhs_vector3| lhs_vector3.equals(rhs_vector3), else => false, }, @@ -1499,15 +1886,18 @@ pub const RuntimeObj = opaque { }; } - pub fn get_hash(self: *RuntimeObj) usize { + /// + /// Computes a hash for `self` based on the held type and value. + /// + pub fn hash(self: *RuntimeObj) usize { return switch (self.internal().payload) { .false => 1237, .true => 1231, .float => |float| @bitCast(float), .fixed => |fixed| @intCast(@as(u32, @bitCast(fixed))), .symbol => |symbol| @intFromPtr(symbol), - .vector2 => |vector| @bitCast(vector), - .vector3 => |vector| coral.io.jenkins_hash(@typeInfo(usize).Int, coral.io.bytes_of(&vector)), + .vector2 => |vector| @bitCast(vector.to_scalars()), + .vector3 => |vector| coral.io.jenkins_hash(@typeInfo(usize).Int, coral.io.bytes_of(&vector.to_scalars())), .syscall => |syscall| @intFromPtr(syscall), .boxed => |boxed| @intFromPtr(boxed), .string => |string| coral.io.djb2_hash(@typeInfo(usize).Int, string.unpack()), @@ -1515,7 +1905,10 @@ pub const RuntimeObj = opaque { }; } - pub fn get_typename(self: *RuntimeObj) []const coral.io.Byte { + /// + /// Returns the name of the type held in `self`. + /// + pub fn typename(self: *RuntimeObj) []const coral.io.Byte { return switch (self.internal().payload) { .false => "false", .true => "true", @@ -1531,17 +1924,28 @@ pub const RuntimeObj = opaque { }; } - pub fn is_dynamic(self: *RuntimeObj, typeinfo: *const Typeinfo) ?[]u8 { + /// + /// Checks if `self` is a dynamic object with type information matching `typeinfo`, returning the userdata buffer if + /// it is or `null` if it is not. + /// + pub fn is_dynamic(self: *RuntimeObj, typeinfo: *const Typeinfo) ?[]coral.io.Byte { return switch (self.internal().payload) { .dynamic => |dynamic| if (dynamic.typeinfo() == typeinfo) dynamic.userdata() else null, else => null, }; } + /// + /// Checks if `self` is a false object (i.e. any object that evaluates to false), returning `true` if it is or + /// `false` if it is not. + /// pub fn is_false(self: *RuntimeObj) bool { return !self.is_true(); } + /// + /// Checks if `self` is a fixed number object, returning the value if it is or `null` if it is not. + /// pub fn is_fixed(self: *RuntimeObj) ?Fixed { return switch (self.internal().payload) { .fixed => |fixed| fixed, @@ -1549,6 +1953,10 @@ pub const RuntimeObj = opaque { }; } + /// + /// Checks if `self` is a numeric object (i.e. any object that behaves like a number), returning the supertype for + /// further inspection if it is or `null` if it is not. + /// pub fn is_numeric(self: *RuntimeObj) ?Numeric { return switch (self.internal().payload) { .fixed => |fixed| .{.fixed = fixed}, @@ -1559,6 +1967,9 @@ pub const RuntimeObj = opaque { }; } + /// + /// Checks if `self` is a string text object, returning the value if it is or `null` if it is not. + /// pub fn is_string(self: *RuntimeObj) ?[]const coral.io.Byte { return switch (self.internal().payload) { .string => |string| get: { @@ -1572,6 +1983,9 @@ pub const RuntimeObj = opaque { }; } + /// + /// Checks if `self` is a symbo text object, returning the value if it is or `null` if it is not. + /// pub fn is_symbol(self: *RuntimeObj) ?[*:0]const coral.io.Byte { return switch (self.internal().payload) { .symbol => |symbol| symbol, @@ -1579,6 +1993,10 @@ pub const RuntimeObj = opaque { }; } + /// + /// Checks if `self` is a true object (i.e. any object that evaluates to true), returning `true` if it is and + /// `false` if it is not. + /// pub fn is_true(self: *RuntimeObj) bool { return switch (self.internal().payload) { .false => false, @@ -1595,6 +2013,9 @@ pub const RuntimeObj = opaque { }; } + /// + /// Checks if `self` is a 2-component vector number object, returning the value if it is or `null` if it is not. + /// pub fn is_vector2(self: *RuntimeObj) ?Vector2 { return switch (self.internal().payload) { .vector2 => |vector2| vector2, @@ -1602,6 +2023,9 @@ pub const RuntimeObj = opaque { }; } + /// + /// Checks if `self` is a 3-component vector number object, returning the value if it is or `null` if it is not. + /// pub fn is_vector3(self: *RuntimeObj) ?Vector3 { return switch (self.internal().payload) { .vector3 => |vector3| vector3, @@ -1610,38 +2034,124 @@ pub const RuntimeObj = opaque { } }; +/// +/// Bridge function type between the virtual machine and native code. +/// pub const Syscall = fn (env: *RuntimeEnv) RuntimeError!?*RuntimeObj; +/// +/// Type information for dynamically created object types +/// pub const Typeinfo = struct { name: []const coral.io.Byte, size: usize, - destruct: ?*const fn (env: *RuntimeEnv, userdata: []coral.io.Byte) void = null, - to_string: *const fn (env: *RuntimeEnv, userdata: []coral.io.Byte) RuntimeError!*RuntimeObj = default_to_string, - call: *const fn (env: *RuntimeEnv, userdata: []coral.io.Byte) RuntimeError!?*RuntimeObj = default_call, - get: *const fn (env: *RuntimeEnv, userdata: []coral.io.Byte, index: *RuntimeObj) RuntimeError!?*RuntimeObj = default_get, - set: *const fn (env: *RuntimeEnv, userdata: []coral.io.Byte, value: *RuntimeObj, value: ?*RuntimeObj) RuntimeError!void = default_set, + destruct: ?*const fn (context: DestructContext) void = null, + to_string: *const fn (context: ToStringContext) RuntimeError!*RuntimeObj = default_to_string, + call: *const fn (context: CallContext) RuntimeError!?*RuntimeObj = default_call, + get: *const fn (context: GetContext) RuntimeError!?*RuntimeObj = default_get, + set: *const fn (context: SetContext) RuntimeError!void = default_set, - fn default_call(env: *RuntimeEnv, _: []coral.io.Byte) RuntimeError!?*RuntimeObj { - return env.raise(error.BadOperation, "this dynamic object is not callable", .{}); + /// + /// Context used for calling objects. + /// + pub const CallContext = struct { + env: *RuntimeEnv, + userdata: []coral.io.Byte, + }; + + /// + /// Context used with objects that have special destruction semantics + /// + pub const DestructContext = struct { + env: *RuntimeEnv, + userdata: []coral.io.Byte, + }; + + /// + /// Context used for getting indices of objects. + /// + pub const GetContext = struct { + env: *RuntimeEnv, + userdata: []coral.io.Byte, + + /// + /// TODO: + /// + pub fn get_index(self: *const GetContext) *RuntimeObj { + coral.debug.assert(self.env.locals.values.len > 0); + + return self.env.locals.values[self.env.locals.values.len - 1].?.internal().acquire(); + } + }; + + /// + /// Context used for setting indices of objects to new values. + /// + pub const SetContext = struct { + env: *RuntimeEnv, + userdata: []coral.io.Byte, + + /// + /// TODO: + /// + pub fn get_index(self: *const SetContext) *RuntimeObj { + coral.debug.assert(self.env.locals.values.len > 0); + + return self.env.locals.values[self.env.locals.values.len - 1].?.internal().acquire(); + } + + /// + /// TODO: + /// + pub fn get_value(self: *const SetContext) ?*RuntimeObj { + coral.debug.assert(self.env.locals.values.len > 1); + + if (self.env.locals.values[self.env.locals.values.len - 2]) |value| { + return value.internal().acquire(); + } + + return null; + } + }; + + /// + /// Context used for converting objects to string representations. + /// + pub const ToStringContext = struct { + env: *RuntimeEnv, + userdata: []coral.io.Byte, + }; + + fn default_call(context: CallContext) RuntimeError!?*RuntimeObj { + return context.env.raise(error.BadOperation, "this dynamic object is not callable", .{}); } - fn default_get(env: *RuntimeEnv, _: []coral.io.Byte, _: *RuntimeObj) RuntimeError!?*RuntimeObj { - return env.raise(error.BadOperation, "this dynamic object is not get-indexable", .{}); + fn default_get(context: GetContext) RuntimeError!?*RuntimeObj { + return context.env.raise(error.BadOperation, "this dynamic object is not get-indexable", .{}); } - fn default_set(env: *RuntimeEnv, _: []coral.io.Byte, _: *RuntimeObj, _: ?*RuntimeObj) RuntimeError!void { - return env.raise(error.BadOperation, "this dynamic object is not set-indexable", .{}); + fn default_set(context: SetContext) RuntimeError!void { + return context.env.raise(error.BadOperation, "this dynamic object is not set-indexable", .{}); } - fn default_to_string(env: *RuntimeEnv, _: []coral.io.Byte) RuntimeError!*RuntimeObj { - return (try env.new_string("{}")).pop().?; + fn default_to_string(context: ToStringContext) RuntimeError!*RuntimeObj { + return (try context.env.new_string("{}")).pop().?; } }; -pub const Vector2 = [2]f32; +/// +/// 2-component vector type. +/// +pub const Vector2 = gfx.lina.Vector2; -pub const Vector3 = [3]f32; +/// +/// 3-component vector type. +/// +pub const Vector3 = gfx.lina.Vector3; +/// +/// Higher-level wrapper for [RuntimeEnv.index_get] that makes it easier to index [Fixed] keys of objects. +/// pub fn get_at(env: *RuntimeEnv, indexable: *RuntimeObj, index: Fixed) RuntimeError!?*RuntimeObj { const index_number = (try env.new_fixed(index)).pop(); @@ -1650,6 +2160,9 @@ pub fn get_at(env: *RuntimeEnv, indexable: *RuntimeObj, index: Fixed) RuntimeErr return (try (try env.push(indexable)).index_get(index_number)).pop(); } +/// +/// Higher-level wrapper for [RuntimeEnv.index_get] that makes it easier to index field keys of objects. +/// pub fn get_field(env: *RuntimeEnv, indexable: *RuntimeObj, field: []const coral.io.Byte) RuntimeError!?*RuntimeObj { const field_symbol = (try env.new_symbol(field)).pop().?; @@ -1658,6 +2171,9 @@ pub fn get_field(env: *RuntimeEnv, indexable: *RuntimeObj, field: []const coral. return (try (try env.push(indexable)).index_get(field_symbol)).pop(); } +/// +/// Higher-level wrapper for [RuntimeEnv.index_get] that makes it easier to index string keys of objects. +/// pub fn get_key(env: *RuntimeEnv, indexable: *RuntimeObj, key: []const coral.io.Byte) RuntimeError!?*RuntimeObj { const key_string = (try env.new_string(key)).pop(); diff --git a/source/ona/kym/Chunk.zig b/source/ona/kym/Chunk.zig index 8bdf4de..4496bcb 100644 --- a/source/ona/kym/Chunk.zig +++ b/source/ona/kym/Chunk.zig @@ -632,7 +632,7 @@ pub fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.Runtime .push_false => _ = try env.new_boolean(false), .push_const => |push_const| _ = try env.push(try self.get_constant(env, push_const)), .push_local => |push_local| _ = try env.local_get(push_local), - .push_top => _ = try env.local_get(env.local_count() - 1), + .push_top => _ = try env.local_top(), .push_table => |push_table| { const table = (try env.new_table()).pop().?; @@ -708,7 +708,7 @@ pub fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.Runtime .vec3 => syscall_vec3, }), - .set_local => |local_set| _ = env.local_set(local_set, env.pop()), + .set_local => |local_set| _ = try env.local_set(local_set, env.pop()), .get_box => _ = try env.boxed_get(), .set_box => { @@ -765,10 +765,10 @@ pub fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.Runtime defer env.release(addable); _ = switch (try env.expect_numeric(addable)) { - .fixed => |fixed| try env.add_fixed(fixed), - .float => |float| try env.add_float(float), - .vector2 => |vector2| try env.add_vector2(vector2), - .vector3 => |vector3| try env.add_vector3(vector3), + .fixed => |fixed| try env.fixed_add(fixed), + .float => |float| try env.float_add(float), + .vector2 => |vector2| try env.vector2_add(vector2), + .vector3 => |vector3| try env.vector3_add(vector3), }; }, @@ -778,10 +778,10 @@ pub fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.Runtime defer env.release(subtractable); _ = switch (try env.expect_numeric(subtractable)) { - .fixed => |fixed| try env.sub_fixed(fixed), - .float => |float| try env.sub_float(float), - .vector2 => |vector2| try env.sub_vector2(vector2), - .vector3 => |vector3| try env.sub_vector3(vector3), + .fixed => |fixed| try env.fixed_subtract(fixed), + .float => |float| try env.float_subtract(float), + .vector2 => |vector2| try env.vector2_subtract(vector2), + .vector3 => |vector3| try env.vector3_subtract(vector3), }; }, @@ -791,10 +791,10 @@ pub fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.Runtime defer env.release(multiplicable); _ = switch (try env.expect_numeric(multiplicable)) { - .fixed => |fixed| try env.mul_fixed(fixed), - .float => |float| try env.mul_float(float), - .vector2 => |vector2| try env.mul_vector2(vector2), - .vector3 => |vector3| try env.mul_vector3(vector3), + .fixed => |fixed| try env.fixed_multiply(fixed), + .float => |float| try env.float_multiply(float), + .vector2 => |vector2| try env.vector2_multiply(vector2), + .vector3 => |vector3| try env.vector3_multiply(vector3), }; }, @@ -804,10 +804,10 @@ pub fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.Runtime defer env.release(divisible); _ = switch (try env.expect_numeric(divisible)) { - .fixed => |fixed| try env.div_fixed(fixed), - .float => |float| try env.div_float(float), - .vector2 => |vector2| try env.div_vector2(vector2), - .vector3 => |vector3| try env.div_vector3(vector3), + .fixed => |fixed| try env.fixed_divide(fixed), + .float => |float| try env.float_divide(float), + .vector2 => |vector2| try env.vector2_divide(vector2), + .vector3 => |vector3| try env.vector3_divide(vector3), }; }, @@ -951,10 +951,13 @@ fn syscall_vec2(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeObj { if ((try env.arg_get(1)).pop()) |y| { defer env.release(y); - return (try env.new_vector2(x, @floatCast(try env.expect_float(y)))).pop(); + return (try env.new_vector2(.{ + .y = @floatCast(try env.expect_float(y)), + .x = x, + })).pop(); } - return (try env.new_vector2(x, x)).pop(); + return (try env.new_vector2(kym.Vector2.from_scalar(x))).pop(); } fn syscall_vec3(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeObj { @@ -971,21 +974,24 @@ fn syscall_vec3(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeObj { if ((try env.arg_get(1)).pop()) |y| { defer env.release(y); - const z = @as(f32, get_z: { - const z = (try env.arg_get(0)).pop() orelse { - return env.raise(error.BadOperation, - "a third argument is required to create a vector if a first and second exist", .{}); - }; + return (try env.new_vector3(.{ + .z = @as(f32, get_z: { + const z = (try env.arg_get(0)).pop() orelse { + return env.raise(error.BadOperation, + "a third argument is required to create a vector if a first and second exist", .{}); + }; - defer env.release(z); + defer env.release(z); - break: get_z @floatCast(try env.expect_float(z)); - }); + break: get_z @floatCast(try env.expect_float(z)); + }), - return (try env.new_vector3(x, @floatCast(try env.expect_float(y)), z)).pop(); + .y = @floatCast(try env.expect_float(y)), + .x = x, + })).pop(); } - return (try env.new_vector3(x, x, x)).pop(); + return (try env.new_vector3(kym.Vector3.from_scalar(x))).pop(); } pub const typeinfo = &kym.Typeinfo{ @@ -996,16 +1002,16 @@ pub const typeinfo = &kym.Typeinfo{ .to_string = typeinfo_to_string, }; -fn typeinfo_call(env: *kym.RuntimeEnv, userdata: []coral.io.Byte) kym.RuntimeError!?*kym.RuntimeObj { - return @as(*Self, @ptrCast(@alignCast(userdata))).execute(env); +fn typeinfo_call(context: kym.Typeinfo.CallContext) kym.RuntimeError!?*kym.RuntimeObj { + return @as(*Self, @ptrCast(@alignCast(context.userdata))).execute(context.env); } -fn typeinfo_destruct(env: *kym.RuntimeEnv, userdata: []coral.io.Byte) void { - @as(*Self, @ptrCast(@alignCast(userdata))).deinit(env); +fn typeinfo_destruct(context: kym.Typeinfo.DestructContext) void { + @as(*Self, @ptrCast(@alignCast(context.userdata))).deinit(context.env); } -fn typeinfo_to_string(env: *kym.RuntimeEnv, userdata: []coral.io.Byte) kym.RuntimeError!*kym.RuntimeObj { - return (try (try env.push(@as(*Self, @ptrCast(@alignCast(userdata))).name)).to_string()).pop().?; +fn typeinfo_to_string(context: kym.Typeinfo.ToStringContext) kym.RuntimeError!*kym.RuntimeObj { + return (try (try context.env.push(@as(*Self, @ptrCast(@alignCast(context.userdata))).name)).to_string()).pop().?; } pub fn write(self: *Self, line: tokens.Line, opcode: Opcode) coral.io.AllocationError!void { diff --git a/source/ona/kym/Table.zig b/source/ona/kym/Table.zig index 3d02acf..fad9327 100644 --- a/source/ona/kym/Table.zig +++ b/source/ona/kym/Table.zig @@ -8,7 +8,7 @@ contiguous: RefList, const RefList = coral.list.Stack(?*kym.RuntimeObj); const RefTable = coral.map.Table(*kym.RuntimeObj, *kym.RuntimeObj, struct { - pub const hash = kym.RuntimeObj.get_hash; + pub const hash = kym.RuntimeObj.hash; pub const equals = kym.RuntimeObj.equals; }); @@ -51,41 +51,41 @@ pub const typeinfo = &kym.Typeinfo{ .set = typeinfo_set, }; -fn typeinfo_destruct(env: *kym.RuntimeEnv, userdata: []coral.io.Byte) void { - @as(*Self, @ptrCast(@alignCast(userdata))).deinit(env); +fn typeinfo_destruct(context: kym.Typeinfo.DestructContext) void { + @as(*Self, @ptrCast(@alignCast(context.userdata))).deinit(context.env); } -fn typeinfo_get(env: *kym.RuntimeEnv, userdata: []coral.io.Byte, index: *const kym.RuntimeObj) kym.RuntimeError!?*kym.RuntimeObj { - const table = @as(*Self, @ptrCast(@alignCast(userdata))); - const acquired_index = index.acquire(); +fn typeinfo_get(context: kym.Typeinfo.GetContext) kym.RuntimeError!?*kym.RuntimeObj { + const table = @as(*Self, @ptrCast(@alignCast(context.userdata))); + const index = context.get_index(); - defer env.release(acquired_index); + defer context.env.release(index); - if (acquired_index.is_fixed()) |fixed| { + if (index.is_fixed()) |fixed| { if (fixed < 0) { // TODO: Negative indexing. unreachable; } if (fixed < table.contiguous.values.len) { - return (table.contiguous.values[@intCast(fixed)] orelse return null).acquire(); + return table.contiguous.values[@intCast(fixed)]; } } - if (table.associative.lookup(acquired_index)) |value| { - return value.acquire(); + if (table.associative.lookup(index)) |value| { + return value; } return null; } -fn typeinfo_set(env: *kym.RuntimeEnv, userdata: []coral.io.Byte, index: *const kym.RuntimeObj, value: ?*const kym.RuntimeObj) kym.RuntimeError!void { - const table = @as(*Self, @ptrCast(@alignCast(userdata))); - const acquired_index = index.acquire(); +fn typeinfo_set(context: kym.Typeinfo.SetContext) kym.RuntimeError!void { + const table = @as(*Self, @ptrCast(@alignCast(context.userdata))); + const index = context.get_index(); - errdefer env.release(acquired_index); + errdefer context.env.release(index); - if (acquired_index.is_fixed()) |fixed| { + if (index.is_fixed()) |fixed| { if (fixed < 0) { // TODO: Negative indexing. unreachable; @@ -95,28 +95,34 @@ fn typeinfo_set(env: *kym.RuntimeEnv, userdata: []coral.io.Byte, index: *const k const maybe_replacing = &table.contiguous.values[@intCast(fixed)]; if (maybe_replacing.*) |replacing| { - env.release(replacing); + context.env.release(replacing); } - maybe_replacing.* = if (value) |ref| ref.acquire() else null; + if (context.get_value()) |value| { + errdefer context.env.release(value); + + maybe_replacing.* = value; + } else { + maybe_replacing.* = null; + } return; } } - const acquired_value = (value orelse { - if (table.associative.remove(acquired_index)) |removed| { - env.release(removed.key); - env.release(removed.value); + const value = context.get_value() orelse { + if (table.associative.remove(index)) |removed| { + context.env.release(removed.key); + context.env.release(removed.value); } return; - }).acquire(); + }; - errdefer env.release(acquired_value); + errdefer context.env.release(value); - if (try table.associative.replace(acquired_index, acquired_value)) |replaced| { - env.release(replaced.key); - env.release(replaced.value); + if (try table.associative.replace(index, value)) |replaced| { + context.env.release(replaced.key); + context.env.release(replaced.value); } }