From 1828eb3b453e62414767915c02c2c16dc90eb96f Mon Sep 17 00:00:00 2001 From: kayomn Date: Tue, 18 Jul 2023 23:52:56 +0100 Subject: [PATCH 1/6] Refactor Kym runtime values to be objects --- source/coral/io.zig | 15 + source/coral/list.zig | 32 ++ source/coral/map.zig | 36 ++ source/ona/app.zig | 44 ++- source/ona/kym.zig | 806 ++++++++++++++------------------------- source/ona/kym/Ast.zig | 169 ++++++-- source/ona/kym/Chunk.zig | 504 ++++++++++++++++++++++++ source/ona/kym/State.zig | 148 ------- source/ona/kym/Table.zig | 122 +++--- source/ona/ona.zig | 10 +- 10 files changed, 1117 insertions(+), 769 deletions(-) create mode 100644 source/ona/kym/Chunk.zig delete mode 100644 source/ona/kym/State.zig diff --git a/source/coral/io.zig b/source/coral/io.zig index 2f53b8d..ce7a6b2 100644 --- a/source/coral/io.zig +++ b/source/coral/io.zig @@ -223,6 +223,21 @@ pub fn copy(target: []Byte, source: []const Byte) void { } } +pub fn compare(this: []const Byte, that: []const Byte) isize { + const range = math.min(this.len, that.len); + var index: usize = 0; + + while (index < range) : (index += 1) { + const difference = @as(isize, @intCast(this[index])) - @as(isize, @intCast(that[index])); + + if (difference != 0) { + return difference; + } + } + + return @as(isize, @intCast(this.len)) - @as(isize, @intCast(that.len)); +} + pub fn ends_with(target: []const Byte, match: []const Byte) bool { if (target.len < match.len) { return false; diff --git a/source/coral/list.zig b/source/coral/list.zig index 48bc310..4e040f6 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 drop(self: *Self, amount: usize) bool { + if (amount > self.values.len) { + return false; + } + + self.values = self.values[0 .. self.values.len - amount]; + + return true; + } + pub fn free(self: *Self) void { if (self.capacity == 0) { return; @@ -49,6 +59,28 @@ pub fn Stack(comptime Value: type) type { }; } + pub fn pack(self: *Self) io.AllocationError!void { + const packed_size = self.values.len; + const buffer = try self.allocator.reallocate(null, @sizeOf(Value) * self.values.len); + + io.copy(buffer, io.bytes_of(self.values)); + + if (self.capacity != 0) { + self.allocator.deallocate(self.values.ptr[0 .. self.capacity]); + } + + self.values = @as([*]Value, @ptrCast(@alignCast(buffer)))[0 .. packed_size]; + self.capacity = packed_size; + } + + pub fn peek(self: *Self) ?Value { + if (self.values.len == 0) { + return null; + } + + return &self.values[self.values.len - 1]; + } + pub fn pop(self: *Self) ?Value { if (self.values.len == 0) { return null; diff --git a/source/coral/map.zig b/source/coral/map.zig index 7dbb777..d42b7fc 100644 --- a/source/coral/map.zig +++ b/source/coral/map.zig @@ -55,6 +55,23 @@ pub fn Slab(comptime Value: type) type { } } + pub fn insert_at(self: *Self, key: usize, value: Value) bool { + if (self.entries.values.len < key) { + return false; + } + + const entry = &self.entries.values[key - 1]; + + if (entry.* == .value) { + return false; + } + + self.next_index = entry.next_index; + entry.* = .{.value = value}; + + return true; + } + pub fn make(allocator: io.Allocator) Self { return .{ .next_index = 0, @@ -143,6 +160,25 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra const Self = @This(); + pub fn remove(self: *Self, key: Key) ?Entry { + const hash_max = math.min(math.max_int(@typeInfo(usize).Int), self.entries.len); + var hashed_key = math.wrap(traits.hash(key), math.min_int(@typeInfo(usize).Int), hash_max); + + while (true) { + const entry = &(self.entries[hashed_key] orelse continue); + + if (traits.match(entry.key, key)) { + const original_entry = entry.*; + + self.entries[hashed_key] = null; + + return original_entry; + } + + hashed_key = (hashed_key +% 1) % hash_max; + } + } + pub fn replace(self: *Self, key: Key, value: Value) io.AllocationError!?Entry { try self.rehash(load_max); diff --git a/source/ona/app.zig b/source/ona/app.zig index 22ea8b8..99a2430 100644 --- a/source/ona/app.zig +++ b/source/ona/app.zig @@ -12,39 +12,53 @@ pub const Manifest = struct { height: u16 = 480, tick_rate: f32 = 60.0, - pub fn load(self: *Manifest, env: *kym.RuntimeEnv, file_access: file.Access) kym.RuntimeError!void { - const manifest = try env.execute_file(file_access, file.Path.from(&.{"app.ona"})); + pub fn load(self: *Manifest, env: *kym.RuntimeEnv, global_ref: ?*const kym.RuntimeRef, file_access: file.Access) kym.RuntimeError!void { + var manifest_ref = try env.execute_file(global_ref, file_access, file.Path.from(&.{"app.ona"})); - defer env.discard(manifest); + defer env.discard(&manifest_ref); - const title = try env.get_field(manifest, "title"); + var title_ref = try kym.get_dynamic_field(env, manifest_ref, "title"); - defer env.discard(title); + defer env.discard(&title_ref); - const title_string = try env.get_string(title); + const title_string = switch (env.unbox(title_ref)) { + .string => |string| string, + else => "", + }; const width = @as(u16, get: { - const ref = try env.get_field(manifest, "width"); + var ref = try kym.get_dynamic_field(env, manifest_ref, "width"); - defer env.discard(ref); + defer env.discard(&ref); - break: get @intFromFloat(env.get_float(ref) catch @as(f64, @floatFromInt(self.width))); + // TODO: Add safety-checks to int cast. + break: get switch (env.unbox(ref)) { + .number => |number| @intFromFloat(number), + else => self.width, + }; }); const height = @as(u16, get: { - const ref = try env.get_field(manifest, "height"); + var ref = try kym.get_dynamic_field(env, manifest_ref, "height"); - defer env.discard(ref); + defer env.discard(&ref); - break: get @intFromFloat(env.get_float(ref) catch @as(f64, @floatFromInt(self.height))); + // TODO: Add safety-checks to int cast. + break: get switch (env.unbox(ref)) { + .number => |number| @intFromFloat(number), + else => self.height, + }; }); const tick_rate = @as(f32, get: { - const ref = try env.get_field(manifest, "tick_rate"); + var ref = try kym.get_dynamic_field(env, manifest_ref, "tick_rate"); - defer env.discard(ref); + defer env.discard(&ref); - break: get @floatCast(env.get_float(ref) catch self.tick_rate); + break: get switch (env.unbox(ref)) { + .number => |number| @floatCast(number), + else => self.tick_rate, + }; }); { diff --git a/source/ona/kym.zig b/source/ona/kym.zig index e535e49..6c1d38c 100644 --- a/source/ona/kym.zig +++ b/source/ona/kym.zig @@ -1,6 +1,6 @@ const Ast = @import("./kym/Ast.zig"); -const State = @import("./kym/State.zig"); +const Chunk = @import("./kym/Chunk.zig"); const Table = @import("./kym/Table.zig"); @@ -12,10 +12,9 @@ const tokens = @import("./kym/tokens.zig"); pub const CallContext = struct { env: *RuntimeEnv, - caller: *const RuntimeRef, - callable: *const RuntimeRef, + caller: ?*const RuntimeRef, userdata: []u8, - args: []const *const RuntimeRef = &.{}, + args: []const ?*const RuntimeRef = &.{}, pub fn arg_at(self: CallContext, index: u8) RuntimeError!*const RuntimeRef { if (!coral.math.is_clamped(index, 0, self.args.len - 1)) { @@ -26,408 +25,106 @@ pub const CallContext = struct { } }; -const Compiler = struct { - state: *State, - opcodes: OpcodeList, - - locals: struct { - buffer: [255][]const coral.io.Byte = [_][]const coral.io.Byte{""} ** 255, - count: u8 = 0, - - const Self = @This(); - - fn declare(self: *Self, identifier: []const u8) CompileError!void { - if (self.count == self.buffer.len) { - return error.TooManyLocals; - } - - self.buffer[self.count] = identifier; - self.count += 1; - } - - fn resolve(self: *Self, local_identifier: []const coral.io.Byte) ?u8 { - if (self.count == 0) { - return null; - } - - var index = @as(u8, self.count - 1); - - while (true) : (index -= 1) { - if (coral.io.equals(local_identifier, self.buffer[index])) { - return index; - } - - if (index == 0) { - return null; - } - } - } - }, - - const CompileError = coral.io.AllocationError || error { - UndefinedLocal, - TooManyLocals, - }; - - const LocalsList = coral.list.Stack([]const u8); - - const OpcodeList = coral.list.Stack(Opcode); - - fn compile_ast(self: *Compiler, ast: Ast) CompileError!void { - for (ast.list_statements()) |statement| { - switch (statement) { - .return_expression => |return_expression| { - try self.compile_expression(return_expression); - }, - - .return_nothing => { - try self.opcodes.push_one(.push_nil); - }, - - .set_local => |local| { - try self.compile_expression(local.expression); - - if (self.locals.resolve(local.identifier)) |index| { - try self.opcodes.push_one(.{.set_local = index}); - } else { - try self.locals.declare(local.identifier); - } - }, - } - } - } - - fn compile_expression(self: *Compiler, expression: Ast.Expression) CompileError!void { - const is_zero = struct { - fn is_zero(utf8: []const u8) bool { - return coral.io.equals(utf8, "0") or coral.io.equals(utf8, "0.0"); - } - }.is_zero; - - const number_format = coral.utf8.DecimalFormat{ - .delimiter = "_", - .positive_prefix = .none, - }; - - switch (expression) { - .nil_literal => try self.opcodes.push_one(.push_nil), - .true_literal => try self.opcodes.push_one(.push_true), - .false_literal => try self.opcodes.push_one(.push_false), - - .number_literal => |literal| { - const parsed_number = number_format.parse(literal, State.Float); - - coral.debug.assert(parsed_number != null); - - try self.opcodes.push_one(if (is_zero(literal)) .push_zero else .{.push_number = parsed_number.?}); - }, - - .string_literal => |literal| { - try self.opcodes.push_one(.{ - .push_object = try self.state.acquire_interned(literal, &string_info), - }); - }, - - .table_literal => |fields| { - if (fields.values.len > coral.math.max_int(@typeInfo(u32).Int)) { - return error.OutOfMemory; - } - - for (fields.values) |field| { - try self.compile_expression(field.expression); - - try self.opcodes.push_one(.{ - .push_object = try self.state.acquire_interned(field.identifier, &string_info), - }); - } - - try self.opcodes.push_one(.{.push_table = @intCast(fields.values.len)}); - }, - - .binary_operation => |operation| { - try self.compile_expression(operation.lhs_expression.*); - try self.compile_expression(operation.rhs_expression.*); - - try self.opcodes.push_one(switch (operation.operator) { - .addition => .add, - .subtraction => .sub, - .multiplication => .mul, - .divsion => .div, - .greater_equals_comparison => .eql, - .greater_than_comparison => .cgt, - .equals_comparison => .cge, - .less_than_comparison => .clt, - .less_equals_comparison => .cle, - }); - }, - - .unary_operation => |operation| { - try self.compile_expression(operation.expression.*); - - try self.opcodes.push_one(switch (operation.operator) { - .boolean_negation => .not, - .numeric_negation => .neg, - }); - }, - - .grouped_expression => |grouped_expression| { - try self.compile_expression(grouped_expression.*); - }, - - .get_local => |local| { - try self.opcodes.push_one(.{ - .get_local = self.locals.resolve(local) orelse return error.UndefinedLocal, - }); - }, - } - } - - fn free(self: *Compiler) void { - for (self.opcodes.values) |opcode| { - if (opcode == .push_object) { - self.state.release(opcode.push_object); - } - } - - self.opcodes.free(); - } - - fn list_opcodes(self: Compiler) []const Opcode { - return self.opcodes.values; - } - - fn make(allocator: coral.io.Allocator, state: *State) Compiler { - return .{ - .locals = .{}, - .opcodes = OpcodeList.make(allocator), - .state = state, - }; - } +pub const DynamicObject = struct { + userdata: []coral.io.Byte, + typeinfo: *const Typeinfo, }; +pub const Float = f64; + pub const IndexContext = struct { env: *RuntimeEnv, - indexable: *const RuntimeRef, - index: *const RuntimeRef, - userdata: []u8, -}; - -pub const ObjectInfo = struct { - call: *const fn (context: CallContext) RuntimeError!*RuntimeRef = default_call, - clean: *const fn (userdata: []u8) void = default_clean, - get: *const fn (context: IndexContext) RuntimeError!*RuntimeRef = default_get, - set: *const fn (context: IndexContext, value: *const RuntimeRef) RuntimeError!void = default_set, - - fn cast(object_info: *const anyopaque) *const ObjectInfo { - return @ptrCast(@alignCast(object_info)); - } - - fn default_call(context: CallContext) RuntimeError!*RuntimeRef { - return context.env.raise(error.BadOperation, "attempt to call non-callable"); - } - - fn default_clean(_: []u8) void { - // Nothing to clean up by default. - } - - fn default_get(context: IndexContext) RuntimeError!*RuntimeRef { - return context.env.raise(error.BadOperation, "attempt to get non-indexable"); - } - - fn default_set(context: IndexContext, _: *const RuntimeRef) RuntimeError!void { - return context.env.raise(error.BadOperation, "attempt to set non-indexable"); - } -}; - -pub const Opcode = union (enum) { - push_nil, - push_true, - push_false, - push_zero, - push_number: State.Float, - push_table: u32, - push_object: *State.Object, - - set_local: u8, - get_local: u8, - - not, - neg, - - add, - sub, - mul, - div, - - eql, - cgt, - clt, - cge, - cle, + userdata: []coral.io.Byte, + index_ref: ?*const RuntimeRef, }; pub const RuntimeEnv = struct { allocator: coral.io.Allocator, err_writer: coral.io.Writer, - bound_refs: VariantSlab, - state: State, + local_refs: RefStack, + frames: FrameStack, + ref_values: RefSlab, + + const FrameStack = coral.list.Stack(struct { + locals_top: usize, + }); + + const RefStack = coral.list.Stack(?*RuntimeRef); pub const Options = struct { out_writer: coral.io.Writer = coral.io.null_writer, err_writer: coral.io.Writer = coral.io.null_writer, }; + const RefSlab = coral.map.Slab(struct { + ref_count: usize, + + object: union (enum) { + false, + true, + number: Float, + string: []coral.io.Byte, + dynamic: *DynamicObject, + }, + }); + pub const ScriptSource = struct { name: []const coral.io.Byte, data: []const coral.io.Byte, }; - const VariantSlab = coral.map.Slab(State.Variant); + pub fn acquire(self: *RuntimeEnv, ref: ?*const RuntimeRef) ?*RuntimeRef { + const key = @intFromPtr(ref); + var ref_data = self.ref_values.remove(key); - pub fn discard(self: *RuntimeEnv, ref: *RuntimeRef) void { - coral.debug.assert(self.bound_refs.remove(@intFromPtr(ref)) != null); + coral.debug.assert(ref_data != null); + + ref_data.?.ref_count += 1; + + coral.debug.assert(self.ref_values.insert_at(key, ref_data.?)); + + return @ptrFromInt(key); } - pub fn execute_chunk(self: *RuntimeEnv, name: []const coral.io.Byte, opcodes: []const Opcode) RuntimeError!*RuntimeRef { - _ = name; + pub fn call( + self: *RuntimeEnv, + caller_ref: *const RuntimeRef, + callable_ref: *const RuntimeRef, + arg_refs: []const *const RuntimeRef, + ) RuntimeError!*RuntimeRef { + const callable = (try callable_ref.fetch(self)).to_object(); - for (opcodes) |opcode| { - switch (opcode) { - .push_nil => try self.state.push_value(.nil), - .push_true => try self.state.push_value(.true), - .push_false => try self.state.push_value(.false), - .push_zero => try self.state.push_value(.{.number = 0}), - .push_number => |number| try self.state.push_value(.{.number = number}), + return callable.userinfo.call(.{ + .env = self, + .caller = caller_ref, + .callable = callable_ref, + .userdata = callable.userdata, + .args = arg_refs, + }); + } - .push_table => |size| { - var table = Table.make(self.allocator, &self.state); + pub fn discard(self: *RuntimeEnv, ref: *?*RuntimeRef) void { + const key = @intFromPtr(ref.* orelse return); + var ref_data = self.ref_values.remove(key) orelse unreachable; - errdefer table.free(); + coral.debug.assert(ref_data.ref_count != 0); - { - var popped = @as(usize, 0); + ref_data.ref_count -= 1; - while (popped < size) : (popped += 1) { - try table.set_field( - try to_object(self, try self.state.pop_value()), - try self.state.pop_value()); - } - } - - const table_object = try self.state.acquire_new(coral.io.bytes_of(&table), &table_info); - - errdefer self.state.release(table_object); - - try self.state.push_value(.{.object = table_object}); - }, - - .push_object => |object| { - const acquired_object = self.state.acquire_instance(object); - - errdefer self.state.release(acquired_object); - - try self.state.push_value(.{.object = acquired_object}); - }, - - .set_local => |local| { - if (!self.state.set_value(local, try self.state.pop_value())) { - return self.raise(error.BadOperation, "invalid local set"); - } - }, - - .get_local => |local| { - try self.state.push_value(self.state.get_value(local)); - }, - - .not => { - try self.state.push_value(switch (try self.state.pop_value()) { - .nil => return self.raise(error.BadOperation, "cannot convert nil to true or false"), - .false => .true, - .true => .false, - .number => return self.raise(error.BadOperation, "cannot convert a number to true or false"), - .object => return self.raise(error.BadOperation, "cannot convert an object to true or false"), - }); - }, - - .neg => { - try self.state.push_value(.{.number = -(try to_number(self, try self.state.pop_value()))}); - }, - - .add => { - const rhs_number = try to_number(self, try self.state.pop_value()); - const lhs_number = try to_number(self, try self.state.pop_value()); - - try self.state.push_value(.{.number = lhs_number + rhs_number}); - }, - - .sub => { - const rhs_number = try to_number(self, try self.state.pop_value()); - const lhs_number = try to_number(self, try self.state.pop_value()); - - try self.state.push_value(.{.number = lhs_number - rhs_number}); - }, - - .mul => { - const rhs_number = try to_number(self, try self.state.pop_value()); - const lhs_number = try to_number(self, try self.state.pop_value()); - - try self.state.push_value(.{.number = lhs_number * rhs_number}); - }, - - .div => { - const rhs_number = try to_number(self, try self.state.pop_value()); - const lhs_number = try to_number(self, try self.state.pop_value()); - - try self.state.push_value(.{.number = lhs_number / rhs_number}); - }, - - .eql => { - const rhs = try self.state.pop_value(); - const lhs = try self.state.pop_value(); - - try self.state.push_value(if (lhs.equals(rhs)) .true else .false); - }, - - .cgt => { - const rhs_number = try to_number(self, try self.state.pop_value()); - const lhs_number = try to_number(self, try self.state.pop_value()); - - try self.state.push_value(if (lhs_number > rhs_number) .true else .false); - }, - - .clt => { - const rhs_number = try to_number(self, try self.state.pop_value()); - const lhs_number = try to_number(self, try self.state.pop_value()); - - try self.state.push_value(if (lhs_number < rhs_number) .true else .false); - }, - - .cge => { - const rhs_number = try to_number(self, try self.state.pop_value()); - const lhs_number = try to_number(self, try self.state.pop_value()); - - try self.state.push_value(if (lhs_number >= rhs_number) .true else .false); - }, - - .cle => { - const rhs_number = try to_number(self, try self.state.pop_value()); - const lhs_number = try to_number(self, try self.state.pop_value()); - - try self.state.push_value(if (lhs_number <= rhs_number) .true else .false); - }, + if (ref_data.ref_count == 0) { + switch (ref_data.object) { + .string => |string| self.allocator.deallocate(string), + .dynamic => |virtual| self.allocator.deallocate(virtual), + else => {}, } + } else { + coral.debug.assert(self.ref_values.insert_at(key, ref_data)); } - const return_value = try self.state.pop_value(); - - errdefer if (return_value == .object) { - self.state.release(return_value.object); - }; - - return @ptrFromInt(try self.bound_refs.insert(return_value)); + ref.* = null; } - pub fn execute_file(self: *RuntimeEnv, file_access: file.Access, file_path: file.Path) RuntimeError!*RuntimeRef { + pub fn execute_file(self: *RuntimeEnv, global_ref: ?*const RuntimeRef, file_access: file.Access, file_path: file.Path) RuntimeError!?*RuntimeRef { const error_message = "failed to load file"; const file_data = (try file.allocate_and_load(self.allocator, file_access, file_path)) orelse { @@ -436,13 +133,13 @@ pub const RuntimeEnv = struct { defer self.allocator.deallocate(file_data); - return self.execute_script(.{ + return self.execute_script(global_ref, .{ .name = file_path.to_string() orelse return self.raise(error.SystemFailure, error_message), .data = file_data, }); } - pub fn execute_script(self: *RuntimeEnv, source: ScriptSource) RuntimeError!*RuntimeRef { + pub fn execute_script(self: *RuntimeEnv, global_ref: ?*const RuntimeRef, source: ScriptSource) RuntimeError!?*RuntimeRef { var ast = Ast.make(self.allocator); defer ast.free(); @@ -456,198 +153,271 @@ pub const RuntimeEnv = struct { }; } - var compiler = Compiler.make(self.allocator, &self.state); + var exe = Chunk.make(self); - defer compiler.free(); + defer exe.free(); - compiler.compile_ast(ast) catch |compile_error| return switch (compile_error) { - error.OutOfMemory => error.OutOfMemory, - error.UndefinedLocal => self.raise(error.BadOperation, "use of undefined local"), - error.TooManyLocals => self.raise(error.OutOfMemory, "functions cannot contain more than 255 locals"), - }; + try exe.compile_ast(ast); - return self.execute_chunk(source.name, compiler.list_opcodes()); + return exe.execute(global_ref, source.name); } - pub fn free(self: *RuntimeEnv) void { - self.bound_refs.free(); - self.state.free(); + pub fn frame_pop(self: *RuntimeEnv) RuntimeError!void { + const frame = self.frames.pop() orelse return self.raise(error.IllegalState, "stack underflow"); + + coral.debug.assert(self.local_refs.drop(self.local_refs.values.len - frame.locals_top)); } - pub fn get_field(self: *RuntimeEnv, indexable: *const RuntimeRef, field: []const u8) RuntimeError!*RuntimeRef { - const interned_field = try self.intern(field); - - defer self.discard(interned_field); - - const indexable_object = try to_object(self, try indexable.fetch(self)); - - return ObjectInfo.cast(indexable_object.userinfo).get(.{ - .env = self, - .indexable = indexable, - .index = interned_field, - .userdata = indexable_object.userdata, + pub fn frame_push(self: *RuntimeEnv) RuntimeError!void { + return self.frames.push_one(.{ + .locals_top = self.local_refs.values.len, }); } - pub fn get_float(self: *RuntimeEnv, ref: *const RuntimeRef) RuntimeError!State.Float { - return to_number(self, try ref.fetch(self)); + pub fn free(self: *RuntimeEnv) void { + self.local_refs.free(); + self.ref_values.free(); } - pub fn get_string(self: *RuntimeEnv, ref: *const RuntimeRef) RuntimeError![]const u8 { - const object = try to_object(self, try ref.fetch(self)); + pub fn get_dynamic(self: *RuntimeEnv, indexable_ref: ?*const RuntimeRef, index_ref: ?*const RuntimeRef) RuntimeError!?*RuntimeRef { + return switch (self.unbox(indexable_ref)) { + .nil => self.raise(error.BadOperation, "nil is immutable"), + .boolean => self.raise(error.BadOperation, "boolean has no such index"), + .number => self.raise(error.BadOperation, "number has no such index"), + .string => self.raise(error.BadOperation, "string has no such index"), - if (ObjectInfo.cast(object.userinfo) != &string_info) { - return self.raise(error.BadOperation, "object is not a string"); - } - - return object.userdata; + .dynamic => |dynamic| dynamic.typeinfo.get(.{ + .userdata = dynamic.userdata, + .env = self, + .index_ref = index_ref, + }), + }; } - pub fn intern(self: *RuntimeEnv, data: []const u8) RuntimeError!*RuntimeRef { - const data_object = try self.state.acquire_interned(data, &string_info); + pub fn local_get(self: *RuntimeEnv, local: u8) RuntimeError!?*RuntimeRef { + return self.local_refs.values[local]; + } - errdefer self.state.release(data_object); + pub fn local_pop(self: *RuntimeEnv) RuntimeError!?*RuntimeRef { + return self.local_refs.pop() orelse self.raise(error.IllegalState, "stack underflow"); + } - return @ptrFromInt(try self.bound_refs.insert(.{.object = data_object})); + pub fn local_push_ref(self: *RuntimeEnv, ref: ?*RuntimeRef) RuntimeError!void { + return self.local_refs.push_one(self.acquire(ref)); + } + + pub fn local_push_boolean(self: *RuntimeEnv, boolean: bool) RuntimeError!void { + return self.local_refs.push_one(try self.new_boolean(boolean)); + } + + pub fn local_push_number(self: *RuntimeEnv, number: Float) RuntimeError!void { + return self.local_refs.push_one(try self.new_number(number)); + } + + pub fn local_set(self: *RuntimeEnv, local: u8, value: ?*RuntimeRef) RuntimeError!void { + self.local_refs.values[local] = self.acquire(value); } pub fn make(allocator: coral.io.Allocator, options: Options) RuntimeError!RuntimeEnv { - var env = RuntimeEnv{ - .allocator = allocator, - .bound_refs = VariantSlab.make(allocator), - .state = State.make(allocator), + return .{ + .local_refs = RefStack.make(allocator), + .ref_values = RefSlab.make(allocator), + .frames = FrameStack.make(allocator), .err_writer = options.err_writer, + .allocator = allocator, }; - - return env; } - pub fn new_object(self: *RuntimeEnv, userdata: []const u8, info: *const ObjectInfo) RuntimeError!*RuntimeRef { - const data_object = try self.state.acquire_new(userdata, info); - - defer self.state.release(data_object); - - return @ptrFromInt(try self.bound_refs.insert(.{.object = data_object})); + pub fn new_boolean(self: *RuntimeEnv, value: bool) RuntimeError!?*RuntimeRef { + return @ptrFromInt(try self.ref_values.insert(.{ + .ref_count = 1, + .object = if (value) .true else .false, + })); } - pub fn raise(self: *RuntimeEnv, runtime_error: RuntimeError, error_message: []const u8) RuntimeError { + pub fn new_dynamic(self: *RuntimeEnv, userdata: []const coral.io.Byte, typeinfo: *const Typeinfo) RuntimeError!*RuntimeRef { + const userdata_copy = try coral.io.allocate_copy(self.allocator, userdata); + + errdefer self.allocator.deallocate(userdata_copy); + + const dynamic_object = try coral.io.allocate_one(self.allocator, DynamicObject{ + .typeinfo = typeinfo, + .userdata = userdata_copy, + }); + + errdefer self.allocator.deallocate(dynamic_object); + + return @ptrFromInt(try self.ref_values.insert(.{ + .ref_count = 1, + .object = .{.dynamic = dynamic_object}, + })); + } + + pub fn new_number(self: *RuntimeEnv, value: Float) RuntimeError!?*RuntimeRef { + return @ptrFromInt(try self.ref_values.insert(.{ + .ref_count = 1, + .object = .{.number = value}, + })); + } + + pub fn new_string(self: *RuntimeEnv, string_data: []const coral.io.Byte) RuntimeError!?*RuntimeRef { + const string_copy = try coral.io.allocate_copy(self.allocator, string_data); + + errdefer self.allocator.deallocate(string_copy); + + return @ptrFromInt(try self.ref_values.insert(.{ + .ref_count = 1, + .object = .{.string = string_copy}, + })); + } + + pub fn raise(self: *RuntimeEnv, error_value: RuntimeError, error_message: []const u8) RuntimeError { // TODO: Print stack trace from state. coral.utf8.print_formatted(self.err_writer, "{name}@{line}: {message}", .{ .name = "???", .line = @as(u64, 0), .message = error_message, - }) catch return error.SystemFailure; + }) catch {}; - return runtime_error; + return error_value; + } + + pub fn set_dynamic(self: *RuntimeEnv, indexable_ref: ?*const RuntimeRef, index_ref: ?*const RuntimeRef, value_ref: ?*const RuntimeRef) RuntimeError!void { + return switch (self.unbox(indexable_ref)) { + .nil => self.raise(error.BadOperation, "nil is immutable"), + .boolean => self.raise(error.BadOperation, "boolean is immutable"), + .number => self.raise(error.BadOperation, "number is immutable"), + .string => self.raise(error.BadOperation, "string is immutable"), + + .dynamic => |dynamic| dynamic.typeinfo.set(.{ + .userdata = dynamic.userdata, + .env = self, + .index_ref = index_ref, + }, value_ref), + }; + } + + pub fn unbox(self: *RuntimeEnv, ref: ?*const RuntimeRef) Unboxed { + const ref_data = self.ref_values.lookup(@intFromPtr(ref orelse return .nil)); + + coral.debug.assert(ref_data != null); + + return switch (ref_data.?.object) { + .false => .{.boolean = false}, + .true => .{.boolean = true}, + .number => |number| .{.number = number}, + .string => |string| .{.string = string}, + .dynamic => |dynamic| .{.dynamic = dynamic}, + }; } }; -pub const RuntimeError = coral.io.AllocationError || State.PopError || error { - BadSyntax, - BadOperation, +pub const RuntimeError = coral.io.AllocationError || error { + IllegalState, SystemFailure, + TypeMismatch, + BadOperation, + BadSyntax, }; -pub const RuntimeRef = opaque { - fn fetch(self: *const RuntimeRef, env: *RuntimeEnv) RuntimeError!State.Variant { - return env.bound_refs.lookup(@intFromPtr(self)) orelse env.raise(error.BadOperation, "stale ref"); +pub const RuntimeRef = opaque {}; + +pub const TestContext = struct { + env: *RuntimeEnv, + userdata: []const coral.io.Byte, + other_ref: ?*const RuntimeRef, +}; + +pub const Typeinfo = struct { + call: *const fn (context: CallContext) RuntimeError!?*RuntimeRef, + clean: *const fn (userdata: []u8) void, + get: *const fn (context: IndexContext) RuntimeError!?*RuntimeRef, + set: *const fn (context: IndexContext, value: ?*const RuntimeRef) RuntimeError!void, + test_difference: *const fn (context: TestContext) RuntimeError!Float = default_test_difference, + test_equality: *const fn (context: TestContext) RuntimeError!bool = default_test_equality, + + fn default_test_difference(context: TestContext) RuntimeError!Float { + return context.env.raise(error.TypeMismatch, "object is not comparable"); + } + + fn default_test_equality(context: TestContext) RuntimeError!bool { + return switch (context.env.unbox(context.other_ref)) { + .dynamic => |dynamic| context.userdata.ptr == dynamic.userdata.ptr, + else => false, + }; } }; -fn table_clean(userdata: []u8) void { - @as(*Table, @ptrCast(@alignCast(userdata.ptr))).free(); -} - -fn table_get(context: IndexContext) RuntimeError!*RuntimeRef { - const table = @as(*Table, @ptrCast(@alignCast(context.userdata.ptr))); - - switch (try context.index.fetch(context.env)) { - .nil => return context.env.raise(error.BadOperation, "cannot index a table with nil"), - .true => return context.env.raise(error.BadOperation, "cannot index a table with true"), - .false => return context.env.raise(error.BadOperation, "cannot index a table with false"), - - .object => |index_object| { - const value = table.get_field(index_object); - - errdefer if (value == .object) { - context.env.state.release(value.object); - }; - - return @ptrFromInt(try context.env.bound_refs.insert(value)); - }, - - .number => |index_number| { - const value = table.get_index(@intFromFloat(index_number)); - - errdefer if (value == .object) { - context.env.state.release(value.object); - }; - - return @ptrFromInt(try context.env.bound_refs.insert(value)); - }, - } -} - -const table_info = ObjectInfo{ - .clean = table_clean, - .get = table_get, - .set = table_set, +pub const Unboxed = union (enum) { + nil, + boolean: bool, + number: Float, + string: []const u8, + dynamic: *const DynamicObject, }; -fn table_set(context: IndexContext, value: *const RuntimeRef) RuntimeError!void { - const table = @as(*Table, @ptrCast(@alignCast(context.userdata.ptr))); +pub fn get_dynamic_field(env: *RuntimeEnv, indexable: ?*const RuntimeRef, field: []const u8) RuntimeError!?*RuntimeRef { + var interned_field = try env.new_string(field); - switch (try context.index.fetch(context.env)) { - .nil => return context.env.raise(error.BadOperation, "cannot index a table with nil"), - .true => return context.env.raise(error.BadOperation, "cannot index a table with true"), - .false => return context.env.raise(error.BadOperation, "cannot index a table with false"), + defer env.discard(&interned_field); - .object => |index_object| { - const fetched_value = try value.fetch(context.env); - - if (fetched_value == .object) { - try table.set_field(index_object, .{ - .object = context.env.state.acquire_instance(fetched_value.object), - }); - } else { - try table.set_field(index_object, fetched_value); - } - }, - - .number => |index_number| { - const fetched_value = try value.fetch(context.env); - - if (fetched_value == .object) { - try table.set_index(@intFromFloat(index_number), .{ - .object = context.env.state.acquire_instance(fetched_value.object), - }); - } else { - try table.set_index(@intFromFloat(index_number), fetched_value); - } - }, - } + return env.get_dynamic(indexable, interned_field); } -fn to_number(env: *RuntimeEnv, variant: State.Variant) RuntimeError!State.Float { - return switch (variant) { - .nil => env.raise(error.BadOperation, "cannot convert nil to number"), - .true => env.raise(error.BadOperation, "cannot convert true to number"), - .false => env.raise(error.BadOperation, "cannot convert false to number"), - .number => |number| number, - .object => env.raise(error.BadOperation, "cannot convert object to number"), +pub fn new_table(env: *RuntimeEnv) RuntimeError!?*RuntimeRef { + var table = Table.make(env); + + errdefer table.free(); + + return try env.new_dynamic(coral.io.bytes_of(&table), &Table.typeinfo); +} + +pub fn test_difference(env: *RuntimeEnv, lhs_ref: ?*const RuntimeRef, rhs_ref: ?*const RuntimeRef) RuntimeError!Float { + return switch (env.unbox(lhs_ref)) { + .nil => env.raise(error.TypeMismatch, "cannot compare nil objects"), + .boolean => env.raise(error.TypeMismatch, "cannot compare boolean objects"), + + .number => |lhs_number| switch (env.unbox(rhs_ref)) { + .number => |rhs_number| rhs_number - lhs_number, + else => env.raise(error.TypeMismatch, "right-hand object is not comparable with number objects"), + }, + + .string => |lhs_string| switch (env.unbox(rhs_ref)) { + .string => |rhs_string| @floatFromInt(coral.io.compare(lhs_string, rhs_string)), + else => env.raise(error.TypeMismatch, "right-hand object is not comparable with string objects"), + }, + + .dynamic => |lhs_dynamic| lhs_dynamic.typeinfo.test_difference(.{ + .env = env, + .userdata = lhs_dynamic.userdata, + .other_ref = rhs_ref, + }), }; } -fn to_object(env: *RuntimeEnv, variant: State.Variant) RuntimeError!*State.Object { - return switch (variant) { - .nil => env.raise(error.BadOperation, "cannot convert nil to object"), - .true => env.raise(error.BadOperation, "cannot convert true to object"), - .false => env.raise(error.BadOperation, "cannot convert false to object"), - .number => env.raise(error.BadOperation, "cannot convert number to object"), - .object => |object| object, +pub fn test_equality(env: *RuntimeEnv, lhs_ref: ?*const RuntimeRef, rhs_ref: ?*const RuntimeRef) RuntimeError!bool { + return switch (env.unbox(lhs_ref)) { + .nil => (rhs_ref == null), + + .boolean => |lhs_boolean| switch (env.unbox(rhs_ref)) { + .boolean => |rhs_boolean| rhs_boolean == lhs_boolean, + else => false, + }, + + .number => |lhs_number| switch (env.unbox(rhs_ref)) { + .number => |rhs_number| lhs_number == rhs_number, + else => false, + }, + + .string => |lhs_string| switch (env.unbox(rhs_ref)) { + .string => |rhs_string| coral.io.equals(lhs_string, rhs_string), + else => false, + }, + + .dynamic => |lhs_dynamic| try lhs_dynamic.typeinfo.test_equality(.{ + .env = env, + .userdata = lhs_dynamic.userdata, + .other_ref = rhs_ref, + }), }; } - -const string_info = ObjectInfo{ - -}; diff --git a/source/ona/kym/Ast.zig b/source/ona/kym/Ast.zig index cf1bf71..cbf7335 100755 --- a/source/ona/kym/Ast.zig +++ b/source/ona/kym/Ast.zig @@ -5,17 +5,22 @@ const tokens = @import("./tokens.zig"); allocator: coral.io.Allocator, arena: coral.arena.Stacking, statements: Statement.List, -error_message: []const u8, +error_message: []const coral.io.Byte, pub const Expression = union (enum) { nil_literal, true_literal, false_literal, - number_literal: []const u8, - string_literal: []const u8, + number_literal: []const coral.io.Byte, + string_literal: []const coral.io.Byte, table_literal: NamedList, grouped_expression: *Expression, - get_local: []const u8, + get_local: []const coral.io.Byte, + + call_global: struct { + identifier: []const coral.io.Byte, + argument_expressions: List, + }, binary_operation: struct { operator: BinaryOperator, @@ -55,7 +60,7 @@ pub const Expression = union (enum) { }; pub const NamedList = coral.list.Stack(struct { - identifier: []const u8, + identifier: []const coral.io.Byte, expression: Expression, }); @@ -80,6 +85,11 @@ pub const Statement = union (enum) { expression: Expression, }, + call_global: struct { + identifier: []const coral.io.Byte, + argument_expressions: Expression.List, + }, + const List = coral.list.Stack(Statement); }; @@ -88,39 +98,40 @@ const UnaryOperator = enum { numeric_negation, }; -fn binary_operation_parser(comptime parse_next: ExpressionParser, comptime operators: []const Expression.BinaryOperator) ExpressionParser { - return struct { +fn binary_operation_parser( + comptime parse_next: ExpressionParser, + comptime operators: []const Expression.BinaryOperator) ExpressionParser { + + const BinaryOperationParser = struct { fn parse(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression { + const allocator = self.arena.as_allocator(); var expression = try parse_next(self, tokenizer); - { - const allocator = self.arena.as_allocator(); + inline for (operators) |operator| { + const token = comptime operator.token(); - inline for (operators) |operator| { - const token = comptime operator.token(); + if (tokenizer.is_token(coral.io.tag_of(token))) { + tokenizer.step(); - if (tokenizer.is_token(coral.io.tag_of(token))) { - tokenizer.step(); - - if (tokenizer.token == null) { - return self.raise( - "expected right-hand side of expression after `" ++ comptime token.text() ++ "`"); - } - - expression = .{ - .binary_operation = .{ - .operator = operator, - .lhs_expression = try coral.io.allocate_one(allocator, expression), - .rhs_expression = try coral.io.allocate_one(allocator, try parse_next(self, tokenizer)), - }, - }; + if (tokenizer.token == null) { + return self.raise("expected other half of expression after `" ++ comptime token.text() ++ "`"); } + + expression = .{ + .binary_operation = .{ + .operator = operator, + .lhs_expression = try coral.io.allocate_one(allocator, expression), + .rhs_expression = try coral.io.allocate_one(allocator, try parse_next(self, tokenizer)), + }, + }; } } return expression; } - }.parse; + }; + + return BinaryOperationParser.parse; } pub fn free(self: *Self) void { @@ -150,9 +161,12 @@ pub fn list_statements(self: Self) []const Statement { pub fn parse(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!void { self.free(); + const allocator = self.arena.as_allocator(); var has_returned = false; while (true) { + const no_effect_message = "statement has no effect"; + tokenizer.skip(.newline); switch (tokenizer.token orelse return) { @@ -181,8 +195,6 @@ pub fn parse(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!void { .local => |identifier| { tokenizer.step(); - const no_effect_message = "statement has no effect"; - switch (tokenizer.token orelse return self.raise(no_effect_message)) { .newline => return self.raise(no_effect_message), @@ -209,6 +221,45 @@ pub fn parse(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!void { } }, + .global => |identifier| { + tokenizer.step(); + + switch (tokenizer.token orelse return self.raise(no_effect_message)) { + .newline => return self.raise(no_effect_message), + + .symbol_paren_left => { + tokenizer.step(); + + var expressions_list = Expression.List.make(allocator); + + while (true) { + if (tokenizer.is_token(.symbol_paren_right)) { + break; + } + + try expressions_list.push_one(try self.parse_expression(tokenizer)); + + switch (tokenizer.token orelse return self.raise("unexpected end after after `(`")) { + .symbol_comma => continue, + .symbol_paren_right => break, + else => return self.raise("expected `)` or argument after `(`"), + } + } + + tokenizer.step(); + + try self.statements.push_one(.{ + .call_global = .{ + .argument_expressions = expressions_list, + .identifier = identifier, + }, + }); + }, + + else => return self.raise("expected `=` after local"), + } + }, + else => return self.raise("invalid statement"), } } @@ -252,6 +303,24 @@ fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression return Expression{.grouped_expression = try coral.io.allocate_one(allocator, expression)}; }, + .keyword_nil => { + tokenizer.step(); + + return .nil_literal; + }, + + .keyword_true => { + tokenizer.step(); + + return .true_literal; + }, + + .keyword_false => { + tokenizer.step(); + + return .false_literal; + }, + .number => |value| { tokenizer.step(); @@ -264,6 +333,48 @@ fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression return Expression{.string_literal = value}; }, + .global => |identifier| { + tokenizer.skip(.newline); + + var expression_list = Expression.List.make(allocator); + + while (true) { + switch (tokenizer.token orelse return self.raise("expected expression or `)` after `(`")) { + .symbol_paren_right => { + tokenizer.step(); + + return Expression{ + .call_global = .{ + .identifier = identifier, + .argument_expressions = expression_list, + }, + }; + }, + + else => { + try expression_list.push_one(try self.parse_expression(tokenizer)); + + switch (tokenizer.token orelse return self.raise("expected `,` or `)` after argument")) { + .symbol_comma => continue, + + .symbol_paren_right => { + tokenizer.step(); + + return Expression{ + .call_global = .{ + .identifier = identifier, + .argument_expressions = expression_list, + }, + }; + }, + + else => return self.raise("expected `,` or `)` after argument"), + } + }, + } + } + }, + .local => |identifier| { tokenizer.step(); diff --git a/source/ona/kym/Chunk.zig b/source/ona/kym/Chunk.zig new file mode 100644 index 0000000..7ede24b --- /dev/null +++ b/source/ona/kym/Chunk.zig @@ -0,0 +1,504 @@ +const Ast = @import("./Ast.zig"); + +const coral = @import("coral"); + +const kym = @import("../kym.zig"); + +env: *kym.RuntimeEnv, +constant_refs: RefList, +opcodes: OpcodeList, + +const AstCompiler = struct { + chunk: *Self, + local_identifiers_buffer: [255][]const coral.io.Byte = [_][]const coral.io.Byte{""} ** 255, + local_identifiers_count: u8 = 0, + + fn compile_expression(self: *AstCompiler, expression: Ast.Expression) kym.RuntimeError!void { + const number_format = coral.utf8.DecimalFormat{ + .delimiter = "_", + .positive_prefix = .none, + }; + + switch (expression) { + .nil_literal => try self.chunk.append_opcode(.push_nil), + .true_literal => try self.chunk.append_opcode(.push_true), + .false_literal => try self.chunk.append_opcode(.push_false), + + .number_literal => |literal| { + const parsed_number = number_format.parse(literal, kym.Float); + + coral.debug.assert(parsed_number != null); + + try self.chunk.append_opcode(.{ + .push_const = try self.chunk.declare_constant_number(parsed_number.?), + }); + }, + + .string_literal => |literal| { + try self.chunk.append_opcode(.{.push_const = try self.chunk.declare_constant_string(literal)}); + }, + + .table_literal => |fields| { + if (fields.values.len > coral.math.max_int(@typeInfo(u32).Int)) { + return error.OutOfMemory; + } + + for (fields.values) |field| { + try self.compile_expression(field.expression); + + try self.chunk.append_opcode(.{ + .push_const = try self.chunk.declare_constant_string(field.identifier), + }); + } + + try self.chunk.append_opcode(.{ + .push_table = .{.field_count = @intCast(fields.values.len)}}); + }, + + .binary_operation => |operation| { + try self.compile_expression(operation.lhs_expression.*); + try self.compile_expression(operation.rhs_expression.*); + + try self.chunk.append_opcode(switch (operation.operator) { + .addition => .add, + .subtraction => .sub, + .multiplication => .mul, + .divsion => .div, + .greater_equals_comparison => .eql, + .greater_than_comparison => .cgt, + .equals_comparison => .cge, + .less_than_comparison => .clt, + .less_equals_comparison => .cle, + }); + }, + + .unary_operation => |operation| { + try self.compile_expression(operation.expression.*); + + try self.chunk.append_opcode(switch (operation.operator) { + .boolean_negation => .not, + .numeric_negation => .neg, + }); + }, + + .grouped_expression => |grouped_expression| { + try self.compile_expression(grouped_expression.*); + }, + + .get_local => |local| { + try self.chunk.append_opcode(.{ + .local_push_ref = self.resolve_local(local) orelse return self.chunk.env.raise(error.OutOfMemory, "undefined local"), + }); + }, + + .call_global => |call| { + if (call.argument_expressions.values.len > coral.math.max_int(@typeInfo(u8).Int)) { + return self.chunk.env.raise(error.OutOfMemory, "functions may receive a maximum of 255 locals"); + } + + for (call.argument_expressions.values) |argument_expression| { + try self.compile_expression(argument_expression); + } + + try self.chunk.append_opcode(.{.push_const = try self.chunk.declare_constant_string(call.identifier)}); + + try self.chunk.append_opcode(.{ + .global_call = .{ + .arg_count = @intCast(call.argument_expressions.values.len), + }, + }); + }, + } + } + + fn compile_statement(self: *AstCompiler, statement: Ast.Statement) kym.RuntimeError!void { + switch (statement) { + .return_expression => |return_expression| try self.compile_expression(return_expression), + .return_nothing => try self.chunk.append_opcode(.push_nil), + + .set_local => |local| { + try self.compile_expression(local.expression); + + if (self.resolve_local(local.identifier)) |index| { + try self.chunk.append_opcode(.{.set_local = index}); + } else { + try self.declare_local(local.identifier); + } + }, + + .call_global => |call| { + if (call.argument_expressions.values.len > coral.math.max_int(@typeInfo(u8).Int)) { + return self.chunk.env.raise(error.OutOfMemory, "functions may receive a maximum of 255 locals"); + } + + for (call.argument_expressions.values) |argument_expression| { + try self.compile_expression(argument_expression); + } + + try self.chunk.append_opcode(.{.push_const = try self.chunk.declare_constant_string(call.identifier)}); + + try self.chunk.append_opcode(.{ + .global_call = .{ + .arg_count = @intCast(call.argument_expressions.values.len), + }, + }); + } + } + } + + fn declare_local(self: *AstCompiler, identifier: []const u8) kym.RuntimeError!void { + if (self.local_identifiers_count == self.local_identifiers_buffer.len) { + return self.chunk.env.raise(error.OutOfMemory, "functions may contain a maximum of 255 locals"); + } + + self.local_identifiers_buffer[self.local_identifiers_count] = identifier; + self.local_identifiers_count += 1; + } + + fn resolve_local(self: *AstCompiler, local_identifier: []const coral.io.Byte) ?u8 { + if (self.local_identifiers_count == 0) { + return null; + } + + var index = @as(u8, self.local_identifiers_count - 1); + + while (true) : (index -= 1) { + if (coral.io.equals(local_identifier, self.local_identifiers_buffer[index])) { + return index; + } + + if (index == 0) { + return null; + } + } + } +}; + +pub const Constant = u16; + +const RefList = coral.list.Stack(?*kym.RuntimeRef); + +const LocalsList = coral.list.Stack([]const u8); + +pub const Opcode = union (enum) { + push_nil, + push_true, + push_false, + push_const: Constant, + local_push_ref: u8, + set_local: u8, + get_global, + + push_table: struct { + field_count: u32, + }, + + global_call: struct { + arg_count: u8, + }, + + not, + neg, + + add, + sub, + mul, + div, + + eql, + cgt, + clt, + cge, + cle, +}; + +const OpcodeList = coral.list.Stack(Opcode); + +const Self = @This(); + +pub fn append_opcode(self: *Self, opcode: Opcode) kym.RuntimeError!void { + return self.opcodes.push_one(opcode); +} + +pub fn compile_ast(self: *Self, ast: Ast) kym.RuntimeError!void { + self.free(); + + try self.constant_refs.grow(coral.math.max_int(@typeInfo(u16).Int)); + + var compiler = AstCompiler{.chunk = self}; + + for (ast.list_statements()) |statement| { + try compiler.compile_statement(statement); + } + + try self.constant_refs.pack(); + try self.opcodes.pack(); +} + +pub fn declare_constant_number(self: *Self, constant: kym.Float) kym.RuntimeError!Constant { + const tail = self.constant_refs.values.len; + + if (tail == coral.math.max_int(@typeInfo(u16).Int)) { + return self.env.raise(error.BadSyntax, "functions may contain a maximum of 65,535 constants"); + } + + var constant_ref = try self.env.new_number(constant); + + errdefer self.env.discard(&constant_ref); + + try self.constant_refs.push_one(constant_ref); + + return @intCast(tail); +} + +pub fn declare_constant_string(self: *Self, constant: []const coral.io.Byte) kym.RuntimeError!Constant { + const tail = self.constant_refs.values.len; + + if (tail == coral.math.max_int(@typeInfo(u16).Int)) { + return self.env.raise(error.BadSyntax, "functions may contain a maximum of 65,535 constants"); + } + + var constant_ref = try self.env.new_string(constant); + + errdefer self.env.discard(&constant_ref); + + try self.constant_refs.push_one(constant_ref); + + return @intCast(tail); +} + +pub fn execute(self: *Self, global_ref: ?*const kym.RuntimeRef, name: []const coral.io.Byte) kym.RuntimeError!?*kym.RuntimeRef { + _ = name; + + try self.env.frame_push(); + + for (self.opcodes.values) |opcode| { + switch (opcode) { + .push_nil => try self.env.local_push_ref(null), + .push_true => try self.env.local_push_boolean(true), + .push_false => try self.env.local_push_boolean(false), + .push_const => |constant| try self.env.local_push_ref(self.constant_refs.values[constant]), + + .push_table => |push_table| { + var table_ref = try kym.new_table(self.env); + + defer self.env.discard(&table_ref); + + { + var popped = @as(usize, 0); + + while (popped < push_table.field_count) : (popped += 1) { + try self.env.set_dynamic(table_ref, try self.env.local_pop(), try self.env.local_pop()); + } + } + + try self.env.local_push_ref(table_ref); + }, + + .local_push_ref => |local| { + var ref = try self.env.local_get(local); + + defer self.env.discard(&ref); + + try self.env.local_push_ref(ref); + }, + + .get_global => { + var identifier_ref = try self.env.local_pop(); + + defer self.env.discard(&identifier_ref); + + var ref = try self.env.get_dynamic(global_ref, identifier_ref); + + defer self.env.discard(&ref); + + try self.env.local_push_ref(ref); + }, + + .set_local => |local| { + var ref = try self.env.local_pop(); + + defer self.env.discard(&ref); + + try self.env.local_set(local, ref); + }, + + .global_call => |_| { + + }, + + .neg => try self.env.local_push_number(switch (self.env.unbox(try self.env.local_pop())) { + .number => |number| -number, + else => return self.env.raise(error.TypeMismatch, "object is not scalar negatable"), + }), + + .not => try self.env.local_push_boolean(switch (self.env.unbox(try self.env.local_pop())) { + .boolean => |boolean| !boolean, + else => return self.env.raise(error.TypeMismatch, "object is not boolean negatable"), + }), + + .add => { + var rhs_ref = try self.env.local_pop(); + + defer self.env.discard(&rhs_ref); + + var lhs_ref = try self.env.local_pop(); + + defer self.env.discard(&lhs_ref); + + try self.env.local_push_ref(switch (self.env.unbox(lhs_ref)) { + .number => |lhs_number| switch (self.env.unbox(rhs_ref)) { + .number => |rhs_number| try self.env.new_number(lhs_number + rhs_number), + else => return self.env.raise(error.TypeMismatch, "right-hand object is not addable"), + }, + + else => return self.env.raise(error.TypeMismatch, "left-hand object is not addable"), + }); + }, + + .sub => { + var rhs_ref = try self.env.local_pop(); + + defer self.env.discard(&rhs_ref); + + var lhs_ref = try self.env.local_pop(); + + defer self.env.discard(&lhs_ref); + + try self.env.local_push_ref(switch (self.env.unbox(lhs_ref)) { + .number => |lhs_number| switch (self.env.unbox(rhs_ref)) { + .number => |rhs_number| try self.env.new_number(lhs_number - rhs_number), + else => return self.env.raise(error.TypeMismatch, "right-hand object is not subtractable"), + }, + + else => return self.env.raise(error.TypeMismatch, "left-hand object is not subtractable"), + }); + }, + + .mul => { + var rhs_ref = try self.env.local_pop(); + + defer self.env.discard(&rhs_ref); + + var lhs_ref = try self.env.local_pop(); + + defer self.env.discard(&lhs_ref); + + try self.env.local_push_ref(switch (self.env.unbox(lhs_ref)) { + .number => |lhs_number| switch (self.env.unbox(rhs_ref)) { + .number => |rhs_number| try self.env.new_number(lhs_number * rhs_number), + else => return self.env.raise(error.TypeMismatch, "right-hand object is not multiplyable"), + }, + + else => return self.env.raise(error.TypeMismatch, "left-hand object is not multiplyable"), + }); + }, + + .div => { + var rhs_ref = try self.env.local_pop(); + + defer self.env.discard(&rhs_ref); + + var lhs_ref = try self.env.local_pop(); + + defer self.env.discard(&lhs_ref); + + try self.env.local_push_ref(switch (self.env.unbox(lhs_ref)) { + .number => |lhs_number| switch (self.env.unbox(rhs_ref)) { + .number => |rhs_number| try self.env.new_number(lhs_number / rhs_number), + else => return self.env.raise(error.TypeMismatch, "right-hand object is not divisable"), + }, + + else => return self.env.raise(error.TypeMismatch, "left-hand object is not divisable"), + }); + }, + + .eql => { + var rhs_ref = try self.env.local_pop(); + + defer self.env.discard(&rhs_ref); + + var lhs_ref = try self.env.local_pop(); + + defer self.env.discard(&lhs_ref); + + try self.env.local_push_boolean(try kym.test_equality(self.env, lhs_ref, rhs_ref)); + }, + + .cgt => { + var rhs_ref = try self.env.local_pop(); + + defer self.env.discard(&rhs_ref); + + var lhs_ref = try self.env.local_pop(); + + defer self.env.discard(&lhs_ref); + + try self.env.local_push_boolean(try kym.test_difference(self.env, lhs_ref, rhs_ref) > 0); + }, + + .clt => { + var rhs_ref = try self.env.local_pop(); + + defer self.env.discard(&rhs_ref); + + var lhs_ref = try self.env.local_pop(); + + defer self.env.discard(&lhs_ref); + + try self.env.local_push_boolean(try kym.test_difference(self.env, lhs_ref, rhs_ref) < 0); + }, + + .cge => { + var rhs_ref = try self.env.local_pop(); + + defer self.env.discard(&rhs_ref); + + var lhs_ref = try self.env.local_pop(); + + defer self.env.discard(&lhs_ref); + + try self.env.local_push_boolean(try kym.test_difference(self.env, lhs_ref, rhs_ref) >= 0); + }, + + .cle => { + var rhs_ref = try self.env.local_pop(); + + defer self.env.discard(&rhs_ref); + + var lhs_ref = try self.env.local_pop(); + + defer self.env.discard(&lhs_ref); + + try self.env.local_push_boolean(try kym.test_difference(self.env, lhs_ref, rhs_ref) <= 0); + }, + } + } + + const result = try self.env.local_pop(); + + try self.env.frame_pop(); + + return result; +} + +pub fn free(self: *Self) void { + for (self.constant_refs.values) |*constant| { + self.env.discard(constant); + } + + self.opcodes.free(); + self.constant_refs.free(); +} + +fn is_zero(utf8: []const u8) bool { + return coral.io.equals(utf8, "0") or coral.io.equals(utf8, "0.0"); +} + +pub fn make(env: *kym.RuntimeEnv) Self { + return Self{ + .opcodes = OpcodeList.make(env.allocator), + .constant_refs = RefList.make(env.allocator), + .env = env, + }; +} diff --git a/source/ona/kym/State.zig b/source/ona/kym/State.zig deleted file mode 100644 index 7f29d16..0000000 --- a/source/ona/kym/State.zig +++ /dev/null @@ -1,148 +0,0 @@ -const coral = @import("coral"); - -allocator: coral.io.Allocator, -interned: SymbolTable, -globals: Object, -values: DataStack, -frames: CallStack, - -pub const Float = f64; - -const CallStack = coral.list.Stack(struct { - callable: *Object, - opcode_index: usize, - stack_index: usize, -}); - -const DataStack = coral.list.Stack(Variant); - -pub const Object = struct { - ref_count: usize, - userdata: []coral.io.Byte, - userinfo: *const anyopaque, -}; - -pub const PopError = error { - StackOverflow, -}; - -const Self = @This(); - -const SymbolTable = coral.map.Table([]const coral.io.Byte, *Object, coral.map.string_table_traits); - -pub const Variant = union (enum) { - nil, - true, - false, - number: Float, - object: *Object, - - pub fn equals(self: Variant, other: Variant) bool { - return switch (self) { - .nil => other == .nil, - .true => other == .true, - .false => other == .false, - - .number => |number| switch (other) { - .number => |other_number| number == other_number, - else => false, - }, - - .object => |object| switch (other) { - .object => |other_object| object == other_object, - else => false, - }, - }; - } -}; - -pub fn acquire_instance(_: *Self, object: *Object) *Object { - // TODO: safety-check object belongs to state. - object.ref_count += 1; - - return object; -} - -pub fn acquire_interned(self: *Self, userdata: []const u8, userinfo: *const anyopaque) coral.io.AllocationError!*Object { - // TODO: Include userinfo in matching lookup. - if (self.interned.lookup(userdata)) |object| { - return self.acquire_instance(object); - } else { - const data_object = try self.acquire_new(userdata, userinfo); - - errdefer self.release(data_object); - - coral.debug.assert(try self.interned.insert(data_object.userdata, data_object)); - - return data_object; - } -} - -pub fn acquire_new(self: *Self, userdata: []const u8, userinfo: *const anyopaque) coral.io.AllocationError!*Object { - const allocated_userdata = try coral.io.allocate_copy(self.allocator, userdata); - - errdefer self.allocator.deallocate(allocated_userdata); - - const allocated_object = try coral.io.allocate_one(self.allocator, Object{ - .ref_count = 1, - .userdata = allocated_userdata, - .userinfo = userinfo, - }); - - errdefer self.allocator.deallocate(allocated_object); - - return allocated_object; -} - -pub fn free(self: *Self) void { - self.values.free(); - self.frames.free(); - self.interned.free(); -} - -pub fn get_value(self: *Self, index: u8) Variant { - return if (index < self.values.values.len) self.values.values[index] else .nil; -} - -pub fn make(allocator: coral.io.Allocator) Self { - return .{ - .values = DataStack.make(allocator), - .frames = CallStack.make(allocator), - .interned = SymbolTable.make(allocator), - .allocator = allocator, - - .globals = .{ - .ref_count = 0, - .userdata = &.{}, - .userinfo = &.{}, - }, - }; -} - -pub fn pop_value(self: *Self) PopError!Variant { - return self.values.pop() orelse error.StackOverflow; -} - -pub fn push_value(self: *Self, value: Variant) coral.io.AllocationError!void { - return self.values.push_one(value); -} - -pub fn release(self: *Self, object: *Object) void { - coral.debug.assert(object.ref_count != 0); - - object.ref_count -= 1; - - if (object.ref_count == 0) { - self.allocator.deallocate(object); - } -} - -pub fn set_value(self: *Self, index: u8, value: Variant) bool { - if (index >= self.values.values.len) { - return false; - } - - self.values.values[index] = value; - - return true; -} diff --git a/source/ona/kym/Table.zig b/source/ona/kym/Table.zig index 57f1d0d..8867bcc 100644 --- a/source/ona/kym/Table.zig +++ b/source/ona/kym/Table.zig @@ -1,76 +1,82 @@ -const State = @import("./State.zig"); - const coral = @import("coral"); -state: *State, +const kym = @import("../kym.zig"); + fields: FieldTable, -array: ArrayList, -const ArrayList = coral.list.Stack(State.Variant); - -const Field = struct { - field: *State.Object, - value: State.Variant, - - fn release_objects(self: Field, state: *State) void { - state.release(self.field); - - if (self.value == .object) { - state.release(self.value.object); - } - } -}; - -const FieldTable = coral.map.Table([]const coral.io.Byte, Field, coral.map.string_table_traits); +const FieldTable = coral.map.Table([]const coral.io.Byte, struct { + key_ref: ?*kym.RuntimeRef, + value_ref: ?*kym.RuntimeRef, +}, coral.map.string_table_traits); const Self = @This(); pub fn free(self: *Self) void { - { - var field_iterator = FieldTable.Iterable{.table = &self.fields}; - - while (field_iterator.next()) |entry| { - entry.value.release_objects(self.state); - } - } - self.fields.free(); - self.array.free(); } -pub fn get_field(self: *Self, field_name: *State.Object) State.Variant { - const field = self.fields.lookup(field_name.userdata) orelse return .nil; - - if (field.value == .object) { - return .{.object = self.state.acquire_instance(field.value.object)}; - } - - return field.value; -} - -pub fn get_index(self: *Self, index: usize) State.Variant { - return self.array.values[index]; -} - -pub fn make(allocator: coral.io.Allocator, state: *State) Self { +pub fn make(env: *kym.RuntimeEnv) Self { return .{ - .state = state, - .fields = FieldTable.make(allocator), - .array = ArrayList.make(allocator), + .fields = FieldTable.make(env.allocator), }; } -pub fn set_field(self: *Self, field_name: *State.Object, value: State.Variant) coral.io.AllocationError!void { - const previous_entry = try self.fields.replace(field_name.userdata, .{ - .field = field_name, - .value = value, - }); +pub const typeinfo = kym.Typeinfo{ + .call = typeinfo_call, + .clean = typeinfo_clean, + .get = typeinfo_get, + .set = typeinfo_set, +}; - if (previous_entry) |entry| { - entry.value.release_objects(self.state); +fn typeinfo_call(context: kym.CallContext) kym.RuntimeError!?*kym.RuntimeRef { + return context.env.raise(error.TypeMismatch, "cannot call a table"); +} + +fn typeinfo_clean(userdata: []u8) void { + @as(*Self, @ptrCast(@alignCast(userdata.ptr))).free(); +} + +fn typeinfo_get(context: kym.IndexContext) kym.RuntimeError!?*kym.RuntimeRef { + const table = @as(*Self, @ptrCast(@alignCast(context.userdata.ptr))); + + return switch (context.env.unbox(context.index_ref)) { + .string => |string| context.env.acquire((table.fields.lookup(string) orelse return null).value_ref), + // TODO: Implement number indices in tables. + .number => |_| unreachable, + else => context.env.raise(error.TypeMismatch, "table objects may only be indexed with strings or numbers"), + }; +} + +fn typeinfo_set(context: kym.IndexContext, value_ref: ?*const kym.RuntimeRef) kym.RuntimeError!void { + const table = @as(*Self, @ptrCast(@alignCast(context.userdata.ptr))); + var acquired_value_ref = context.env.acquire(value_ref); + + errdefer context.env.discard(&acquired_value_ref); + + switch (context.env.unbox(context.index_ref)) { + .string => |string| { + var acquired_index_ref = context.env.acquire(context.index_ref); + + errdefer context.env.discard(&acquired_index_ref); + + var displaced_table_entry = if (acquired_value_ref) |ref| try table.fields.replace(string, .{ + .key_ref = acquired_index_ref, + .value_ref = ref, + }) else table.fields.remove(string); + + if (displaced_table_entry) |*entry| { + context.env.discard(&entry.value.key_ref); + context.env.discard(&entry.value.value_ref); + } + }, + + .number => |_| { + // TODO: Implement number indices in tables. + unreachable; + }, + + else => { + return context.env.raise(error.TypeMismatch, "table objects may only be indexed with strings or numbers"); + }, } } - -pub fn set_index(self: *Self, index: usize, value: State.Variant) coral.io.AllocationError!void { - self.array.values[index] = value; -} diff --git a/source/ona/ona.zig b/source/ona/ona.zig index 1fcbace..82ee995 100644 --- a/source/ona/ona.zig +++ b/source/ona/ona.zig @@ -48,9 +48,17 @@ pub fn run_app(file_access: file.Access) RuntimeError!void { defer script_env.free(); + var globals_ref = kym.new_table(&script_env) catch { + try fail_log.write("failed to initialize script runtime"); + + return error.InitFailure; + }; + + defer script_env.discard(&globals_ref); + var manifest = app.Manifest{}; - manifest.load(&script_env, file_access) catch { + manifest.load(&script_env, globals_ref, file_access) catch { fail_log.write("failed to load / execute app.ona manifest") catch {}; return error.BadManifest; From 84e5236ba18f034d9bc40b533a0ab22ad6e3c26d Mon Sep 17 00:00:00 2001 From: kayomn Date: Wed, 19 Jul 2023 12:19:56 +0100 Subject: [PATCH 2/6] Add global function calling syntax support --- source/coral/list.zig | 2 +- source/ona/kym.zig | 176 ++++++++++++++++++++++++--------------- source/ona/kym/Chunk.zig | 171 ++++++++++++++++++------------------- source/ona/kym/Table.zig | 17 ++-- 4 files changed, 200 insertions(+), 166 deletions(-) diff --git a/source/coral/list.zig b/source/coral/list.zig index 4e040f6..d9b794d 100644 --- a/source/coral/list.zig +++ b/source/coral/list.zig @@ -73,7 +73,7 @@ pub fn Stack(comptime Value: type) type { self.capacity = packed_size; } - pub fn peek(self: *Self) ?Value { + pub fn peek(self: Self) ?Value { if (self.values.len == 0) { return null; } diff --git a/source/ona/kym.zig b/source/ona/kym.zig index 6c1d38c..091e86b 100644 --- a/source/ona/kym.zig +++ b/source/ona/kym.zig @@ -13,7 +13,7 @@ const tokens = @import("./kym/tokens.zig"); pub const CallContext = struct { env: *RuntimeEnv, caller: ?*const RuntimeRef, - userdata: []u8, + userdata: []coral.io.Byte, args: []const ?*const RuntimeRef = &.{}, pub fn arg_at(self: CallContext, index: u8) RuntimeError!*const RuntimeRef { @@ -25,6 +25,8 @@ pub const CallContext = struct { } }; +pub const Callable = coral.io.Generator(RuntimeError!?*RuntimeRef, CallContext); + pub const DynamicObject = struct { userdata: []coral.io.Byte, typeinfo: *const Typeinfo, @@ -46,6 +48,7 @@ pub const RuntimeEnv = struct { ref_values: RefSlab, const FrameStack = coral.list.Stack(struct { + arg_count: u8, locals_top: usize, }); @@ -74,7 +77,7 @@ pub const RuntimeEnv = struct { }; pub fn acquire(self: *RuntimeEnv, ref: ?*const RuntimeRef) ?*RuntimeRef { - const key = @intFromPtr(ref); + const key = @intFromPtr(ref orelse return null); var ref_data = self.ref_values.remove(key); coral.debug.assert(ref_data != null); @@ -88,23 +91,24 @@ pub const RuntimeEnv = struct { pub fn call( self: *RuntimeEnv, - caller_ref: *const RuntimeRef, - callable_ref: *const RuntimeRef, + caller_ref: ?*const RuntimeRef, + callable_ref: ?*const RuntimeRef, arg_refs: []const *const RuntimeRef, - ) RuntimeError!*RuntimeRef { - const callable = (try callable_ref.fetch(self)).to_object(); + ) RuntimeError!?*RuntimeRef { + return switch (self.unbox(callable_ref)) { + .dynamic => |dynamic| dynamic.typeinfo.call(.{ + .env = self, + .caller = caller_ref, + .userdata = dynamic.userdata, + .args = arg_refs, + }), - return callable.userinfo.call(.{ - .env = self, - .caller = caller_ref, - .callable = callable_ref, - .userdata = callable.userdata, - .args = arg_refs, - }); + else => self.raise(error.TypeMismatch, "type is not callable"), + }; } - pub fn discard(self: *RuntimeEnv, ref: *?*RuntimeRef) void { - const key = @intFromPtr(ref.* orelse return); + pub fn discard(self: *RuntimeEnv, ref: ?*RuntimeRef) void { + const key = @intFromPtr(ref orelse return); var ref_data = self.ref_values.remove(key) orelse unreachable; coral.debug.assert(ref_data.ref_count != 0); @@ -120,8 +124,36 @@ pub const RuntimeEnv = struct { } else { coral.debug.assert(self.ref_values.insert_at(key, ref_data)); } + } - ref.* = null; + pub fn dynamic_get(self: *RuntimeEnv, indexable_ref: ?*const RuntimeRef, index_ref: ?*const RuntimeRef) RuntimeError!?*RuntimeRef { + return switch (self.unbox(indexable_ref)) { + .nil => self.raise(error.BadOperation, "nil has no such index"), + .boolean => self.raise(error.BadOperation, "boolean has no such index"), + .number => self.raise(error.BadOperation, "number has no such index"), + .string => self.raise(error.BadOperation, "string has no such index"), + + .dynamic => |dynamic| dynamic.typeinfo.get(.{ + .userdata = dynamic.userdata, + .env = self, + .index_ref = index_ref, + }), + }; + } + + pub fn dynamic_set(self: *RuntimeEnv, indexable_ref: ?*const RuntimeRef, index_ref: ?*const RuntimeRef, value_ref: ?*const RuntimeRef) RuntimeError!void { + return switch (self.unbox(indexable_ref)) { + .nil => self.raise(error.BadOperation, "nil is immutable"), + .boolean => self.raise(error.BadOperation, "boolean is immutable"), + .number => self.raise(error.BadOperation, "number is immutable"), + .string => self.raise(error.BadOperation, "string is immutable"), + + .dynamic => |dynamic| dynamic.typeinfo.set(.{ + .userdata = dynamic.userdata, + .env = self, + .index_ref = index_ref, + }, value_ref), + }; } pub fn execute_file(self: *RuntimeEnv, global_ref: ?*const RuntimeRef, file_access: file.Access, file_path: file.Path) RuntimeError!?*RuntimeRef { @@ -162,16 +194,20 @@ pub const RuntimeEnv = struct { return exe.execute(global_ref, source.name); } - pub fn frame_pop(self: *RuntimeEnv) RuntimeError!void { - const frame = self.frames.pop() orelse return self.raise(error.IllegalState, "stack underflow"); + pub fn frame_pop(self: *RuntimeEnv) void { + const frame = self.frames.pop(); - coral.debug.assert(self.local_refs.drop(self.local_refs.values.len - frame.locals_top)); + coral.debug.assert(frame != null); + coral.debug.assert(self.local_refs.drop((self.local_refs.values.len - frame.?.locals_top) + frame.?.arg_count)); } - pub fn frame_push(self: *RuntimeEnv) RuntimeError!void { - return self.frames.push_one(.{ + pub fn frame_push(self: *RuntimeEnv, arg_count: u8) RuntimeError![]const *const RuntimeRef { + try self.frames.push_one(.{ + .arg_count = arg_count, .locals_top = self.local_refs.values.len, }); + + return @as([]const *const RuntimeRef, @ptrCast(self.local_refs.values[(self.local_refs.values.len - arg_count) .. self.local_refs.values.len])); } pub fn free(self: *RuntimeEnv) void { @@ -179,27 +215,16 @@ pub const RuntimeEnv = struct { self.ref_values.free(); } - pub fn get_dynamic(self: *RuntimeEnv, indexable_ref: ?*const RuntimeRef, index_ref: ?*const RuntimeRef) RuntimeError!?*RuntimeRef { - return switch (self.unbox(indexable_ref)) { - .nil => self.raise(error.BadOperation, "nil is immutable"), - .boolean => self.raise(error.BadOperation, "boolean has no such index"), - .number => self.raise(error.BadOperation, "number has no such index"), - .string => self.raise(error.BadOperation, "string has no such index"), - - .dynamic => |dynamic| dynamic.typeinfo.get(.{ - .userdata = dynamic.userdata, - .env = self, - .index_ref = index_ref, - }), - }; - } - - pub fn local_get(self: *RuntimeEnv, local: u8) RuntimeError!?*RuntimeRef { + pub fn local_get(self: *RuntimeEnv, local: u8) ?*RuntimeRef { return self.local_refs.values[local]; } - pub fn local_pop(self: *RuntimeEnv) RuntimeError!?*RuntimeRef { - return self.local_refs.pop() orelse self.raise(error.IllegalState, "stack underflow"); + pub fn local_pop(self: *RuntimeEnv) ?*RuntimeRef { + const ref = self.local_refs.pop(); + + coral.debug.assert(ref != null); + + return ref.?; } pub fn local_push_ref(self: *RuntimeEnv, ref: ?*RuntimeRef) RuntimeError!void { @@ -214,7 +239,7 @@ pub const RuntimeEnv = struct { return self.local_refs.push_one(try self.new_number(number)); } - pub fn local_set(self: *RuntimeEnv, local: u8, value: ?*RuntimeRef) RuntimeError!void { + pub fn local_set(self: *RuntimeEnv, local: u8, value: ?*RuntimeRef) void { self.local_refs.values[local] = self.acquire(value); } @@ -271,7 +296,7 @@ pub const RuntimeEnv = struct { })); } - pub fn raise(self: *RuntimeEnv, error_value: RuntimeError, error_message: []const u8) RuntimeError { + pub fn raise(self: *RuntimeEnv, error_value: RuntimeError, error_message: []const coral.io.Byte) RuntimeError { // TODO: Print stack trace from state. coral.utf8.print_formatted(self.err_writer, "{name}@{line}: {message}", .{ .name = "???", @@ -282,21 +307,6 @@ pub const RuntimeEnv = struct { return error_value; } - pub fn set_dynamic(self: *RuntimeEnv, indexable_ref: ?*const RuntimeRef, index_ref: ?*const RuntimeRef, value_ref: ?*const RuntimeRef) RuntimeError!void { - return switch (self.unbox(indexable_ref)) { - .nil => self.raise(error.BadOperation, "nil is immutable"), - .boolean => self.raise(error.BadOperation, "boolean is immutable"), - .number => self.raise(error.BadOperation, "number is immutable"), - .string => self.raise(error.BadOperation, "string is immutable"), - - .dynamic => |dynamic| dynamic.typeinfo.set(.{ - .userdata = dynamic.userdata, - .env = self, - .index_ref = index_ref, - }, value_ref), - }; - } - pub fn unbox(self: *RuntimeEnv, ref: ?*const RuntimeRef) Unboxed { const ref_data = self.ref_values.lookup(@intFromPtr(ref orelse return .nil)); @@ -329,13 +339,29 @@ pub const TestContext = struct { }; pub const Typeinfo = struct { - call: *const fn (context: CallContext) RuntimeError!?*RuntimeRef, - clean: *const fn (userdata: []u8) void, - get: *const fn (context: IndexContext) RuntimeError!?*RuntimeRef, - set: *const fn (context: IndexContext, value: ?*const RuntimeRef) RuntimeError!void, + call: *const fn (context: CallContext) RuntimeError!?*RuntimeRef = default_call, + clean: *const fn (userdata: []coral.io.Byte) void = default_clean, + get: *const fn (context: IndexContext) RuntimeError!?*RuntimeRef = default_get, + set: *const fn (context: IndexContext, value: ?*const RuntimeRef) RuntimeError!void = default_set, test_difference: *const fn (context: TestContext) RuntimeError!Float = default_test_difference, test_equality: *const fn (context: TestContext) RuntimeError!bool = default_test_equality, + fn default_call(context: CallContext) RuntimeError!?*RuntimeRef { + return context.env.raise(error.TypeMismatch, "object is not callable"); + } + + fn default_clean(_: []coral.io.Byte) void { + // Nothing to clean by default. + } + + fn default_get(context: IndexContext) RuntimeError!?*RuntimeRef { + return context.env.raise(error.TypeMismatch, "object is not indexable"); + } + + fn default_set(context: IndexContext, _: ?*const RuntimeRef) RuntimeError!void { + return context.env.raise(error.TypeMismatch, "object is not indexable"); + } + fn default_test_difference(context: TestContext) RuntimeError!Float { return context.env.raise(error.TypeMismatch, "object is not comparable"); } @@ -352,16 +378,28 @@ pub const Unboxed = union (enum) { nil, boolean: bool, number: Float, - string: []const u8, + string: []const coral.io.Byte, dynamic: *const DynamicObject, }; -pub fn get_dynamic_field(env: *RuntimeEnv, indexable: ?*const RuntimeRef, field: []const u8) RuntimeError!?*RuntimeRef { - var interned_field = try env.new_string(field); +pub fn get_dynamic_field(env: *RuntimeEnv, indexable_ref: ?*const RuntimeRef, field_name: []const coral.io.Byte) RuntimeError!?*RuntimeRef { + const field_name_ref = try env.new_string(field_name); - defer env.discard(&interned_field); + defer env.discard(field_name_ref); - return env.get_dynamic(indexable, interned_field); + return env.dynamic_get(indexable_ref, field_name_ref); +} + +pub fn new_callable(env: *RuntimeEnv, generator: Callable) RuntimeError!?*RuntimeRef { + const Concrete = struct { + fn call(context: CallContext) RuntimeError!?*RuntimeRef { + return @as(*Callable, @ptrCast(@alignCast(context.userdata.ptr))).invoke(context); + } + }; + + return env.new_dynamic(coral.io.bytes_of(&generator), &.{ + .call = Concrete.call, + }); } pub fn new_table(env: *RuntimeEnv) RuntimeError!?*RuntimeRef { @@ -372,6 +410,14 @@ pub fn new_table(env: *RuntimeEnv) RuntimeError!?*RuntimeRef { return try env.new_dynamic(coral.io.bytes_of(&table), &Table.typeinfo); } +pub fn set_dynamic_field(env: *RuntimeEnv, indexable_ref: ?*const RuntimeRef, field_name: []const coral.io.Byte, value_ref: ?*const RuntimeRef) RuntimeError!void { + const field_name_ref = try env.new_string(field_name); + + defer env.discard(field_name_ref); + + return env.dynamic_set(indexable_ref, field_name_ref, value_ref); +} + pub fn test_difference(env: *RuntimeEnv, lhs_ref: ?*const RuntimeRef, rhs_ref: ?*const RuntimeRef) RuntimeError!Float { return switch (env.unbox(lhs_ref)) { .nil => env.raise(error.TypeMismatch, "cannot compare nil objects"), diff --git a/source/ona/kym/Chunk.zig b/source/ona/kym/Chunk.zig index 7ede24b..5013c3c 100644 --- a/source/ona/kym/Chunk.zig +++ b/source/ona/kym/Chunk.zig @@ -51,8 +51,7 @@ const AstCompiler = struct { }); } - try self.chunk.append_opcode(.{ - .push_table = .{.field_count = @intCast(fields.values.len)}}); + try self.chunk.append_opcode(.{.push_table = @intCast(fields.values.len)}); }, .binary_operation => |operation| { @@ -87,7 +86,7 @@ const AstCompiler = struct { .get_local => |local| { try self.chunk.append_opcode(.{ - .local_push_ref = self.resolve_local(local) orelse return self.chunk.env.raise(error.OutOfMemory, "undefined local"), + .push_local = self.resolve_local(local) orelse return self.chunk.env.raise(error.OutOfMemory, "undefined local"), }); }, @@ -101,12 +100,7 @@ const AstCompiler = struct { } try self.chunk.append_opcode(.{.push_const = try self.chunk.declare_constant_string(call.identifier)}); - - try self.chunk.append_opcode(.{ - .global_call = .{ - .arg_count = @intCast(call.argument_expressions.values.len), - }, - }); + try self.chunk.append_opcode(.{.call = @intCast(call.argument_expressions.values.len)}); }, } } @@ -136,12 +130,8 @@ const AstCompiler = struct { } try self.chunk.append_opcode(.{.push_const = try self.chunk.declare_constant_string(call.identifier)}); - - try self.chunk.append_opcode(.{ - .global_call = .{ - .arg_count = @intCast(call.argument_expressions.values.len), - }, - }); + try self.chunk.append_opcode(.get_global); + try self.chunk.append_opcode(.{.call = @intCast(call.argument_expressions.values.len)}); } } } @@ -185,17 +175,11 @@ pub const Opcode = union (enum) { push_true, push_false, push_const: Constant, - local_push_ref: u8, + push_local: u8, + push_table: u32, set_local: u8, get_global, - - push_table: struct { - field_count: u32, - }, - - global_call: struct { - arg_count: u8, - }, + call: u8, not, neg, @@ -242,9 +226,9 @@ pub fn declare_constant_number(self: *Self, constant: kym.Float) kym.RuntimeErro return self.env.raise(error.BadSyntax, "functions may contain a maximum of 65,535 constants"); } - var constant_ref = try self.env.new_number(constant); + const constant_ref = try self.env.new_number(constant); - errdefer self.env.discard(&constant_ref); + errdefer self.env.discard(constant_ref); try self.constant_refs.push_one(constant_ref); @@ -258,9 +242,9 @@ pub fn declare_constant_string(self: *Self, constant: []const coral.io.Byte) kym return self.env.raise(error.BadSyntax, "functions may contain a maximum of 65,535 constants"); } - var constant_ref = try self.env.new_string(constant); + const constant_ref = try self.env.new_string(constant); - errdefer self.env.discard(&constant_ref); + errdefer self.env.discard(constant_ref); try self.constant_refs.push_one(constant_ref); @@ -269,8 +253,9 @@ pub fn declare_constant_string(self: *Self, constant: []const coral.io.Byte) kym pub fn execute(self: *Self, global_ref: ?*const kym.RuntimeRef, name: []const coral.io.Byte) kym.RuntimeError!?*kym.RuntimeRef { _ = name; + _ = try self.env.frame_push(0); - try self.env.frame_push(); + defer self.env.frame_pop(); for (self.opcodes.values) |opcode| { switch (opcode) { @@ -279,72 +264,84 @@ pub fn execute(self: *Self, global_ref: ?*const kym.RuntimeRef, name: []const co .push_false => try self.env.local_push_boolean(false), .push_const => |constant| try self.env.local_push_ref(self.constant_refs.values[constant]), - .push_table => |push_table| { - var table_ref = try kym.new_table(self.env); + .push_table => |field_count| { + const table_ref = try kym.new_table(self.env); - defer self.env.discard(&table_ref); + defer self.env.discard(table_ref); { var popped = @as(usize, 0); - while (popped < push_table.field_count) : (popped += 1) { - try self.env.set_dynamic(table_ref, try self.env.local_pop(), try self.env.local_pop()); + while (popped < field_count) : (popped += 1) { + try self.env.dynamic_set(table_ref, self.env.local_pop(), self.env.local_pop()); } } try self.env.local_push_ref(table_ref); }, - .local_push_ref => |local| { - var ref = try self.env.local_get(local); + .push_local => |local| { + const ref = self.env.local_get(local); - defer self.env.discard(&ref); + defer self.env.discard(ref); try self.env.local_push_ref(ref); }, .get_global => { - var identifier_ref = try self.env.local_pop(); + const identifier_ref = self.env.local_pop(); - defer self.env.discard(&identifier_ref); + defer self.env.discard(identifier_ref); - var ref = try self.env.get_dynamic(global_ref, identifier_ref); + const ref = try self.env.dynamic_get(global_ref, identifier_ref); - defer self.env.discard(&ref); + defer self.env.discard(ref); try self.env.local_push_ref(ref); }, .set_local => |local| { - var ref = try self.env.local_pop(); + const ref = self.env.local_pop(); - defer self.env.discard(&ref); + defer self.env.discard(ref); - try self.env.local_set(local, ref); + self.env.local_set(local, ref); }, - .global_call => |_| { + .call => |arg_count| { + const callable_ref = self.env.local_pop(); + defer self.env.discard(callable_ref); + + const args = try self.env.frame_push(arg_count); + + defer self.env.frame_pop(); + + const result_ref = try self.env.call(global_ref, callable_ref, args); + + defer self.env.discard(result_ref); + + try self.env.local_push_ref(result_ref); }, - .neg => try self.env.local_push_number(switch (self.env.unbox(try self.env.local_pop())) { + .neg => try self.env.local_push_number(switch (self.env.unbox(self.env.local_pop())) { .number => |number| -number, else => return self.env.raise(error.TypeMismatch, "object is not scalar negatable"), }), - .not => try self.env.local_push_boolean(switch (self.env.unbox(try self.env.local_pop())) { + .not => try self.env.local_push_boolean(switch (self.env.unbox(self.env.local_pop())) { .boolean => |boolean| !boolean, else => return self.env.raise(error.TypeMismatch, "object is not boolean negatable"), }), .add => { - var rhs_ref = try self.env.local_pop(); + const rhs_ref = self.env.local_pop(); - defer self.env.discard(&rhs_ref); + defer self.env.discard(rhs_ref); - var lhs_ref = try self.env.local_pop(); + const lhs_ref = self.env.local_pop(); - defer self.env.discard(&lhs_ref); + defer self.env.discard(lhs_ref); try self.env.local_push_ref(switch (self.env.unbox(lhs_ref)) { .number => |lhs_number| switch (self.env.unbox(rhs_ref)) { @@ -357,13 +354,13 @@ pub fn execute(self: *Self, global_ref: ?*const kym.RuntimeRef, name: []const co }, .sub => { - var rhs_ref = try self.env.local_pop(); + const rhs_ref = self.env.local_pop(); - defer self.env.discard(&rhs_ref); + defer self.env.discard(rhs_ref); - var lhs_ref = try self.env.local_pop(); + const lhs_ref = self.env.local_pop(); - defer self.env.discard(&lhs_ref); + defer self.env.discard(lhs_ref); try self.env.local_push_ref(switch (self.env.unbox(lhs_ref)) { .number => |lhs_number| switch (self.env.unbox(rhs_ref)) { @@ -376,13 +373,13 @@ pub fn execute(self: *Self, global_ref: ?*const kym.RuntimeRef, name: []const co }, .mul => { - var rhs_ref = try self.env.local_pop(); + const rhs_ref = self.env.local_pop(); - defer self.env.discard(&rhs_ref); + defer self.env.discard(rhs_ref); - var lhs_ref = try self.env.local_pop(); + const lhs_ref = self.env.local_pop(); - defer self.env.discard(&lhs_ref); + defer self.env.discard(lhs_ref); try self.env.local_push_ref(switch (self.env.unbox(lhs_ref)) { .number => |lhs_number| switch (self.env.unbox(rhs_ref)) { @@ -395,13 +392,13 @@ pub fn execute(self: *Self, global_ref: ?*const kym.RuntimeRef, name: []const co }, .div => { - var rhs_ref = try self.env.local_pop(); + const rhs_ref = self.env.local_pop(); - defer self.env.discard(&rhs_ref); + defer self.env.discard(rhs_ref); - var lhs_ref = try self.env.local_pop(); + const lhs_ref = self.env.local_pop(); - defer self.env.discard(&lhs_ref); + defer self.env.discard(lhs_ref); try self.env.local_push_ref(switch (self.env.unbox(lhs_ref)) { .number => |lhs_number| switch (self.env.unbox(rhs_ref)) { @@ -414,76 +411,72 @@ pub fn execute(self: *Self, global_ref: ?*const kym.RuntimeRef, name: []const co }, .eql => { - var rhs_ref = try self.env.local_pop(); + const rhs_ref = self.env.local_pop(); - defer self.env.discard(&rhs_ref); + defer self.env.discard(rhs_ref); - var lhs_ref = try self.env.local_pop(); + const lhs_ref = self.env.local_pop(); - defer self.env.discard(&lhs_ref); + defer self.env.discard(lhs_ref); try self.env.local_push_boolean(try kym.test_equality(self.env, lhs_ref, rhs_ref)); }, .cgt => { - var rhs_ref = try self.env.local_pop(); + const rhs_ref = self.env.local_pop(); - defer self.env.discard(&rhs_ref); + defer self.env.discard(rhs_ref); - var lhs_ref = try self.env.local_pop(); + const lhs_ref = self.env.local_pop(); - defer self.env.discard(&lhs_ref); + defer self.env.discard(lhs_ref); try self.env.local_push_boolean(try kym.test_difference(self.env, lhs_ref, rhs_ref) > 0); }, .clt => { - var rhs_ref = try self.env.local_pop(); + const rhs_ref = self.env.local_pop(); - defer self.env.discard(&rhs_ref); + defer self.env.discard(rhs_ref); - var lhs_ref = try self.env.local_pop(); + const lhs_ref = self.env.local_pop(); - defer self.env.discard(&lhs_ref); + defer self.env.discard(lhs_ref); try self.env.local_push_boolean(try kym.test_difference(self.env, lhs_ref, rhs_ref) < 0); }, .cge => { - var rhs_ref = try self.env.local_pop(); + const rhs_ref = self.env.local_pop(); - defer self.env.discard(&rhs_ref); + defer self.env.discard(rhs_ref); - var lhs_ref = try self.env.local_pop(); + const lhs_ref = self.env.local_pop(); - defer self.env.discard(&lhs_ref); + defer self.env.discard(lhs_ref); try self.env.local_push_boolean(try kym.test_difference(self.env, lhs_ref, rhs_ref) >= 0); }, .cle => { - var rhs_ref = try self.env.local_pop(); + const rhs_ref = self.env.local_pop(); - defer self.env.discard(&rhs_ref); + defer self.env.discard(rhs_ref); - var lhs_ref = try self.env.local_pop(); + const lhs_ref = self.env.local_pop(); - defer self.env.discard(&lhs_ref); + defer self.env.discard(lhs_ref); try self.env.local_push_boolean(try kym.test_difference(self.env, lhs_ref, rhs_ref) <= 0); }, } } - const result = try self.env.local_pop(); - - try self.env.frame_pop(); - - return result; + return self.env.local_pop(); } pub fn free(self: *Self) void { - for (self.constant_refs.values) |*constant| { + for (self.constant_refs.values) |constant| { self.env.discard(constant); } diff --git a/source/ona/kym/Table.zig b/source/ona/kym/Table.zig index 8867bcc..ae6af0a 100644 --- a/source/ona/kym/Table.zig +++ b/source/ona/kym/Table.zig @@ -22,16 +22,11 @@ pub fn make(env: *kym.RuntimeEnv) Self { } pub const typeinfo = kym.Typeinfo{ - .call = typeinfo_call, .clean = typeinfo_clean, .get = typeinfo_get, .set = typeinfo_set, }; -fn typeinfo_call(context: kym.CallContext) kym.RuntimeError!?*kym.RuntimeRef { - return context.env.raise(error.TypeMismatch, "cannot call a table"); -} - fn typeinfo_clean(userdata: []u8) void { @as(*Self, @ptrCast(@alignCast(userdata.ptr))).free(); } @@ -49,15 +44,15 @@ fn typeinfo_get(context: kym.IndexContext) kym.RuntimeError!?*kym.RuntimeRef { fn typeinfo_set(context: kym.IndexContext, value_ref: ?*const kym.RuntimeRef) kym.RuntimeError!void { const table = @as(*Self, @ptrCast(@alignCast(context.userdata.ptr))); - var acquired_value_ref = context.env.acquire(value_ref); + const acquired_value_ref = context.env.acquire(value_ref); - errdefer context.env.discard(&acquired_value_ref); + errdefer context.env.discard(acquired_value_ref); switch (context.env.unbox(context.index_ref)) { .string => |string| { - var acquired_index_ref = context.env.acquire(context.index_ref); + const acquired_index_ref = context.env.acquire(context.index_ref); - errdefer context.env.discard(&acquired_index_ref); + errdefer context.env.discard(acquired_index_ref); var displaced_table_entry = if (acquired_value_ref) |ref| try table.fields.replace(string, .{ .key_ref = acquired_index_ref, @@ -65,8 +60,8 @@ fn typeinfo_set(context: kym.IndexContext, value_ref: ?*const kym.RuntimeRef) ky }) else table.fields.remove(string); if (displaced_table_entry) |*entry| { - context.env.discard(&entry.value.key_ref); - context.env.discard(&entry.value.value_ref); + context.env.discard(entry.value.key_ref); + context.env.discard(entry.value.value_ref); } }, From 1ea115b277bb833bf3d7df48a38b8351e27a7366 Mon Sep 17 00:00:00 2001 From: kayomn Date: Fri, 21 Jul 2023 23:03:25 +0100 Subject: [PATCH 3/6] Replace globals with native call interface --- debug/app.ona | 4 +- source/coral/io.zig | 43 ++++-- source/coral/list.zig | 28 +++- source/coral/map.zig | 21 ++- source/ona/app.zig | 95 +++---------- source/ona/kym.zig | 294 +++++++++++++++++++++++---------------- source/ona/kym/Ast.zig | 10 +- source/ona/kym/Chunk.zig | 72 ++++++---- source/ona/kym/Table.zig | 5 +- source/ona/ona.zig | 90 ++++++------ source/runner.zig | 4 +- 11 files changed, 380 insertions(+), 286 deletions(-) diff --git a/debug/app.ona b/debug/app.ona index 69c8d59..37c0243 100644 --- a/debug/app.ona +++ b/debug/app.ona @@ -1,8 +1,8 @@ -title = "Afterglow" +@log_info("game is loading") return { - title = title, + title = "Afterglow", width = 1280, height = 800, tick_rate = 60, diff --git a/source/coral/io.zig b/source/coral/io.zig index ce7a6b2..8882160 100644 --- a/source/coral/io.zig +++ b/source/coral/io.zig @@ -116,18 +116,32 @@ pub fn Functor(comptime Output: type, comptime Input: type) type { pub fn bind(comptime State: type, state: *const State, comptime invoker: fn (capture: *const State, input: Input) Output) Self { const is_zero_aligned = @alignOf(State) == 0; + const Invoker = struct { + fn invoke(context: *const anyopaque, input: Input) Output { + if (is_zero_aligned) { + return invoker(@ptrCast(context), input); + } + + return invoker(@ptrCast(@alignCast(context)), input); + } + }; + return .{ .context = if (is_zero_aligned) state else @ptrCast(state), + .invoker = Invoker.invoke, + }; + } - .invoker = struct { - fn invoke(context: *const anyopaque, input: Input) Output { - if (is_zero_aligned) { - return invoker(@ptrCast(context), input); - } + pub fn from(comptime invoker: fn (input: Input) Output) Self { + const Invoker = struct { + fn invoke(_: *const anyopaque, input: Input) Output { + return invoker(input); + } + }; - return invoker(@ptrCast(@alignCast(context)), input); - } - }.invoke, + return .{ + .context = &.{}, + .invoker = Invoker.invoke, }; } @@ -162,6 +176,19 @@ pub fn Generator(comptime Output: type, comptime Input: type) type { }; } + pub fn from(comptime invoker: fn (input: Input) Output) Self { + const Invoker = struct { + fn invoke(_: *const anyopaque, input: Input) Output { + return invoker(input); + } + }; + + return .{ + .context = &.{}, + .invoker = Invoker.invoke, + }; + } + pub fn invoke(self: Self, input: Input) Output { return self.invoker(self.context, input); } diff --git a/source/coral/list.zig b/source/coral/list.zig index d9b794d..a265cb5 100644 --- a/source/coral/list.zig +++ b/source/coral/list.zig @@ -78,7 +78,7 @@ pub fn Stack(comptime Value: type) type { return null; } - return &self.values[self.values.len - 1]; + return self.values[self.values.len - 1]; } pub fn pop(self: *Self) ?Value { @@ -93,6 +93,22 @@ pub fn Stack(comptime Value: type) type { return self.values[last_index]; } + pub fn push_all(self: *Self, values: []const Value) io.AllocationError!void { + const new_length = self.values.len + values.len; + + if (new_length > self.capacity) { + try self.grow(values.len + values.len); + } + + const offset_index = self.values.len; + + self.values = self.values.ptr[0 .. new_length]; + + for (0 .. values.len) |index| { + self.values[offset_index + index] = values[index]; + } + } + pub fn push_one(self: *Self, value: Value) io.AllocationError!void { if (self.values.len == self.capacity) { try self.grow(math.max(1, self.capacity)); @@ -106,3 +122,13 @@ pub fn Stack(comptime Value: type) type { } }; } + +pub fn stack_as_writer(self: *ByteStack) io.Writer { + return io.Writer.bind(ByteStack, self, write_stack); +} + +fn write_stack(stack: *ByteStack, bytes: []const io.Byte) ?usize { + stack.push_all(bytes) catch return null; + + return bytes.len; +} diff --git a/source/coral/map.zig b/source/coral/map.zig index d42b7fc..f9c961f 100644 --- a/source/coral/map.zig +++ b/source/coral/map.zig @@ -103,6 +103,13 @@ pub fn Slab(comptime Value: type) type { }; } +pub fn StringTable(comptime Value: type) type { + return Table([]const io.Byte, Value, .{ + .hash = hash_string, + .match = io.equals, + }); +} + pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTraits(Key)) type { const load_max = 0.75; @@ -143,7 +150,7 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra pub const Iterable = struct { table: *Self, - iterations: usize = 0, + iterations: usize, pub fn next(self: *Iterable) ?Entry { while (self.iterations < self.table.entries.len) { @@ -160,6 +167,13 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra const Self = @This(); + pub fn as_iterable(self: *Self) Iterable { + return .{ + .table = self, + .iterations = 0, + }; + } + pub fn remove(self: *Self, key: Key) ?Entry { const hash_max = math.min(math.max_int(@typeInfo(usize).Int), self.entries.len); var hashed_key = math.wrap(traits.hash(key), math.min_int(@typeInfo(usize).Int), hash_max); @@ -332,8 +346,3 @@ fn hash_string(key: []const io.Byte) usize { return hash_code; } - -pub const string_table_traits = TableTraits([]const io.Byte){ - .hash = hash_string, - .match = io.equals, -}; diff --git a/source/ona/app.zig b/source/ona/app.zig index 99a2430..2a22b06 100644 --- a/source/ona/app.zig +++ b/source/ona/app.zig @@ -12,14 +12,14 @@ pub const Manifest = struct { height: u16 = 480, tick_rate: f32 = 60.0, - pub fn load(self: *Manifest, env: *kym.RuntimeEnv, global_ref: ?*const kym.RuntimeRef, file_access: file.Access) kym.RuntimeError!void { - var manifest_ref = try env.execute_file(global_ref, file_access, file.Path.from(&.{"app.ona"})); + pub fn load(self: *Manifest, env: *kym.RuntimeEnv, file_access: file.Access) kym.RuntimeError!void { + const manifest_ref = try env.execute_file(file_access, file.Path.from(&.{"app.ona"})); - defer env.discard(&manifest_ref); + defer env.discard(manifest_ref); - var title_ref = try kym.get_dynamic_field(env, manifest_ref, "title"); + const title_ref = try kym.get_field(env, manifest_ref, "title"); - defer env.discard(&title_ref); + defer env.discard(title_ref); const title_string = switch (env.unbox(title_ref)) { .string => |string| string, @@ -27,9 +27,9 @@ pub const Manifest = struct { }; const width = @as(u16, get: { - var ref = try kym.get_dynamic_field(env, manifest_ref, "width"); + const ref = try kym.get_field(env, manifest_ref, "width"); - defer env.discard(&ref); + defer env.discard(ref); // TODO: Add safety-checks to int cast. break: get switch (env.unbox(ref)) { @@ -39,9 +39,9 @@ pub const Manifest = struct { }); const height = @as(u16, get: { - var ref = try kym.get_dynamic_field(env, manifest_ref, "height"); + const ref = try kym.get_field(env, manifest_ref, "height"); - defer env.discard(&ref); + defer env.discard(ref); // TODO: Add safety-checks to int cast. break: get switch (env.unbox(ref)) { @@ -51,9 +51,9 @@ pub const Manifest = struct { }); const tick_rate = @as(f32, get: { - var ref = try kym.get_dynamic_field(env, manifest_ref, "tick_rate"); + const ref = try kym.get_field(env, manifest_ref, "tick_rate"); - defer env.discard(&ref); + defer env.discard(ref); break: get switch (env.unbox(ref)) { .number => |number| @floatCast(number), @@ -74,69 +74,14 @@ pub const Manifest = struct { } }; -pub const LogSeverity = enum { - info, - warn, - fail, -}; +pub fn log_info(message: []const coral.io.Byte) void { + ext.SDL_LogInfo(ext.SDL_LOG_CATEGORY_APPLICATION, "%.*s", @as(c_int, @intCast(message.len)), message.ptr); +} -pub const WritableLog = struct { - severity: LogSeverity, - write_buffer: coral.list.ByteStack, +pub fn log_warn(message: []const coral.io.Byte) void { + ext.SDL_LogWarn(ext.SDL_LOG_CATEGORY_APPLICATION, "%.*s", @as(c_int, @intCast(message.len)), message.ptr); +} - pub fn as_writer(self: *WritableLog) coral.io.Writer { - return coral.io.Writer.bind(WritableLog, self, struct { - fn write(writable_log: *WritableLog, bytes: []const coral.io.Byte) ?usize { - writable_log.write(bytes) catch return null; - - return bytes.len; - } - }.write); - } - - pub fn free(self: *WritableLog) void { - self.write_buffer.free(); - } - - pub fn make(log_severity: LogSeverity, allocator: coral.io.Allocator) WritableLog { - return .{ - .severity = log_severity, - .write_buffer = coral.list.ByteStack.make(allocator), - }; - } - - pub fn write(self: *WritableLog, bytes: []const coral.io.Byte) coral.io.AllocationError!void { - const format_string = "%.*s"; - var line_written = @as(usize, 0); - - for (bytes) |byte| { - if (byte == '\n') { - ext.SDL_LogError( - ext.SDL_LOG_CATEGORY_APPLICATION, - format_string, - self.write_buffer.values.len, - self.write_buffer.values.ptr); - - self.write_buffer.clear(); - - line_written = 0; - - continue; - } - - try self.write_buffer.push_one(byte); - - line_written += 1; - } - - if (self.write_buffer.values.len == 0) { - ext.SDL_LogError( - ext.SDL_LOG_CATEGORY_APPLICATION, - format_string, - self.write_buffer.values.len, - self.write_buffer.values.ptr); - - self.write_buffer.clear(); - } - } -}; +pub fn log_fail(message: []const coral.io.Byte) void { + ext.SDL_LogError(ext.SDL_LOG_CATEGORY_APPLICATION, "%.*s", @as(c_int, @intCast(message.len)), message.ptr); +} diff --git a/source/ona/kym.zig b/source/ona/kym.zig index 091e86b..d57ce67 100644 --- a/source/ona/kym.zig +++ b/source/ona/kym.zig @@ -10,30 +10,34 @@ const file = @import("./file.zig"); const tokens = @import("./kym/tokens.zig"); +pub const Args = []const ?*const RuntimeRef; + pub const CallContext = struct { env: *RuntimeEnv, caller: ?*const RuntimeRef, userdata: []coral.io.Byte, - args: []const ?*const RuntimeRef = &.{}, - - pub fn arg_at(self: CallContext, index: u8) RuntimeError!*const RuntimeRef { - if (!coral.math.is_clamped(index, 0, self.args.len - 1)) { - return self.env.check_fail("argument out of bounds"); - } - - return self.args[@as(usize, index)]; - } }; -pub const Callable = coral.io.Generator(RuntimeError!?*RuntimeRef, CallContext); - pub const DynamicObject = struct { userdata: []coral.io.Byte, typeinfo: *const Typeinfo, }; +pub const ErrorHandler = coral.io.Generator(void, ErrorInfo); + +pub const ErrorInfo = struct { + message: []const coral.io.Byte, + frames: []const Frame, +}; + pub const Float = f64; +pub const Frame = struct { + name: []const coral.io.Byte, + arg_count: u8, + locals_top: usize, +}; + pub const IndexContext = struct { env: *RuntimeEnv, userdata: []coral.io.Byte, @@ -42,23 +46,18 @@ pub const IndexContext = struct { pub const RuntimeEnv = struct { allocator: coral.io.Allocator, - err_writer: coral.io.Writer, + error_handler: ErrorHandler, + syscallers: SyscallerTable, local_refs: RefStack, frames: FrameStack, ref_values: RefSlab, - const FrameStack = coral.list.Stack(struct { - arg_count: u8, - locals_top: usize, - }); + const FrameStack = coral.list.Stack(Frame); + + const SyscallerTable = coral.map.StringTable(Syscaller); const RefStack = coral.list.Stack(?*RuntimeRef); - pub const Options = struct { - out_writer: coral.io.Writer = coral.io.null_writer, - err_writer: coral.io.Writer = coral.io.null_writer, - }; - const RefSlab = coral.map.Slab(struct { ref_count: usize, @@ -71,9 +70,9 @@ pub const RuntimeEnv = struct { }, }); - pub const ScriptSource = struct { + pub const Syscall = struct { name: []const coral.io.Byte, - data: []const coral.io.Byte, + caller: Syscaller, }; pub fn acquire(self: *RuntimeEnv, ref: ?*const RuntimeRef) ?*RuntimeRef { @@ -89,22 +88,10 @@ pub const RuntimeEnv = struct { return @ptrFromInt(key); } - pub fn call( - self: *RuntimeEnv, - caller_ref: ?*const RuntimeRef, - callable_ref: ?*const RuntimeRef, - arg_refs: []const *const RuntimeRef, - ) RuntimeError!?*RuntimeRef { - return switch (self.unbox(callable_ref)) { - .dynamic => |dynamic| dynamic.typeinfo.call(.{ - .env = self, - .caller = caller_ref, - .userdata = dynamic.userdata, - .args = arg_refs, - }), - - else => self.raise(error.TypeMismatch, "type is not callable"), - }; + pub fn bind_syscalls(self: *RuntimeEnv, syscalls: []const Syscall) RuntimeError!void { + for (syscalls) |syscall| { + _ = try self.syscallers.replace(syscall.name, syscall.caller); + } } pub fn discard(self: *RuntimeEnv, ref: ?*RuntimeRef) void { @@ -126,58 +113,29 @@ pub const RuntimeEnv = struct { } } - pub fn dynamic_get(self: *RuntimeEnv, indexable_ref: ?*const RuntimeRef, index_ref: ?*const RuntimeRef) RuntimeError!?*RuntimeRef { - return switch (self.unbox(indexable_ref)) { - .nil => self.raise(error.BadOperation, "nil has no such index"), - .boolean => self.raise(error.BadOperation, "boolean has no such index"), - .number => self.raise(error.BadOperation, "number has no such index"), - .string => self.raise(error.BadOperation, "string has no such index"), + pub fn execute_file(self: *RuntimeEnv, file_access: file.Access, file_path: file.Path) RuntimeError!?*RuntimeRef { + if ((try file.allocate_and_load(self.allocator, file_access, file_path))) |file_data| { + defer self.allocator.deallocate(file_data); - .dynamic => |dynamic| dynamic.typeinfo.get(.{ - .userdata = dynamic.userdata, - .env = self, - .index_ref = index_ref, - }), - }; + if (file_path.to_string()) |string| { + return self.execute_script(string, file_data); + } + } + + return self.raise(error.BadOperation, "failed to load file"); } - pub fn dynamic_set(self: *RuntimeEnv, indexable_ref: ?*const RuntimeRef, index_ref: ?*const RuntimeRef, value_ref: ?*const RuntimeRef) RuntimeError!void { - return switch (self.unbox(indexable_ref)) { - .nil => self.raise(error.BadOperation, "nil is immutable"), - .boolean => self.raise(error.BadOperation, "boolean is immutable"), - .number => self.raise(error.BadOperation, "number is immutable"), - .string => self.raise(error.BadOperation, "string is immutable"), - - .dynamic => |dynamic| dynamic.typeinfo.set(.{ - .userdata = dynamic.userdata, - .env = self, - .index_ref = index_ref, - }, value_ref), - }; - } - - pub fn execute_file(self: *RuntimeEnv, global_ref: ?*const RuntimeRef, file_access: file.Access, file_path: file.Path) RuntimeError!?*RuntimeRef { - const error_message = "failed to load file"; - - const file_data = (try file.allocate_and_load(self.allocator, file_access, file_path)) orelse { - return self.raise(error.SystemFailure, error_message); - }; - - defer self.allocator.deallocate(file_data); - - return self.execute_script(global_ref, .{ - .name = file_path.to_string() orelse return self.raise(error.SystemFailure, error_message), - .data = file_data, - }); - } - - pub fn execute_script(self: *RuntimeEnv, global_ref: ?*const RuntimeRef, source: ScriptSource) RuntimeError!?*RuntimeRef { + pub fn execute_script( + self: *RuntimeEnv, + name: []const coral.io.Byte, + data: []const coral.io.Byte, + ) RuntimeError!?*RuntimeRef { var ast = Ast.make(self.allocator); defer ast.free(); { - var tokenizer = tokens.Tokenizer{.source = source.data}; + var tokenizer = tokens.Tokenizer{.source = data}; ast.parse(&tokenizer) catch |parse_error| switch (parse_error) { error.BadSyntax => return self.raise(error.BadSyntax, ast.error_message), @@ -191,7 +149,7 @@ pub const RuntimeEnv = struct { try exe.compile_ast(ast); - return exe.execute(global_ref, source.name); + return try exe.execute(name); } pub fn frame_pop(self: *RuntimeEnv) void { @@ -201,13 +159,12 @@ pub const RuntimeEnv = struct { coral.debug.assert(self.local_refs.drop((self.local_refs.values.len - frame.?.locals_top) + frame.?.arg_count)); } - pub fn frame_push(self: *RuntimeEnv, arg_count: u8) RuntimeError![]const *const RuntimeRef { + pub fn frame_push(self: *RuntimeEnv, frame_name: []const coral.io.Byte, arg_count: u8) RuntimeError!void { try self.frames.push_one(.{ + .name = frame_name, .arg_count = arg_count, .locals_top = self.local_refs.values.len, }); - - return @as([]const *const RuntimeRef, @ptrCast(self.local_refs.values[(self.local_refs.values.len - arg_count) .. self.local_refs.values.len])); } pub fn free(self: *RuntimeEnv) void { @@ -215,6 +172,16 @@ pub const RuntimeEnv = struct { self.ref_values.free(); } + pub fn get_arg(self: *RuntimeEnv, index: usize) RuntimeError!?*const RuntimeRef { + const frame = self.frames.peek() orelse return self.raise(error.IllegalState, "stack underflow"); + + if (index >= frame.arg_count) { + return null; + } + + return self.local_refs.values[frame.locals_top - (1 + index)]; + } + pub fn local_get(self: *RuntimeEnv, local: u8) ?*RuntimeRef { return self.local_refs.values[local]; } @@ -243,12 +210,13 @@ pub const RuntimeEnv = struct { self.local_refs.values[local] = self.acquire(value); } - pub fn make(allocator: coral.io.Allocator, options: Options) RuntimeError!RuntimeEnv { - return .{ + pub fn make(allocator: coral.io.Allocator, error_handler: ErrorHandler) coral.io.AllocationError!RuntimeEnv { + return RuntimeEnv{ .local_refs = RefStack.make(allocator), .ref_values = RefSlab.make(allocator), .frames = FrameStack.make(allocator), - .err_writer = options.err_writer, + .syscallers = SyscallerTable.make(allocator), + .error_handler = error_handler, .allocator = allocator, }; } @@ -260,7 +228,11 @@ pub const RuntimeEnv = struct { })); } - pub fn new_dynamic(self: *RuntimeEnv, userdata: []const coral.io.Byte, typeinfo: *const Typeinfo) RuntimeError!*RuntimeRef { + pub fn new_dynamic( + self: *RuntimeEnv, + userdata: []const coral.io.Byte, + typeinfo: *const Typeinfo, + ) RuntimeError!*RuntimeRef { const userdata_copy = try coral.io.allocate_copy(self.allocator, userdata); errdefer self.allocator.deallocate(userdata_copy); @@ -296,17 +268,19 @@ pub const RuntimeEnv = struct { })); } - pub fn raise(self: *RuntimeEnv, error_value: RuntimeError, error_message: []const coral.io.Byte) RuntimeError { - // TODO: Print stack trace from state. - coral.utf8.print_formatted(self.err_writer, "{name}@{line}: {message}", .{ - .name = "???", - .line = @as(u64, 0), - .message = error_message, - }) catch {}; + pub fn raise(self: *RuntimeEnv, error_value: RuntimeError, message: []const coral.io.Byte) RuntimeError { + self.error_handler.invoke(.{ + .message = message, + .frames = self.frames.values, + }); return error_value; } + pub fn syscaller(self: *RuntimeEnv, name: []const coral.io.Byte) RuntimeError!Syscaller { + return self.syscallers.lookup(name) orelse self.raise(error.BadOperation, "attempt to call undefined syscall"); + } + pub fn unbox(self: *RuntimeEnv, ref: ?*const RuntimeRef) Unboxed { const ref_data = self.ref_values.lookup(@intFromPtr(ref orelse return .nil)); @@ -320,18 +294,48 @@ pub const RuntimeEnv = struct { .dynamic => |dynamic| .{.dynamic = dynamic}, }; } + + pub fn unbox_dynamic(self: *RuntimeEnv, ref: ?*const RuntimeRef) RuntimeError![]const coral.io.Byte { + if (ref) |live_ref| { + const ref_data = self.ref_values.lookup(@intFromPtr(live_ref)); + + coral.debug.assert(ref_data != null); + + if (ref_data.?.object == .dynamic) { + return ref_data.?.object.dynamic; + } + } + + return self.raise(error.TypeMismatch, "expected dynamic"); + } + + pub fn unbox_string(self: *RuntimeEnv, ref: ?*const RuntimeRef) RuntimeError![]const coral.io.Byte { + if (ref) |live_ref| { + const ref_data = self.ref_values.lookup(@intFromPtr(live_ref)); + + coral.debug.assert(ref_data != null); + + if (ref_data.?.object == .string) { + return ref_data.?.object.string; + } + } + + return self.raise(error.TypeMismatch, "expected string"); + } }; pub const RuntimeError = coral.io.AllocationError || error { IllegalState, - SystemFailure, TypeMismatch, BadOperation, BadSyntax, + Assertion, }; pub const RuntimeRef = opaque {}; +pub const Syscaller = coral.io.Generator(RuntimeError!?*RuntimeRef, *RuntimeEnv); + pub const TestContext = struct { env: *RuntimeEnv, userdata: []const coral.io.Byte, @@ -339,6 +343,7 @@ pub const TestContext = struct { }; pub const Typeinfo = struct { + name: []const coral.io.Byte, call: *const fn (context: CallContext) RuntimeError!?*RuntimeRef = default_call, clean: *const fn (userdata: []coral.io.Byte) void = default_clean, get: *const fn (context: IndexContext) RuntimeError!?*RuntimeRef = default_get, @@ -380,26 +385,87 @@ pub const Unboxed = union (enum) { number: Float, string: []const coral.io.Byte, dynamic: *const DynamicObject, + + pub fn expect_dynamic(self: Unboxed, env: *RuntimeEnv) RuntimeError!*const DynamicObject { + return switch (self) { + .dynamic => |dynamic| dynamic, + else => env.raise(error.TypeMismatch, "expected dynamic"), + }; + } }; -pub fn get_dynamic_field(env: *RuntimeEnv, indexable_ref: ?*const RuntimeRef, field_name: []const coral.io.Byte) RuntimeError!?*RuntimeRef { +pub fn assert(env: *RuntimeEnv, condition: bool, message: []const coral.io.Byte) RuntimeError!void { + if (!condition) { + return env.raise(error.Assertion, message); + } +} + +pub fn call( + env: *RuntimeEnv, + caller_ref: ?*const RuntimeRef, + callable_ref: ?*const RuntimeRef, + arg_refs: Args, +) RuntimeError!?*RuntimeRef { + for (arg_refs) |arg_ref| { + try env.local_push_ref(arg_ref); + } + + env.frame_push("", arg_refs.len); + + defer env.frame_pop(); + + const dynamic = try env.unbox(callable_ref).expect_dynamic(); + + return dynamic.type_info.call(.{ + .env = env, + .caller = caller_ref, + .userdata = dynamic.userdata, + }); +} + +pub fn get( + env: *RuntimeEnv, + indexable_ref: ?*const RuntimeRef, + index_ref: ?*const RuntimeRef, +) RuntimeError!?*RuntimeRef { + const dynamic = try env.unbox(indexable_ref).expect_dynamic(env); + + return dynamic.typeinfo.get(.{ + .userdata = dynamic.userdata, + .env = env, + .index_ref = index_ref, + }); +} + +pub fn get_field(env: *RuntimeEnv, indexable_ref: ?*const RuntimeRef, field_name: []const coral.io.Byte) RuntimeError!?*RuntimeRef { const field_name_ref = try env.new_string(field_name); defer env.discard(field_name_ref); - return env.dynamic_get(indexable_ref, field_name_ref); + return get(env, indexable_ref, field_name_ref); } -pub fn new_callable(env: *RuntimeEnv, generator: Callable) RuntimeError!?*RuntimeRef { - const Concrete = struct { - fn call(context: CallContext) RuntimeError!?*RuntimeRef { - return @as(*Callable, @ptrCast(@alignCast(context.userdata.ptr))).invoke(context); - } - }; +pub fn set( + env: *RuntimeEnv, + indexable_ref: ?*const RuntimeRef, + index_ref: ?*const RuntimeRef, + value_ref: ?*const RuntimeRef, +) RuntimeError!void { + const dynamic = try env.unbox(indexable_ref).expect_dynamic(env); - return env.new_dynamic(coral.io.bytes_of(&generator), &.{ - .call = Concrete.call, - }); + return dynamic.typeinfo.set(.{ + .userdata = dynamic.userdata, + .env = env, + .index_ref = index_ref, + }, value_ref); +} + +pub fn set_field(env: *RuntimeEnv, indexable_ref: ?*const RuntimeRef, field_name: []const coral.io.Byte, value_ref: ?*const RuntimeRef) RuntimeError!void { + const field_name_ref = try env.new_string(field_name); + + defer env.discard(field_name_ref); + + return set(env, indexable_ref, field_name_ref, value_ref); } pub fn new_table(env: *RuntimeEnv) RuntimeError!?*RuntimeRef { @@ -410,14 +476,6 @@ pub fn new_table(env: *RuntimeEnv) RuntimeError!?*RuntimeRef { return try env.new_dynamic(coral.io.bytes_of(&table), &Table.typeinfo); } -pub fn set_dynamic_field(env: *RuntimeEnv, indexable_ref: ?*const RuntimeRef, field_name: []const coral.io.Byte, value_ref: ?*const RuntimeRef) RuntimeError!void { - const field_name_ref = try env.new_string(field_name); - - defer env.discard(field_name_ref); - - return env.dynamic_set(indexable_ref, field_name_ref, value_ref); -} - pub fn test_difference(env: *RuntimeEnv, lhs_ref: ?*const RuntimeRef, rhs_ref: ?*const RuntimeRef) RuntimeError!Float { return switch (env.unbox(lhs_ref)) { .nil => env.raise(error.TypeMismatch, "cannot compare nil objects"), diff --git a/source/ona/kym/Ast.zig b/source/ona/kym/Ast.zig index cbf7335..c5781f3 100755 --- a/source/ona/kym/Ast.zig +++ b/source/ona/kym/Ast.zig @@ -17,7 +17,7 @@ pub const Expression = union (enum) { grouped_expression: *Expression, get_local: []const coral.io.Byte, - call_global: struct { + call_system: struct { identifier: []const coral.io.Byte, argument_expressions: List, }, @@ -85,7 +85,7 @@ pub const Statement = union (enum) { expression: Expression, }, - call_global: struct { + call_system: struct { identifier: []const coral.io.Byte, argument_expressions: Expression.List, }, @@ -249,7 +249,7 @@ pub fn parse(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!void { tokenizer.step(); try self.statements.push_one(.{ - .call_global = .{ + .call_system = .{ .argument_expressions = expressions_list, .identifier = identifier, }, @@ -344,7 +344,7 @@ fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression tokenizer.step(); return Expression{ - .call_global = .{ + .call_system = .{ .identifier = identifier, .argument_expressions = expression_list, }, @@ -361,7 +361,7 @@ fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression tokenizer.step(); return Expression{ - .call_global = .{ + .call_system = .{ .identifier = identifier, .argument_expressions = expression_list, }, diff --git a/source/ona/kym/Chunk.zig b/source/ona/kym/Chunk.zig index 5013c3c..c029e15 100644 --- a/source/ona/kym/Chunk.zig +++ b/source/ona/kym/Chunk.zig @@ -90,7 +90,7 @@ const AstCompiler = struct { }); }, - .call_global => |call| { + .call_system => |call| { if (call.argument_expressions.values.len > coral.math.max_int(@typeInfo(u8).Int)) { return self.chunk.env.raise(error.OutOfMemory, "functions may receive a maximum of 255 locals"); } @@ -100,7 +100,8 @@ const AstCompiler = struct { } try self.chunk.append_opcode(.{.push_const = try self.chunk.declare_constant_string(call.identifier)}); - try self.chunk.append_opcode(.{.call = @intCast(call.argument_expressions.values.len)}); + try self.chunk.append_opcode(.{.syscall = @intCast(call.argument_expressions.values.len)}); + try self.chunk.append_opcode(.pop); }, } } @@ -120,7 +121,7 @@ const AstCompiler = struct { } }, - .call_global => |call| { + .call_system => |call| { if (call.argument_expressions.values.len > coral.math.max_int(@typeInfo(u8).Int)) { return self.chunk.env.raise(error.OutOfMemory, "functions may receive a maximum of 255 locals"); } @@ -130,8 +131,8 @@ const AstCompiler = struct { } try self.chunk.append_opcode(.{.push_const = try self.chunk.declare_constant_string(call.identifier)}); - try self.chunk.append_opcode(.get_global); - try self.chunk.append_opcode(.{.call = @intCast(call.argument_expressions.values.len)}); + try self.chunk.append_opcode(.{.syscall = @intCast(call.argument_expressions.values.len)}); + try self.chunk.append_opcode(.pop); } } } @@ -171,6 +172,7 @@ const RefList = coral.list.Stack(?*kym.RuntimeRef); const LocalsList = coral.list.Stack([]const u8); pub const Opcode = union (enum) { + pop, push_nil, push_true, push_false, @@ -178,8 +180,8 @@ pub const Opcode = union (enum) { push_local: u8, push_table: u32, set_local: u8, - get_global, call: u8, + syscall: u8, not, neg, @@ -251,14 +253,14 @@ pub fn declare_constant_string(self: *Self, constant: []const coral.io.Byte) kym return @intCast(tail); } -pub fn execute(self: *Self, global_ref: ?*const kym.RuntimeRef, name: []const coral.io.Byte) kym.RuntimeError!?*kym.RuntimeRef { - _ = name; - _ = try self.env.frame_push(0); +pub fn execute(self: *Self, name: []const coral.io.Byte) kym.RuntimeError!?*kym.RuntimeRef { + try self.env.frame_push(name, 0); defer self.env.frame_pop(); for (self.opcodes.values) |opcode| { switch (opcode) { + .pop => self.env.discard(self.env.local_pop()), .push_nil => try self.env.local_push_ref(null), .push_true => try self.env.local_push_boolean(true), .push_false => try self.env.local_push_boolean(false), @@ -270,10 +272,18 @@ pub fn execute(self: *Self, global_ref: ?*const kym.RuntimeRef, name: []const co defer self.env.discard(table_ref); { + const dynamic = try self.env.unbox(table_ref).expect_dynamic(self.env); var popped = @as(usize, 0); while (popped < field_count) : (popped += 1) { - try self.env.dynamic_set(table_ref, self.env.local_pop(), self.env.local_pop()); + const index_ref = self.env.local_pop(); + const value_ref = self.env.local_pop(); + + try dynamic.typeinfo.set(.{ + .userdata = dynamic.userdata, + .env = self.env, + .index_ref = index_ref, + }, value_ref); } } @@ -288,18 +298,6 @@ pub fn execute(self: *Self, global_ref: ?*const kym.RuntimeRef, name: []const co try self.env.local_push_ref(ref); }, - .get_global => { - const identifier_ref = self.env.local_pop(); - - defer self.env.discard(identifier_ref); - - const ref = try self.env.dynamic_get(global_ref, identifier_ref); - - defer self.env.discard(ref); - - try self.env.local_push_ref(ref); - }, - .set_local => |local| { const ref = self.env.local_pop(); @@ -313,17 +311,43 @@ pub fn execute(self: *Self, global_ref: ?*const kym.RuntimeRef, name: []const co defer self.env.discard(callable_ref); - const args = try self.env.frame_push(arg_count); + try self.env.frame_push("", arg_count); defer self.env.frame_pop(); - const result_ref = try self.env.call(global_ref, callable_ref, args); + const dynamic = try self.env.unbox(callable_ref).expect_dynamic(self.env); + + const result_ref = try dynamic.typeinfo.call(.{ + .env = self.env, + .caller = null, + .userdata = dynamic.userdata, + }); defer self.env.discard(result_ref); try self.env.local_push_ref(result_ref); }, + .syscall => |arg_count| { + const identifier_ref = self.env.local_pop(); + + defer self.env.discard(identifier_ref); + + const identifier = try self.env.unbox_string(identifier_ref); + + try self.env.frame_push(identifier, arg_count); + + errdefer self.env.frame_pop(); + + const result_ref = try (try self.env.syscaller(identifier)).invoke(self.env); + + defer self.env.discard(result_ref); + + self.env.frame_pop(); + + try self.env.local_push_ref(result_ref); + }, + .neg => try self.env.local_push_number(switch (self.env.unbox(self.env.local_pop())) { .number => |number| -number, else => return self.env.raise(error.TypeMismatch, "object is not scalar negatable"), diff --git a/source/ona/kym/Table.zig b/source/ona/kym/Table.zig index ae6af0a..ac5819f 100644 --- a/source/ona/kym/Table.zig +++ b/source/ona/kym/Table.zig @@ -4,10 +4,10 @@ const kym = @import("../kym.zig"); fields: FieldTable, -const FieldTable = coral.map.Table([]const coral.io.Byte, struct { +const FieldTable = coral.map.StringTable(struct { key_ref: ?*kym.RuntimeRef, value_ref: ?*kym.RuntimeRef, -}, coral.map.string_table_traits); +}); const Self = @This(); @@ -22,6 +22,7 @@ pub fn make(env: *kym.RuntimeEnv) Self { } pub const typeinfo = kym.Typeinfo{ + .name = "table", .clean = typeinfo_clean, .get = typeinfo_get, .set = typeinfo_set, diff --git a/source/ona/ona.zig b/source/ona/ona.zig index 82ee995..18facc5 100644 --- a/source/ona/ona.zig +++ b/source/ona/ona.zig @@ -10,81 +10,85 @@ const heap = @import("./heap.zig"); const kym = @import("./kym.zig"); -pub const RuntimeError = error { - OutOfMemory, - InitFailure, - BadManifest, -}; +fn kym_handle_errors(info: kym.ErrorInfo) void { + var remaining_frames = info.frames.len; + + while (remaining_frames != 0) { + remaining_frames -= 1; + + app.log_fail(info.frames[remaining_frames].name); + } +} + +fn kym_log_info(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef { + app.log_info(try env.unbox_string(try env.get_arg(0))); + + return null; +} + +fn kym_log_warn(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef { + app.log_warn(try env.unbox_string(try env.get_arg(0))); + + return null; +} + +fn kym_log_fail(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef { + app.log_fail(try env.unbox_string(try env.get_arg(0))); + + return null; +} fn last_sdl_error() [:0]const u8 { return coral.io.slice_sentineled(@as(u8, 0), @as([*:0]const u8, @ptrCast(ext.SDL_GetError()))); } -pub fn run_app(file_access: file.Access) RuntimeError!void { - var info_log = app.WritableLog.make(.info, heap.allocator); - - defer info_log.free(); - - var fail_log = app.WritableLog.make(.fail, heap.allocator); - - defer fail_log.free(); - +pub fn run_app(file_access: file.Access) void { if (ext.SDL_Init(ext.SDL_INIT_EVERYTHING) != 0) { - try fail_log.write(last_sdl_error()); - - return error.InitFailure; + return app.log_fail(last_sdl_error()); } defer ext.SDL_Quit(); - var script_env = kym.RuntimeEnv.make(heap.allocator, .{ - .out_writer = info_log.as_writer(), - .err_writer = fail_log.as_writer(), - }) catch { - try fail_log.write("failed to initialize script runtime"); - - return error.InitFailure; + var script_env = kym.RuntimeEnv.make(heap.allocator, kym.ErrorHandler.from(kym_handle_errors)) catch { + return app.log_fail("failed to initialize script runtime"); }; defer script_env.free(); - var globals_ref = kym.new_table(&script_env) catch { - try fail_log.write("failed to initialize script runtime"); - - return error.InitFailure; + script_env.bind_syscalls(&.{ + .{ + .name = "log_info", + .caller = kym.Syscaller.from(kym_log_info), + }, + .{ + .name = "log_fail", + .caller = kym.Syscaller.from(kym_log_fail), + }, + }) catch { + return app.log_fail("failed to initialize script runtime"); }; - defer script_env.discard(&globals_ref); - var manifest = app.Manifest{}; - manifest.load(&script_env, globals_ref, file_access) catch { - fail_log.write("failed to load / execute app.ona manifest") catch {}; - - return error.BadManifest; - }; + manifest.load(&script_env, file_access) catch return; const window = create: { const pos = ext.SDL_WINDOWPOS_CENTERED; const flags = 0; break: create ext.SDL_CreateWindow(&manifest.title, pos, pos, manifest.width, manifest.height, flags) orelse { - fail_log.write(last_sdl_error()) catch {}; - - return error.InitFailure; + return app.log_fail(last_sdl_error()); }; }; defer ext.SDL_DestroyWindow(window); const renderer = create: { - const defaultDriverIndex = -1; + const default_driver_index = -1; const flags = ext.SDL_RENDERER_ACCELERATED; - break: create ext.SDL_CreateRenderer(window, defaultDriverIndex, flags) orelse { - fail_log.write(last_sdl_error()) catch {}; - - return error.InitFailure; + break: create ext.SDL_CreateRenderer(window, default_driver_index, flags) orelse { + return app.log_fail(last_sdl_error()); }; }; diff --git a/source/runner.zig b/source/runner.zig index e24965c..d38ba14 100644 --- a/source/runner.zig +++ b/source/runner.zig @@ -1,5 +1,5 @@ const ona = @import("ona"); -pub fn main() ona.RuntimeError!void { - try ona.run_app(.{.sandboxed_path = &ona.file.Path.cwd}); +pub fn main() anyerror!void { + ona.run_app(.{.sandboxed_path = &ona.file.Path.cwd}); } From 7b4b827dc231659528c0f06834798f1e13de3493 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sat, 22 Jul 2023 11:03:23 +0100 Subject: [PATCH 4/6] Refactor Kym runtime API --- source/ona/app.zig | 46 ++++---- source/ona/heap.zig | 10 +- source/ona/kym.zig | 231 +++++++++++++++++++++------------------ source/ona/kym/Chunk.zig | 225 ++++++++++++++++++-------------------- source/ona/ona.zig | 12 +- 5 files changed, 266 insertions(+), 258 deletions(-) diff --git a/source/ona/app.zig b/source/ona/app.zig index 2a22b06..bd4117e 100644 --- a/source/ona/app.zig +++ b/source/ona/app.zig @@ -17,25 +17,17 @@ pub const Manifest = struct { defer env.discard(manifest_ref); - const title_ref = try kym.get_field(env, manifest_ref, "title"); - - defer env.discard(title_ref); - - const title_string = switch (env.unbox(title_ref)) { - .string => |string| string, - else => "", - }; - const width = @as(u16, get: { const ref = try kym.get_field(env, manifest_ref, "width"); defer env.discard(ref); - // TODO: Add safety-checks to int cast. - break: get switch (env.unbox(ref)) { - .number => |number| @intFromFloat(number), - else => self.width, - }; + if (env.unbox(ref).expect_number()) |number| { + // TODO: Add safety-checks to int cast. + break: get @intFromFloat(number); + } + + break: get self.width; }); const height = @as(u16, get: { @@ -43,11 +35,12 @@ pub const Manifest = struct { defer env.discard(ref); - // TODO: Add safety-checks to int cast. - break: get switch (env.unbox(ref)) { - .number => |number| @intFromFloat(number), - else => self.height, - }; + if (env.unbox(ref).expect_number()) |number| { + // TODO: Add safety-checks to int cast. + break: get @intFromFloat(number); + } + + break: get self.height; }); const tick_rate = @as(f32, get: { @@ -55,13 +48,20 @@ pub const Manifest = struct { defer env.discard(ref); - break: get switch (env.unbox(ref)) { - .number => |number| @floatCast(number), - else => self.tick_rate, - }; + if (env.unbox(ref).expect_number()) |number| { + // TODO: Add safety-checks to int cast. + break: get @floatCast(number); + } + + break: get self.tick_rate; }); { + const title_ref = try kym.get_field(env, manifest_ref, "title"); + + defer env.discard(title_ref); + + const title_string = env.unbox(title_ref).expect_string() orelse ""; const limited_title_len = coral.math.min(title_string.len, self.title.len); coral.io.copy(&self.title, title_string[0 .. limited_title_len]); diff --git a/source/ona/heap.zig b/source/ona/heap.zig index b4ec97a..8639461 100644 --- a/source/ona/heap.zig +++ b/source/ona/heap.zig @@ -156,15 +156,15 @@ pub const allocator = coral.io.Allocator.bind(Context, &context, .{ pub fn trace_leaks() void { switch (builtin.mode) { .Debug, .ReleaseSafe => { - var current_allocation_info = context.allocation_info_head; + var current_node = context.head; - while (current_allocation_info) |allocation_info| : (current_allocation_info = allocation_info.next_info) { + while (current_node) |node| : (current_node = node.next) { std.debug.print("{d} byte leak at 0x{x} detected:\n", .{ - allocation_info.size, - @as(usize, allocation_info) + @sizeOf(AllocationNode), + node.size, + @intFromPtr(node) + @sizeOf(AllocationNode), }); - allocation_info.trace.dump(); + node.trace.dump(); } }, diff --git a/source/ona/kym.zig b/source/ona/kym.zig index d57ce67..016ffe9 100644 --- a/source/ona/kym.zig +++ b/source/ona/kym.zig @@ -10,17 +10,48 @@ const file = @import("./file.zig"); const tokens = @import("./kym/tokens.zig"); -pub const Args = []const ?*const RuntimeRef; +pub const Any = union (enum) { + nil, + boolean: bool, + number: Float, + string: []const coral.io.Byte, + dynamic: *DynamicObject, -pub const CallContext = struct { - env: *RuntimeEnv, - caller: ?*const RuntimeRef, - userdata: []coral.io.Byte, + pub fn expect_dynamic(self: Any) ?*DynamicObject { + return switch (self) { + .dynamic => |dynamic| dynamic, + else => null, + }; + } + + pub fn expect_number(self: Any) ?Float { + return switch (self) { + .number => |number| number, + else => null, + }; + } + + pub fn expect_string(self: Any) ?[]const coral.io.Byte { + return switch (self) { + .string => |string| string, + else => null, + }; + } }; +pub const Caller = coral.io.Generator(RuntimeError!?*RuntimeRef, *RuntimeEnv); + pub const DynamicObject = struct { userdata: []coral.io.Byte, typeinfo: *const Typeinfo, + + pub fn as_caller(self: *DynamicObject) Caller { + return Caller.bind(DynamicObject, self, DynamicObject.call); + } + + fn call(self: *DynamicObject, env: *RuntimeEnv) RuntimeError!?*RuntimeRef { + return self.typeinfo.call(env); + } }; pub const ErrorHandler = coral.io.Generator(void, ErrorInfo); @@ -54,7 +85,7 @@ pub const RuntimeEnv = struct { const FrameStack = coral.list.Stack(Frame); - const SyscallerTable = coral.map.StringTable(Syscaller); + const SyscallerTable = coral.map.StringTable(Caller); const RefStack = coral.list.Stack(?*RuntimeRef); @@ -72,7 +103,7 @@ pub const RuntimeEnv = struct { pub const Syscall = struct { name: []const coral.io.Byte, - caller: Syscaller, + caller: Caller, }; pub fn acquire(self: *RuntimeEnv, ref: ?*const RuntimeRef) ?*RuntimeRef { @@ -94,6 +125,30 @@ pub const RuntimeEnv = struct { } } + pub fn call( + self: *RuntimeEnv, + name: []const coral.io.Byte, + arg_count: u8, + caller: Caller, + ) RuntimeError!?*RuntimeRef { + try self.frames.push_one(.{ + .name = name, + .arg_count = arg_count, + .locals_top = self.local_refs.values.len, + }); + + defer { + const frame = self.frames.pop(); + + coral.debug.assert(frame != null); + + coral.debug.assert(self.local_refs.drop( + (self.local_refs.values.len - frame.?.locals_top) + frame.?.arg_count)); + } + + return caller.invoke(self); + } + pub fn discard(self: *RuntimeEnv, ref: ?*RuntimeRef) void { const key = @intFromPtr(ref orelse return); var ref_data = self.ref_values.remove(key) orelse unreachable; @@ -143,28 +198,13 @@ pub const RuntimeEnv = struct { }; } - var exe = Chunk.make(self); + var chunk = Chunk.make(self); - defer exe.free(); + defer chunk.free(); - try exe.compile_ast(ast); + try chunk.compile_ast(ast); - return try exe.execute(name); - } - - pub fn frame_pop(self: *RuntimeEnv) void { - const frame = self.frames.pop(); - - coral.debug.assert(frame != null); - coral.debug.assert(self.local_refs.drop((self.local_refs.values.len - frame.?.locals_top) + frame.?.arg_count)); - } - - pub fn frame_push(self: *RuntimeEnv, frame_name: []const coral.io.Byte, arg_count: u8) RuntimeError!void { - try self.frames.push_one(.{ - .name = frame_name, - .arg_count = arg_count, - .locals_top = self.local_refs.values.len, - }); + return self.call(name, 0, chunk.as_caller()); } pub fn free(self: *RuntimeEnv) void { @@ -172,41 +212,35 @@ pub const RuntimeEnv = struct { self.ref_values.free(); } - pub fn get_arg(self: *RuntimeEnv, index: usize) RuntimeError!?*const RuntimeRef { - const frame = self.frames.peek() orelse return self.raise(error.IllegalState, "stack underflow"); - - if (index >= frame.arg_count) { - return null; + pub fn get_local(self: *RuntimeEnv, local: u8) RuntimeError!?*RuntimeRef { + if (local >= self.local_refs.values.len) { + return self.raise(error.IllegalState, "out of bounds local get"); } - return self.local_refs.values[frame.locals_top - (1 + index)]; - } - - pub fn local_get(self: *RuntimeEnv, local: u8) ?*RuntimeRef { return self.local_refs.values[local]; } - pub fn local_pop(self: *RuntimeEnv) ?*RuntimeRef { - const ref = self.local_refs.pop(); - - coral.debug.assert(ref != null); - - return ref.?; + pub fn pop_local(self: *RuntimeEnv) RuntimeError!?*RuntimeRef { + return self.local_refs.pop() orelse self.raise(error.IllegalState, "stack underflow"); } - pub fn local_push_ref(self: *RuntimeEnv, ref: ?*RuntimeRef) RuntimeError!void { - return self.local_refs.push_one(self.acquire(ref)); - } - - pub fn local_push_boolean(self: *RuntimeEnv, boolean: bool) RuntimeError!void { + pub fn push_boolean(self: *RuntimeEnv, boolean: bool) RuntimeError!void { return self.local_refs.push_one(try self.new_boolean(boolean)); } - pub fn local_push_number(self: *RuntimeEnv, number: Float) RuntimeError!void { + pub fn push_number(self: *RuntimeEnv, number: Float) RuntimeError!void { return self.local_refs.push_one(try self.new_number(number)); } - pub fn local_set(self: *RuntimeEnv, local: u8, value: ?*RuntimeRef) void { + pub fn push_ref(self: *RuntimeEnv, ref: ?*RuntimeRef) RuntimeError!void { + return self.local_refs.push_one(self.acquire(ref)); + } + + pub fn set_local(self: *RuntimeEnv, local: u8, value: ?*RuntimeRef) RuntimeError!void { + if (local >= self.local_refs.values.len) { + return self.raise(error.IllegalState, "out of bounds local set"); + } + self.local_refs.values[local] = self.acquire(value); } @@ -277,50 +311,36 @@ pub const RuntimeEnv = struct { return error_value; } - pub fn syscaller(self: *RuntimeEnv, name: []const coral.io.Byte) RuntimeError!Syscaller { + pub fn syscaller(self: *RuntimeEnv, name: []const coral.io.Byte) RuntimeError!Caller { return self.syscallers.lookup(name) orelse self.raise(error.BadOperation, "attempt to call undefined syscall"); } - pub fn unbox(self: *RuntimeEnv, ref: ?*const RuntimeRef) Unboxed { - const ref_data = self.ref_values.lookup(@intFromPtr(ref orelse return .nil)); - - coral.debug.assert(ref_data != null); - - return switch (ref_data.?.object) { - .false => .{.boolean = false}, - .true => .{.boolean = true}, - .number => |number| .{.number = number}, - .string => |string| .{.string = string}, - .dynamic => |dynamic| .{.dynamic = dynamic}, - }; - } - - pub fn unbox_dynamic(self: *RuntimeEnv, ref: ?*const RuntimeRef) RuntimeError![]const coral.io.Byte { + pub fn unbox(self: *RuntimeEnv, ref: ?*const RuntimeRef) Any { if (ref) |live_ref| { const ref_data = self.ref_values.lookup(@intFromPtr(live_ref)); coral.debug.assert(ref_data != null); - if (ref_data.?.object == .dynamic) { - return ref_data.?.object.dynamic; - } + return switch (ref_data.?.object) { + .false => .{.boolean = false}, + .true => .{.boolean = true}, + .number => |number| .{.number = number}, + .string => |string| .{.string = string}, + .dynamic => |dynamic| .{.dynamic = dynamic}, + }; } - return self.raise(error.TypeMismatch, "expected dynamic"); + return .nil; } - pub fn unbox_string(self: *RuntimeEnv, ref: ?*const RuntimeRef) RuntimeError![]const coral.io.Byte { - if (ref) |live_ref| { - const ref_data = self.ref_values.lookup(@intFromPtr(live_ref)); + pub fn view_arg(self: *RuntimeEnv, index: usize) RuntimeError!?*const RuntimeRef { + const frame = self.frames.peek() orelse return self.raise(error.IllegalState, "stack underflow"); - coral.debug.assert(ref_data != null); - - if (ref_data.?.object == .string) { - return ref_data.?.object.string; - } + if (index >= frame.arg_count) { + return null; } - return self.raise(error.TypeMismatch, "expected string"); + return self.local_refs.values[frame.locals_top - (1 + index)]; } }; @@ -329,13 +349,10 @@ pub const RuntimeError = coral.io.AllocationError || error { TypeMismatch, BadOperation, BadSyntax, - Assertion, }; pub const RuntimeRef = opaque {}; -pub const Syscaller = coral.io.Generator(RuntimeError!?*RuntimeRef, *RuntimeEnv); - pub const TestContext = struct { env: *RuntimeEnv, userdata: []const coral.io.Byte, @@ -344,15 +361,15 @@ pub const TestContext = struct { pub const Typeinfo = struct { name: []const coral.io.Byte, - call: *const fn (context: CallContext) RuntimeError!?*RuntimeRef = default_call, + call: *const fn (env: *RuntimeEnv) RuntimeError!?*RuntimeRef = default_call, clean: *const fn (userdata: []coral.io.Byte) void = default_clean, get: *const fn (context: IndexContext) RuntimeError!?*RuntimeRef = default_get, set: *const fn (context: IndexContext, value: ?*const RuntimeRef) RuntimeError!void = default_set, test_difference: *const fn (context: TestContext) RuntimeError!Float = default_test_difference, test_equality: *const fn (context: TestContext) RuntimeError!bool = default_test_equality, - fn default_call(context: CallContext) RuntimeError!?*RuntimeRef { - return context.env.raise(error.TypeMismatch, "object is not callable"); + fn default_call(env: *RuntimeEnv) RuntimeError!?*RuntimeRef { + return env.raise(error.TypeMismatch, "object is not callable"); } fn default_clean(_: []coral.io.Byte) void { @@ -379,32 +396,11 @@ pub const Typeinfo = struct { } }; -pub const Unboxed = union (enum) { - nil, - boolean: bool, - number: Float, - string: []const coral.io.Byte, - dynamic: *const DynamicObject, - - pub fn expect_dynamic(self: Unboxed, env: *RuntimeEnv) RuntimeError!*const DynamicObject { - return switch (self) { - .dynamic => |dynamic| dynamic, - else => env.raise(error.TypeMismatch, "expected dynamic"), - }; - } -}; - -pub fn assert(env: *RuntimeEnv, condition: bool, message: []const coral.io.Byte) RuntimeError!void { - if (!condition) { - return env.raise(error.Assertion, message); - } -} - pub fn call( env: *RuntimeEnv, caller_ref: ?*const RuntimeRef, callable_ref: ?*const RuntimeRef, - arg_refs: Args, + arg_refs: []const ?*const RuntimeRef, ) RuntimeError!?*RuntimeRef { for (arg_refs) |arg_ref| { try env.local_push_ref(arg_ref); @@ -428,7 +424,7 @@ pub fn get( indexable_ref: ?*const RuntimeRef, index_ref: ?*const RuntimeRef, ) RuntimeError!?*RuntimeRef { - const dynamic = try env.unbox(indexable_ref).expect_dynamic(env); + const dynamic = try unbox_dynamic(env, indexable_ref); return dynamic.typeinfo.get(.{ .userdata = dynamic.userdata, @@ -437,7 +433,11 @@ pub fn get( }); } -pub fn get_field(env: *RuntimeEnv, indexable_ref: ?*const RuntimeRef, field_name: []const coral.io.Byte) RuntimeError!?*RuntimeRef { +pub fn get_field( + env: *RuntimeEnv, + indexable_ref: ?*const RuntimeRef, + field_name: []const coral.io.Byte, +) RuntimeError!?*RuntimeRef { const field_name_ref = try env.new_string(field_name); defer env.discard(field_name_ref); @@ -451,7 +451,7 @@ pub fn set( index_ref: ?*const RuntimeRef, value_ref: ?*const RuntimeRef, ) RuntimeError!void { - const dynamic = try env.unbox(indexable_ref).expect_dynamic(env); + const dynamic = try unbox_dynamic(env, indexable_ref); return dynamic.typeinfo.set(.{ .userdata = dynamic.userdata, @@ -460,7 +460,12 @@ pub fn set( }, value_ref); } -pub fn set_field(env: *RuntimeEnv, indexable_ref: ?*const RuntimeRef, field_name: []const coral.io.Byte, value_ref: ?*const RuntimeRef) RuntimeError!void { +pub fn set_field( + env: *RuntimeEnv, + indexable_ref: ?*const RuntimeRef, + field_name: []const coral.io.Byte, + value_ref: ?*const RuntimeRef, +) RuntimeError!void { const field_name_ref = try env.new_string(field_name); defer env.discard(field_name_ref); @@ -525,3 +530,11 @@ pub fn test_equality(env: *RuntimeEnv, lhs_ref: ?*const RuntimeRef, rhs_ref: ?*c }), }; } + +pub fn unbox_dynamic(env: *RuntimeEnv, ref: ?*const RuntimeRef) RuntimeError!*DynamicObject { + return env.unbox(ref).expect_dynamic() orelse env.raise(error.TypeMismatch, "expected dynamic object"); +} + +pub fn unbox_string(env: *RuntimeEnv, ref: ?*const RuntimeRef) RuntimeError![]const coral.io.Byte { + return env.unbox(ref).expect_string() orelse env.raise(error.TypeMismatch, "expected string object"); +} diff --git a/source/ona/kym/Chunk.zig b/source/ona/kym/Chunk.zig index c029e15..e06002c 100644 --- a/source/ona/kym/Chunk.zig +++ b/source/ona/kym/Chunk.zig @@ -206,6 +206,10 @@ pub fn append_opcode(self: *Self, opcode: Opcode) kym.RuntimeError!void { return self.opcodes.push_one(opcode); } +pub fn as_caller(self: *Self) kym.Caller { + return kym.Caller.bind(Self, self, execute); +} + pub fn compile_ast(self: *Self, ast: Ast) kym.RuntimeError!void { self.free(); @@ -253,250 +257,239 @@ pub fn declare_constant_string(self: *Self, constant: []const coral.io.Byte) kym return @intCast(tail); } -pub fn execute(self: *Self, name: []const coral.io.Byte) kym.RuntimeError!?*kym.RuntimeRef { - try self.env.frame_push(name, 0); - - defer self.env.frame_pop(); - +fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef { for (self.opcodes.values) |opcode| { switch (opcode) { - .pop => self.env.discard(self.env.local_pop()), - .push_nil => try self.env.local_push_ref(null), - .push_true => try self.env.local_push_boolean(true), - .push_false => try self.env.local_push_boolean(false), - .push_const => |constant| try self.env.local_push_ref(self.constant_refs.values[constant]), + .pop => env.discard(try env.pop_local()), + .push_nil => try env.push_ref(null), + .push_true => try env.push_boolean(true), + .push_false => try env.push_boolean(false), + .push_const => |constant| try env.push_ref(self.constant_refs.values[constant]), .push_table => |field_count| { - const table_ref = try kym.new_table(self.env); + const table_ref = try kym.new_table(env); - defer self.env.discard(table_ref); + defer env.discard(table_ref); { - const dynamic = try self.env.unbox(table_ref).expect_dynamic(self.env); + const dynamic = try kym.unbox_dynamic(env, table_ref); var popped = @as(usize, 0); while (popped < field_count) : (popped += 1) { - const index_ref = self.env.local_pop(); - const value_ref = self.env.local_pop(); + const index_ref = try env.pop_local(); + + defer env.discard(index_ref); + + const value_ref = try env.pop_local(); + + defer env.discard(value_ref); try dynamic.typeinfo.set(.{ .userdata = dynamic.userdata, - .env = self.env, + .env = env, .index_ref = index_ref, }, value_ref); } } - try self.env.local_push_ref(table_ref); + try env.push_ref(table_ref); }, .push_local => |local| { - const ref = self.env.local_get(local); + const ref = try env.get_local(local); - defer self.env.discard(ref); + defer env.discard(ref); - try self.env.local_push_ref(ref); + try env.push_ref(ref); }, .set_local => |local| { - const ref = self.env.local_pop(); + const ref = try env.pop_local(); - defer self.env.discard(ref); + defer env.discard(ref); - self.env.local_set(local, ref); + try env.set_local(local, ref); }, .call => |arg_count| { - const callable_ref = self.env.local_pop(); + const result_ref = call: { + const callable_ref = try env.pop_local(); - defer self.env.discard(callable_ref); + defer env.discard(callable_ref); - try self.env.frame_push("", arg_count); + break: call try env.call("", arg_count, (try kym.unbox_dynamic(env, callable_ref)).as_caller()); + }; - defer self.env.frame_pop(); + defer env.discard(result_ref); - const dynamic = try self.env.unbox(callable_ref).expect_dynamic(self.env); - - const result_ref = try dynamic.typeinfo.call(.{ - .env = self.env, - .caller = null, - .userdata = dynamic.userdata, - }); - - defer self.env.discard(result_ref); - - try self.env.local_push_ref(result_ref); + try env.push_ref(result_ref); }, .syscall => |arg_count| { - const identifier_ref = self.env.local_pop(); + const result_ref = call: { + const identifier_ref = try env.pop_local(); - defer self.env.discard(identifier_ref); + defer env.discard(identifier_ref); - const identifier = try self.env.unbox_string(identifier_ref); + const identifier = try kym.unbox_string(env, identifier_ref); - try self.env.frame_push(identifier, arg_count); + break: call try env.call(identifier, arg_count, try env.syscaller(identifier)); + }; - errdefer self.env.frame_pop(); + defer env.discard(result_ref); - const result_ref = try (try self.env.syscaller(identifier)).invoke(self.env); - - defer self.env.discard(result_ref); - - self.env.frame_pop(); - - try self.env.local_push_ref(result_ref); + try env.push_ref(result_ref); }, - .neg => try self.env.local_push_number(switch (self.env.unbox(self.env.local_pop())) { + .neg => try env.push_number(switch (env.unbox(try env.pop_local())) { .number => |number| -number, - else => return self.env.raise(error.TypeMismatch, "object is not scalar negatable"), + else => return env.raise(error.TypeMismatch, "object is not scalar negatable"), }), - .not => try self.env.local_push_boolean(switch (self.env.unbox(self.env.local_pop())) { + .not => try env.push_boolean(switch (env.unbox(try env.pop_local())) { .boolean => |boolean| !boolean, - else => return self.env.raise(error.TypeMismatch, "object is not boolean negatable"), + else => return env.raise(error.TypeMismatch, "object is not boolean negatable"), }), .add => { - const rhs_ref = self.env.local_pop(); + const rhs_ref = try env.pop_local(); - defer self.env.discard(rhs_ref); + defer env.discard(rhs_ref); - const lhs_ref = self.env.local_pop(); + const lhs_ref = try env.pop_local(); - defer self.env.discard(lhs_ref); + defer env.discard(lhs_ref); - try self.env.local_push_ref(switch (self.env.unbox(lhs_ref)) { - .number => |lhs_number| switch (self.env.unbox(rhs_ref)) { - .number => |rhs_number| try self.env.new_number(lhs_number + rhs_number), - else => return self.env.raise(error.TypeMismatch, "right-hand object is not addable"), + try env.push_ref(try switch (env.unbox(lhs_ref)) { + .number => |lhs_number| switch (env.unbox(rhs_ref)) { + .number => |rhs_number| env.new_number(lhs_number + rhs_number), + else => return env.raise(error.TypeMismatch, "right-hand object is not addable"), }, - else => return self.env.raise(error.TypeMismatch, "left-hand object is not addable"), + else => return env.raise(error.TypeMismatch, "left-hand object is not addable"), }); }, .sub => { - const rhs_ref = self.env.local_pop(); + const rhs_ref = try env.pop_local(); - defer self.env.discard(rhs_ref); + defer env.discard(rhs_ref); - const lhs_ref = self.env.local_pop(); + const lhs_ref = try env.pop_local(); - defer self.env.discard(lhs_ref); + defer env.discard(lhs_ref); - try self.env.local_push_ref(switch (self.env.unbox(lhs_ref)) { - .number => |lhs_number| switch (self.env.unbox(rhs_ref)) { - .number => |rhs_number| try self.env.new_number(lhs_number - rhs_number), - else => return self.env.raise(error.TypeMismatch, "right-hand object is not subtractable"), + try env.push_ref(try switch (env.unbox(lhs_ref)) { + .number => |lhs_number| switch (env.unbox(rhs_ref)) { + .number => |rhs_number| env.new_number(lhs_number - rhs_number), + else => return env.raise(error.TypeMismatch, "right-hand object is not subtractable"), }, - else => return self.env.raise(error.TypeMismatch, "left-hand object is not subtractable"), + else => return env.raise(error.TypeMismatch, "left-hand object is not subtractable"), }); }, .mul => { - const rhs_ref = self.env.local_pop(); + const rhs_ref = try env.pop_local(); - defer self.env.discard(rhs_ref); + defer env.discard(rhs_ref); - const lhs_ref = self.env.local_pop(); + const lhs_ref = try env.pop_local(); - defer self.env.discard(lhs_ref); + defer env.discard(lhs_ref); - try self.env.local_push_ref(switch (self.env.unbox(lhs_ref)) { - .number => |lhs_number| switch (self.env.unbox(rhs_ref)) { - .number => |rhs_number| try self.env.new_number(lhs_number * rhs_number), - else => return self.env.raise(error.TypeMismatch, "right-hand object is not multiplyable"), + try env.push_ref(try switch (env.unbox(lhs_ref)) { + .number => |lhs_number| switch (env.unbox(rhs_ref)) { + .number => |rhs_number| env.new_number(lhs_number * rhs_number), + else => return env.raise(error.TypeMismatch, "right-hand object is not multiplyable"), }, - else => return self.env.raise(error.TypeMismatch, "left-hand object is not multiplyable"), + else => return env.raise(error.TypeMismatch, "left-hand object is not multiplyable"), }); }, .div => { - const rhs_ref = self.env.local_pop(); + const rhs_ref = try env.pop_local(); - defer self.env.discard(rhs_ref); + defer env.discard(rhs_ref); - const lhs_ref = self.env.local_pop(); + const lhs_ref = try env.pop_local(); - defer self.env.discard(lhs_ref); + defer env.discard(lhs_ref); - try self.env.local_push_ref(switch (self.env.unbox(lhs_ref)) { - .number => |lhs_number| switch (self.env.unbox(rhs_ref)) { - .number => |rhs_number| try self.env.new_number(lhs_number / rhs_number), - else => return self.env.raise(error.TypeMismatch, "right-hand object is not divisable"), + try env.push_ref(try switch (env.unbox(lhs_ref)) { + .number => |lhs_number| switch (env.unbox(rhs_ref)) { + .number => |rhs_number| env.new_number(lhs_number / rhs_number), + else => return env.raise(error.TypeMismatch, "right-hand object is not divisable"), }, - else => return self.env.raise(error.TypeMismatch, "left-hand object is not divisable"), + else => return env.raise(error.TypeMismatch, "left-hand object is not divisable"), }); }, .eql => { - const rhs_ref = self.env.local_pop(); + const rhs_ref = try env.pop_local(); - defer self.env.discard(rhs_ref); + defer env.discard(rhs_ref); - const lhs_ref = self.env.local_pop(); + const lhs_ref = try env.pop_local(); - defer self.env.discard(lhs_ref); + defer env.discard(lhs_ref); - try self.env.local_push_boolean(try kym.test_equality(self.env, lhs_ref, rhs_ref)); + try env.push_boolean(try kym.test_equality(env, lhs_ref, rhs_ref)); }, .cgt => { - const rhs_ref = self.env.local_pop(); + const rhs_ref = try env.pop_local(); - defer self.env.discard(rhs_ref); + defer env.discard(rhs_ref); - const lhs_ref = self.env.local_pop(); + const lhs_ref = try env.pop_local(); - defer self.env.discard(lhs_ref); + defer env.discard(lhs_ref); - try self.env.local_push_boolean(try kym.test_difference(self.env, lhs_ref, rhs_ref) > 0); + try env.push_boolean(try kym.test_difference(env, lhs_ref, rhs_ref) > 0); }, .clt => { - const rhs_ref = self.env.local_pop(); + const rhs_ref = try env.pop_local(); - defer self.env.discard(rhs_ref); + defer env.discard(rhs_ref); - const lhs_ref = self.env.local_pop(); + const lhs_ref = try env.pop_local(); - defer self.env.discard(lhs_ref); + defer env.discard(lhs_ref); - try self.env.local_push_boolean(try kym.test_difference(self.env, lhs_ref, rhs_ref) < 0); + try env.push_boolean(try kym.test_difference(env, lhs_ref, rhs_ref) < 0); }, .cge => { - const rhs_ref = self.env.local_pop(); + const rhs_ref = try env.pop_local(); - defer self.env.discard(rhs_ref); + defer env.discard(rhs_ref); - const lhs_ref = self.env.local_pop(); + const lhs_ref = try env.pop_local(); - defer self.env.discard(lhs_ref); + defer env.discard(lhs_ref); - try self.env.local_push_boolean(try kym.test_difference(self.env, lhs_ref, rhs_ref) >= 0); + try env.push_boolean(try kym.test_difference(env, lhs_ref, rhs_ref) >= 0); }, .cle => { - const rhs_ref = self.env.local_pop(); + const rhs_ref = try env.pop_local(); - defer self.env.discard(rhs_ref); + defer env.discard(rhs_ref); - const lhs_ref = self.env.local_pop(); + const lhs_ref = try env.pop_local(); - defer self.env.discard(lhs_ref); + defer env.discard(lhs_ref); - try self.env.local_push_boolean(try kym.test_difference(self.env, lhs_ref, rhs_ref) <= 0); + try env.push_boolean(try kym.test_difference(env, lhs_ref, rhs_ref) <= 0); }, } } - return self.env.local_pop(); + return env.pop_local(); } pub fn free(self: *Self) void { diff --git a/source/ona/ona.zig b/source/ona/ona.zig index 18facc5..a0573d1 100644 --- a/source/ona/ona.zig +++ b/source/ona/ona.zig @@ -21,19 +21,19 @@ fn kym_handle_errors(info: kym.ErrorInfo) void { } fn kym_log_info(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef { - app.log_info(try env.unbox_string(try env.get_arg(0))); + app.log_info(try kym.unbox_string(env, try env.view_arg(0))); return null; } fn kym_log_warn(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef { - app.log_warn(try env.unbox_string(try env.get_arg(0))); + app.log_warn(try kym.unbox_string(env, try env.view_arg(0))); return null; } fn kym_log_fail(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef { - app.log_fail(try env.unbox_string(try env.get_arg(0))); + app.log_fail(try kym.unbox_string(env, try env.view_arg(0))); return null; } @@ -43,6 +43,8 @@ fn last_sdl_error() [:0]const u8 { } pub fn run_app(file_access: file.Access) void { + defer heap.trace_leaks(); + if (ext.SDL_Init(ext.SDL_INIT_EVERYTHING) != 0) { return app.log_fail(last_sdl_error()); } @@ -58,11 +60,11 @@ pub fn run_app(file_access: file.Access) void { script_env.bind_syscalls(&.{ .{ .name = "log_info", - .caller = kym.Syscaller.from(kym_log_info), + .caller = kym.Caller.from(kym_log_info), }, .{ .name = "log_fail", - .caller = kym.Syscaller.from(kym_log_fail), + .caller = kym.Caller.from(kym_log_fail), }, }) catch { return app.log_fail("failed to initialize script runtime"); From 8137f2b474608880b3e3211c1f6e8ca750fdbd6c Mon Sep 17 00:00:00 2001 From: kayomn Date: Sat, 22 Jul 2023 12:36:47 +0100 Subject: [PATCH 5/6] Fix memory leaks --- source/ona/kym.zig | 39 +++++++++++++++++++++++++++------------ source/ona/kym/Chunk.zig | 2 -- source/ona/kym/Table.zig | 29 +++++++++++++++++++++-------- 3 files changed, 48 insertions(+), 22 deletions(-) diff --git a/source/ona/kym.zig b/source/ona/kym.zig index 016ffe9..178311b 100644 --- a/source/ona/kym.zig +++ b/source/ona/kym.zig @@ -142,8 +142,17 @@ pub const RuntimeEnv = struct { coral.debug.assert(frame != null); - coral.debug.assert(self.local_refs.drop( - (self.local_refs.values.len - frame.?.locals_top) + frame.?.arg_count)); + { + var pops_remaining = (self.local_refs.values.len - frame.?.locals_top) + frame.?.arg_count; + + while (pops_remaining != 0) : (pops_remaining -= 1) { + const local = self.local_refs.pop(); + + coral.debug.assert(local != null); + + self.discard(local.?); + } + } } return caller.invoke(self); @@ -160,7 +169,13 @@ pub const RuntimeEnv = struct { if (ref_data.ref_count == 0) { switch (ref_data.object) { .string => |string| self.allocator.deallocate(string), - .dynamic => |virtual| self.allocator.deallocate(virtual), + + .dynamic => |dynamic| { + dynamic.typeinfo.clean(self, dynamic.userdata); + self.allocator.deallocate(dynamic.userdata); + self.allocator.deallocate(dynamic); + }, + else => {}, } } else { @@ -208,6 +223,12 @@ pub const RuntimeEnv = struct { } pub fn free(self: *RuntimeEnv) void { + for (self.local_refs.values) |ref| { + self.discard(ref); + } + + self.frames.free(); + self.syscallers.free(); self.local_refs.free(); self.ref_values.free(); } @@ -362,7 +383,7 @@ pub const TestContext = struct { pub const Typeinfo = struct { name: []const coral.io.Byte, call: *const fn (env: *RuntimeEnv) RuntimeError!?*RuntimeRef = default_call, - clean: *const fn (userdata: []coral.io.Byte) void = default_clean, + clean: *const fn (env: *RuntimeEnv, userdata: []coral.io.Byte) void = default_clean, get: *const fn (context: IndexContext) RuntimeError!?*RuntimeRef = default_get, set: *const fn (context: IndexContext, value: ?*const RuntimeRef) RuntimeError!void = default_set, test_difference: *const fn (context: TestContext) RuntimeError!Float = default_test_difference, @@ -372,7 +393,7 @@ pub const Typeinfo = struct { return env.raise(error.TypeMismatch, "object is not callable"); } - fn default_clean(_: []coral.io.Byte) void { + fn default_clean(_: *RuntimeEnv, _: []coral.io.Byte) void { // Nothing to clean by default. } @@ -473,13 +494,7 @@ pub fn set_field( return set(env, indexable_ref, field_name_ref, value_ref); } -pub fn new_table(env: *RuntimeEnv) RuntimeError!?*RuntimeRef { - var table = Table.make(env); - - errdefer table.free(); - - return try env.new_dynamic(coral.io.bytes_of(&table), &Table.typeinfo); -} +pub const new_table = Table.new; pub fn test_difference(env: *RuntimeEnv, lhs_ref: ?*const RuntimeRef, rhs_ref: ?*const RuntimeRef) RuntimeError!Float { return switch (env.unbox(lhs_ref)) { diff --git a/source/ona/kym/Chunk.zig b/source/ona/kym/Chunk.zig index e06002c..c58346f 100644 --- a/source/ona/kym/Chunk.zig +++ b/source/ona/kym/Chunk.zig @@ -211,8 +211,6 @@ pub fn as_caller(self: *Self) kym.Caller { } pub fn compile_ast(self: *Self, ast: Ast) kym.RuntimeError!void { - self.free(); - try self.constant_refs.grow(coral.math.max_int(@typeInfo(u16).Int)); var compiler = AstCompiler{.chunk = self}; diff --git a/source/ona/kym/Table.zig b/source/ona/kym/Table.zig index ac5819f..09ae165 100644 --- a/source/ona/kym/Table.zig +++ b/source/ona/kym/Table.zig @@ -11,14 +11,16 @@ const FieldTable = coral.map.StringTable(struct { const Self = @This(); -pub fn free(self: *Self) void { - self.fields.free(); -} - -pub fn make(env: *kym.RuntimeEnv) Self { - return .{ +pub fn new(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef { + var self = Self{ .fields = FieldTable.make(env.allocator), }; + + errdefer { + self.fields.free(); + } + + return try env.new_dynamic(coral.io.bytes_of(&self), &typeinfo); } pub const typeinfo = kym.Typeinfo{ @@ -28,8 +30,19 @@ pub const typeinfo = kym.Typeinfo{ .set = typeinfo_set, }; -fn typeinfo_clean(userdata: []u8) void { - @as(*Self, @ptrCast(@alignCast(userdata.ptr))).free(); +fn typeinfo_clean(env: *kym.RuntimeEnv, userdata: []coral.io.Byte) void { + const table = @as(*Self, @ptrCast(@alignCast(userdata.ptr))); + + { + var field_iterable = table.fields.as_iterable(); + + while (field_iterable.next()) |entry| { + env.discard(entry.value.key_ref); + env.discard(entry.value.value_ref); + } + } + + table.fields.free(); } fn typeinfo_get(context: kym.IndexContext) kym.RuntimeError!?*kym.RuntimeRef { From a320a795bc0879edf4cab778663ddc189a19563f Mon Sep 17 00:00:00 2001 From: kayomn Date: Sat, 22 Jul 2023 13:57:39 +0100 Subject: [PATCH 6/6] Amend code review issues --- source/coral/arena.zig | 4 ++-- source/coral/io.zig | 4 ++-- source/coral/list.zig | 16 ++-------------- source/coral/map.zig | 21 ++++++++++++--------- source/coral/math.zig | 25 +++++++++++++++++++------ source/coral/utf8.zig | 2 +- source/ona/app.zig | 34 +++++++++++++++++++++++++--------- source/ona/kym.zig | 2 +- source/ona/kym/Ast.zig | 10 +++++----- source/ona/kym/Table.zig | 2 +- source/ona/kym/tokens.zig | 26 +++++++++++++------------- source/ona/ona.zig | 2 +- source/runner.zig | 2 +- 13 files changed, 85 insertions(+), 65 deletions(-) diff --git a/source/coral/arena.zig b/source/coral/arena.zig index 3600a0d..7ba2b1d 100644 --- a/source/coral/arena.zig +++ b/source/coral/arena.zig @@ -74,7 +74,7 @@ pub const Stacking = struct { const aligned_size = (size + alignment - 1) & ~(alignment - 1); if (self.pages.values.len == 0) { - const page = try self.allocate_page(math.max(self.min_page_size, aligned_size)); + const page = try self.allocate_page(@max(self.min_page_size, aligned_size)); page.used = size; @@ -84,7 +84,7 @@ pub const Stacking = struct { var page = self.current_page() orelse unreachable; if (page.available() <= aligned_size) { - page = try self.allocate_page(math.max(self.min_page_size, aligned_size)); + page = try self.allocate_page(@max(self.min_page_size, aligned_size)); } debug.assert(page.available() >= size); diff --git a/source/coral/io.zig b/source/coral/io.zig index 8882160..ad969c9 100644 --- a/source/coral/io.zig +++ b/source/coral/io.zig @@ -96,7 +96,7 @@ pub const FixedBuffer = struct { } pub fn write(self: *FixedBuffer, bytes: []const Byte) usize { - const writable = math.min(self.bytes.len, bytes.len); + const writable = @min(self.bytes.len, bytes.len); copy(self.bytes, bytes); @@ -251,7 +251,7 @@ pub fn copy(target: []Byte, source: []const Byte) void { } pub fn compare(this: []const Byte, that: []const Byte) isize { - const range = math.min(this.len, that.len); + const range = @min(this.len, that.len); var index: usize = 0; while (index < range) : (index += 1) { diff --git a/source/coral/list.zig b/source/coral/list.zig index a265cb5..f6915c9 100644 --- a/source/coral/list.zig +++ b/source/coral/list.zig @@ -16,16 +16,6 @@ pub fn Stack(comptime Value: type) type { self.values = self.values[0 .. 0]; } - pub fn drop(self: *Self, amount: usize) bool { - if (amount > self.values.len) { - return false; - } - - self.values = self.values[0 .. self.values.len - amount]; - - return true; - } - pub fn free(self: *Self) void { if (self.capacity == 0) { return; @@ -111,7 +101,7 @@ pub fn Stack(comptime Value: type) type { pub fn push_one(self: *Self, value: Value) io.AllocationError!void { if (self.values.len == self.capacity) { - try self.grow(math.max(1, self.capacity)); + try self.grow(@max(1, self.capacity)); } const offset_index = self.values.len; @@ -128,7 +118,5 @@ pub fn stack_as_writer(self: *ByteStack) io.Writer { } fn write_stack(stack: *ByteStack, bytes: []const io.Byte) ?usize { - stack.push_all(bytes) catch return null; - - return bytes.len; + return stack.push_all(bytes) catch null; } diff --git a/source/coral/map.zig b/source/coral/map.zig index f9c961f..1bc6a96 100644 --- a/source/coral/map.zig +++ b/source/coral/map.zig @@ -112,6 +112,9 @@ pub fn StringTable(comptime Value: type) type { pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTraits(Key)) type { const load_max = 0.75; + const hash_int = @typeInfo(usize).Int; + const max_int = math.max_int(hash_int); + const min_int = math.min_int(hash_int); return struct { allocator: io.Allocator, @@ -123,8 +126,8 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra value: Value, fn write_into(self: Entry, entry_table: []?Entry) bool { - const hash_max = math.min(math.max_int(@typeInfo(usize).Int), entry_table.len); - var hashed_key = math.wrap(traits.hash(self.key), math.min_int(@typeInfo(usize).Int), hash_max); + const hash_max = @min(max_int, entry_table.len); + var hashed_key = math.wrap(traits.hash(self.key), min_int, hash_max); var iterations = @as(usize, 0); while (true) : (iterations += 1) { @@ -175,8 +178,8 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra } pub fn remove(self: *Self, key: Key) ?Entry { - const hash_max = math.min(math.max_int(@typeInfo(usize).Int), self.entries.len); - var hashed_key = math.wrap(traits.hash(key), math.min_int(@typeInfo(usize).Int), hash_max); + const hash_max = @min(max_int, self.entries.len); + var hashed_key = math.wrap(traits.hash(key), min_int, hash_max); while (true) { const entry = &(self.entries[hashed_key] orelse continue); @@ -199,8 +202,8 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra debug.assert(self.entries.len > self.count); { - const hash_max = math.min(math.max_int(@typeInfo(usize).Int), self.entries.len); - var hashed_key = math.wrap(traits.hash(key), math.min_int(@typeInfo(usize).Int), hash_max); + const hash_max = @min(max_int, self.entries.len); + var hashed_key = math.wrap(traits.hash(key), min_int, hash_max); while (true) { const entry = &(self.entries[hashed_key] orelse { @@ -273,8 +276,8 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra return null; } - const hash_max = math.min(math.max_int(@typeInfo(usize).Int), self.entries.len); - var hashed_key = math.wrap(traits.hash(key), math.min_int(@typeInfo(usize).Int), hash_max); + const hash_max = @min(max_int, self.entries.len); + var hashed_key = math.wrap(traits.hash(key), min_int, hash_max); var iterations = @as(usize, 0); while (iterations < self.count) : (iterations += 1) { @@ -303,7 +306,7 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra return; } - const min_count = math.max(1, self.count); + const min_count = @max(1, self.count); const table_size = min_count * 2; const allocation = @as([*]?Entry, @ptrCast(@alignCast(try self.allocator.reallocate(null, @sizeOf(?Entry) * table_size))))[0 .. table_size]; diff --git a/source/coral/math.zig b/source/coral/math.zig index b0cfb32..d5883cf 100644 --- a/source/coral/math.zig +++ b/source/coral/math.zig @@ -1,7 +1,24 @@ const std = @import("std"); -pub fn max(a: anytype, b: anytype) @TypeOf(a, b) { - return @max(a, b); +pub fn Int(comptime int: std.builtin.Type.Int) type { + return @Type(.{.Int = int}); +} + +pub fn clamp(value: anytype, lower: anytype, upper: anytype) @TypeOf(value, lower, upper) { + return @max(lower, @min(upper, value)); +} + +pub fn clamped_cast(comptime dest_int: std.builtin.Type.Int, value: anytype) Int(dest_int) { + const Value = @TypeOf(value); + + return switch (@typeInfo(Value)) { + .Int => |int| switch (int.signedness) { + .signed => @intCast(clamp(value, min_int(dest_int), max_int(dest_int))), + .unsigned => @intCast(@min(value, max_int(dest_int))), + }, + + else => @compileError("`" ++ @typeName(Value) ++ "` cannot be cast to an int"), + }; } pub fn max_int(comptime int: std.builtin.Type.Int) comptime_int { @@ -12,10 +29,6 @@ pub fn max_int(comptime int: std.builtin.Type.Int) comptime_int { return (1 << (bit_count - @intFromBool(int.signedness == .signed))) - 1; } -pub fn min(a: anytype, b: anytype) @TypeOf(a, b) { - return @min(a, b); -} - pub fn min_int(comptime int: std.builtin.Type.Int) comptime_int { if (int.signedness == .unsigned) return 0; diff --git a/source/coral/utf8.zig b/source/coral/utf8.zig index 48b6199..62fccbc 100644 --- a/source/coral/utf8.zig +++ b/source/coral/utf8.zig @@ -126,7 +126,7 @@ pub const DecimalFormat = struct { switch (@typeInfo(ValueType)) { .Int => |int| { const radix = 10; - var buffer = [_]u8{0} ** (1 + math.max(int.bits, 1)); + var buffer = [_]u8{0} ** (1 + @max(int.bits, 1)); var buffer_start = buffer.len - 1; { diff --git a/source/ona/app.zig b/source/ona/app.zig index bd4117e..6450445 100644 --- a/source/ona/app.zig +++ b/source/ona/app.zig @@ -23,8 +23,9 @@ pub const Manifest = struct { defer env.discard(ref); if (env.unbox(ref).expect_number()) |number| { - // TODO: Add safety-checks to int cast. - break: get @intFromFloat(number); + if (number > 0 and number < coral.math.max_int(@typeInfo(@TypeOf(self.width)).Int)) { + break: get @intFromFloat(number); + } } break: get self.width; @@ -36,8 +37,9 @@ pub const Manifest = struct { defer env.discard(ref); if (env.unbox(ref).expect_number()) |number| { - // TODO: Add safety-checks to int cast. - break: get @intFromFloat(number); + if (number > 0 and number < coral.math.max_int(@typeInfo(@TypeOf(self.height)).Int)) { + break: get @intFromFloat(number); + } } break: get self.height; @@ -49,7 +51,6 @@ pub const Manifest = struct { defer env.discard(ref); if (env.unbox(ref).expect_number()) |number| { - // TODO: Add safety-checks to int cast. break: get @floatCast(number); } @@ -62,7 +63,7 @@ pub const Manifest = struct { defer env.discard(title_ref); const title_string = env.unbox(title_ref).expect_string() orelse ""; - const limited_title_len = coral.math.min(title_string.len, self.title.len); + const limited_title_len = @min(title_string.len, self.title.len); coral.io.copy(&self.title, title_string[0 .. limited_title_len]); coral.io.zero(self.title[limited_title_len .. self.title.len]); @@ -75,13 +76,28 @@ pub const Manifest = struct { }; pub fn log_info(message: []const coral.io.Byte) void { - ext.SDL_LogInfo(ext.SDL_LOG_CATEGORY_APPLICATION, "%.*s", @as(c_int, @intCast(message.len)), message.ptr); + ext.SDL_LogInfo( + ext.SDL_LOG_CATEGORY_APPLICATION, + "%.*s", + coral.math.clamped_cast(@typeInfo(c_int).Int, message.len), + message.ptr, + ); } pub fn log_warn(message: []const coral.io.Byte) void { - ext.SDL_LogWarn(ext.SDL_LOG_CATEGORY_APPLICATION, "%.*s", @as(c_int, @intCast(message.len)), message.ptr); + ext.SDL_LogWarn( + ext.SDL_LOG_CATEGORY_APPLICATION, + "%.*s", + coral.math.clamped_cast(@typeInfo(c_int).Int, message.len), + message.ptr, + ); } pub fn log_fail(message: []const coral.io.Byte) void { - ext.SDL_LogError(ext.SDL_LOG_CATEGORY_APPLICATION, "%.*s", @as(c_int, @intCast(message.len)), message.ptr); + ext.SDL_LogError( + ext.SDL_LOG_CATEGORY_APPLICATION, + "%.*s", + coral.math.clamped_cast(@typeInfo(c_int).Int, message.len), + message.ptr, + ); } diff --git a/source/ona/kym.zig b/source/ona/kym.zig index 178311b..f21b08f 100644 --- a/source/ona/kym.zig +++ b/source/ona/kym.zig @@ -238,7 +238,7 @@ pub const RuntimeEnv = struct { return self.raise(error.IllegalState, "out of bounds local get"); } - return self.local_refs.values[local]; + return self.acquire(self.local_refs.values[local]); } pub fn pop_local(self: *RuntimeEnv) RuntimeError!?*RuntimeRef { diff --git a/source/ona/kym/Ast.zig b/source/ona/kym/Ast.zig index c5781f3..cb2855c 100755 --- a/source/ona/kym/Ast.zig +++ b/source/ona/kym/Ast.zig @@ -192,7 +192,7 @@ pub fn parse(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!void { has_returned = true; }, - .local => |identifier| { + .identifier => |identifier| { tokenizer.step(); switch (tokenizer.token orelse return self.raise(no_effect_message)) { @@ -221,7 +221,7 @@ pub fn parse(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!void { } }, - .global => |identifier| { + .special_identifier => |identifier| { tokenizer.step(); switch (tokenizer.token orelse return self.raise(no_effect_message)) { @@ -333,7 +333,7 @@ fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression return Expression{.string_literal = value}; }, - .global => |identifier| { + .special_identifier => |identifier| { tokenizer.skip(.newline); var expression_list = Expression.List.make(allocator); @@ -375,7 +375,7 @@ fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression } }, - .local => |identifier| { + .identifier => |identifier| { tokenizer.step(); return Expression{.get_local = identifier}; @@ -394,7 +394,7 @@ fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression return Expression{.table_literal = table_fields}; }, - .local => |identifier| { + .identifier => |identifier| { tokenizer.skip(.newline); if (!tokenizer.is_token(.symbol_equals)) { diff --git a/source/ona/kym/Table.zig b/source/ona/kym/Table.zig index 09ae165..11506ed 100644 --- a/source/ona/kym/Table.zig +++ b/source/ona/kym/Table.zig @@ -23,7 +23,7 @@ pub fn new(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef { return try env.new_dynamic(coral.io.bytes_of(&self), &typeinfo); } -pub const typeinfo = kym.Typeinfo{ +const typeinfo = kym.Typeinfo{ .name = "table", .clean = typeinfo_clean, .get = typeinfo_get, diff --git a/source/ona/kym/tokens.zig b/source/ona/kym/tokens.zig index 43f4d2c..49e8d0d 100755 --- a/source/ona/kym/tokens.zig +++ b/source/ona/kym/tokens.zig @@ -1,11 +1,11 @@ const coral = @import("coral"); pub const Token = union(enum) { - unknown: u8, + unknown: coral.io.Byte, newline, - global: []const u8, - local: []const u8, + special_identifier: []const coral.io.Byte, + identifier: []const coral.io.Byte, symbol_plus, symbol_minus, @@ -29,8 +29,8 @@ pub const Token = union(enum) { symbol_equals, symbol_double_equals, - number: []const u8, - string: []const u8, + number: []const coral.io.Byte, + string: []const coral.io.Byte, keyword_nil, keyword_false, @@ -39,13 +39,13 @@ pub const Token = union(enum) { keyword_self, keyword_const, - pub fn text(self: Token) []const u8 { + pub fn text(self: Token) []const coral.io.Byte { return switch (self) { - .unknown => |unknown| @as([*]const u8, @ptrCast(&unknown))[0 .. 1], + .unknown => |unknown| @as([*]const coral.io.Byte, @ptrCast(&unknown))[0 .. 1], .newline => "newline", - .global => |identifier| identifier, - .local => |identifier| identifier, + .special_identifier => |identifier| identifier, + .identifier => |identifier| identifier, .symbol_plus => "+", .symbol_minus => "-", @@ -83,7 +83,7 @@ pub const Token = union(enum) { }; pub const Tokenizer = struct { - source: []const u8, + source: []const coral.io.Byte, lines_stepped: usize = 1, token: ?Token = null, @@ -217,7 +217,7 @@ pub const Tokenizer = struct { else => {}, } - self.token = .{.local = identifier}; + self.token = .{.identifier = identifier}; return; }, @@ -236,7 +236,7 @@ pub const Tokenizer = struct { else => break, }; - self.token = .{.global = self.source[begin .. cursor]}; + self.token = .{.special_identifier = self.source[begin .. cursor]}; return; }, @@ -253,7 +253,7 @@ pub const Tokenizer = struct { else => cursor += 1, }; - self.token = .{.global = self.source[begin .. cursor]}; + self.token = .{.special_identifier = self.source[begin .. cursor]}; cursor += 1; return; diff --git a/source/ona/ona.zig b/source/ona/ona.zig index a0573d1..a7ef131 100644 --- a/source/ona/ona.zig +++ b/source/ona/ona.zig @@ -67,7 +67,7 @@ pub fn run_app(file_access: file.Access) void { .caller = kym.Caller.from(kym_log_fail), }, }) catch { - return app.log_fail("failed to initialize script runtime"); + return app.log_fail("failed to bind syscalls to script runtime"); }; var manifest = app.Manifest{}; diff --git a/source/runner.zig b/source/runner.zig index d38ba14..61dd7b5 100644 --- a/source/runner.zig +++ b/source/runner.zig @@ -1,5 +1,5 @@ const ona = @import("ona"); -pub fn main() anyerror!void { +pub fn main() void { ona.run_app(.{.sandboxed_path = &ona.file.Path.cwd}); }