From 894dabb2c44b8f6b29f2654f400ae1b6b18229c0 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sat, 22 Jul 2023 15:20:31 +0100 Subject: [PATCH 01/12] Fix leak detector message formatting --- source/ona/heap.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/ona/heap.zig b/source/ona/heap.zig index 8639461..8f5d785 100644 --- a/source/ona/heap.zig +++ b/source/ona/heap.zig @@ -159,7 +159,7 @@ pub fn trace_leaks() void { var current_node = context.head; while (current_node) |node| : (current_node = node.next) { - std.debug.print("{d} byte leak at 0x{x} detected:\n", .{ + std.debug.print("{d} byte leak at 0x{x} detected", .{ node.size, @intFromPtr(node) + @sizeOf(AllocationNode), }); -- 2.34.1 From 0141c9c2edd70e052348d28362cb276efc5c224e Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 23 Jul 2023 03:12:45 +0100 Subject: [PATCH 02/12] Partially complete work on table overhaul --- .vscode/launch.json | 3 +- debug/app.ona | 8 +- source/coral/map.zig | 2 +- source/ona/app.zig | 39 +++++--- source/ona/kym.zig | 193 ++++++++++++++++++++++++--------------- source/ona/kym/Ast.zig | 41 ++++++--- source/ona/kym/Chunk.zig | 154 ++++++++++++++++++++++--------- source/ona/kym/Table.zig | 146 +++++++++++++++++++++-------- source/ona/ona.zig | 12 ++- 9 files changed, 409 insertions(+), 189 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 8eeeb8d..5a4674f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,7 +7,8 @@ "request": "launch", "target": "${workspaceRoot}/zig-out/bin/runner", "cwd": "${workspaceRoot}/debug/", - "valuesFormatting": "parseText" + "valuesFormatting": "parseText", + "preLaunchTask": "Build All" }, ] } diff --git a/debug/app.ona b/debug/app.ona index 042320c..c66b16b 100644 --- a/debug/app.ona +++ b/debug/app.ona @@ -3,8 +3,8 @@ @log_info("game is loading") return { - title = "Afterglow", - width = 1280, - height = 800, - tick_rate = 60, + .title = "Afterglow", + .width = 1280, + .height = 800, + .tick_rate = 60, } diff --git a/source/coral/map.zig b/source/coral/map.zig index 1bc6a96..148fd15 100644 --- a/source/coral/map.zig +++ b/source/coral/map.zig @@ -336,7 +336,7 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra pub fn TableTraits(comptime Key: type) type { return struct { hash: fn (key: Key) usize, - match: fn (key: Key, key: Key) bool, + match: fn (self: Key, other: Key) bool, }; } diff --git a/source/ona/app.zig b/source/ona/app.zig index 6450445..a7d49ba 100644 --- a/source/ona/app.zig +++ b/source/ona/app.zig @@ -13,7 +13,7 @@ pub const Manifest = struct { tick_rate: f32 = 60.0, 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"})); + const manifest_ref = try env.execute_file(file_access, file.Path.from(&.{"app.ona"})) orelse return; defer env.discard(manifest_ref); @@ -22,9 +22,11 @@ pub const Manifest = struct { defer env.discard(ref); - if (env.unbox(ref).expect_number()) |number| { - if (number > 0 and number < coral.math.max_int(@typeInfo(@TypeOf(self.width)).Int)) { - break: get @intFromFloat(number); + if (ref) |live_ref| { + if (env.unbox(live_ref).expect_number()) |number| { + if (number > 0 and number < coral.math.max_int(@typeInfo(@TypeOf(self.width)).Int)) { + break: get @intFromFloat(number); + } } } @@ -36,9 +38,11 @@ pub const Manifest = struct { defer env.discard(ref); - if (env.unbox(ref).expect_number()) |number| { - if (number > 0 and number < coral.math.max_int(@typeInfo(@TypeOf(self.height)).Int)) { - break: get @intFromFloat(number); + if (ref) |live_ref| { + if (env.unbox(live_ref).expect_number()) |number| { + if (number > 0 and number < coral.math.max_int(@typeInfo(@TypeOf(self.height)).Int)) { + break: get @intFromFloat(number); + } } } @@ -50,19 +54,30 @@ pub const Manifest = struct { defer env.discard(ref); - if (env.unbox(ref).expect_number()) |number| { - break: get @floatCast(number); + if (ref) |live_ref| { + if (env.unbox(live_ref).expect_number()) |number| { + break: get @floatCast(number); + } } break: get self.tick_rate; }); { - const title_ref = try kym.get_field(env, manifest_ref, "title"); + const ref = try kym.get_field(env, manifest_ref, "title"); - defer env.discard(title_ref); + defer env.discard(ref); + + const title_string = unbox: { + if (ref) |live_ref| { + if (env.unbox(live_ref).expect_string()) |string| { + break: unbox string; + } + } + + break: unbox ""; + }; - const title_string = env.unbox(title_ref).expect_string() orelse ""; const limited_title_len = @min(title_string.len, self.title.len); coral.io.copy(&self.title, title_string[0 .. limited_title_len]); diff --git a/source/ona/kym.zig b/source/ona/kym.zig index 0553851..798ec1e 100644 --- a/source/ona/kym.zig +++ b/source/ona/kym.zig @@ -9,10 +9,10 @@ const coral = @import("coral"); const file = @import("./file.zig"); pub const Any = union (enum) { - nil, boolean: bool, number: Float, string: []const coral.io.Byte, + symbol: []const coral.io.Byte, dynamic: *DynamicObject, pub fn expect_dynamic(self: Any) ?*DynamicObject { @@ -39,6 +39,11 @@ pub const Any = union (enum) { pub const Caller = coral.io.Generator(RuntimeError!?*RuntimeRef, *RuntimeEnv); +pub const Context = struct { + env: *RuntimeEnv, + userdata: []coral.io.Byte +}; + pub const DynamicObject = struct { userdata: []coral.io.Byte, typeinfo: *const Typeinfo, @@ -67,13 +72,8 @@ pub const Frame = struct { locals_top: usize, }; -pub const IndexContext = struct { - env: *RuntimeEnv, - userdata: []coral.io.Byte, - index_ref: ?*const RuntimeRef, -}; - pub const RuntimeEnv = struct { + interned_symbols: SymbolTable, allocator: coral.io.Allocator, error_handler: ErrorHandler, syscallers: SyscallerTable, @@ -83,6 +83,8 @@ pub const RuntimeEnv = struct { const FrameStack = coral.list.Stack(Frame); + const SymbolTable = coral.map.StringTable(*RuntimeRef); + const SyscallerTable = coral.map.StringTable(Caller); const RefStack = coral.list.Stack(?*RuntimeRef); @@ -95,6 +97,7 @@ pub const RuntimeEnv = struct { true, number: Float, string: []coral.io.Byte, + symbol: []coral.io.Byte, dynamic: *DynamicObject, }, }); @@ -104,8 +107,8 @@ pub const RuntimeEnv = struct { caller: Caller, }; - pub fn acquire(self: *RuntimeEnv, ref: ?*const RuntimeRef) ?*RuntimeRef { - const key = @intFromPtr(ref orelse return null); + pub fn acquire(self: *RuntimeEnv, ref: *const RuntimeRef) *RuntimeRef { + const key = @intFromPtr(ref); var ref_data = self.ref_values.remove(key); coral.debug.assert(ref_data != null); @@ -166,15 +169,21 @@ pub const RuntimeEnv = struct { if (ref_data.ref_count == 0) { switch (ref_data.object) { + .false => {}, + .true => {}, + .number => {}, .string => |string| self.allocator.deallocate(string), + .symbol => |symbol| self.allocator.deallocate(symbol), .dynamic => |dynamic| { - dynamic.typeinfo.clean(self, dynamic.userdata); + dynamic.typeinfo.clean(.{ + .env = self, + .userdata = dynamic.userdata, + }); + self.allocator.deallocate(dynamic.userdata); self.allocator.deallocate(dynamic); }, - - else => {}, } } else { coral.debug.assert(self.ref_values.insert_at(key, ref_data)); @@ -217,14 +226,23 @@ pub const RuntimeEnv = struct { } pub fn free(self: *RuntimeEnv) void { - for (self.local_refs.values) |ref| { + while (self.local_refs.pop()) |ref| { self.discard(ref); } + { + var iterable = self.interned_symbols.as_iterable(); + + while (iterable.next()) |entry| { + self.discard(entry.value); + } + } + self.frames.free(); - self.syscallers.free(); self.local_refs.free(); + self.syscallers.free(); self.ref_values.free(); + self.interned_symbols.free(); } pub fn get_local(self: *RuntimeEnv, local: u8) RuntimeError!?*RuntimeRef { @@ -232,7 +250,7 @@ pub const RuntimeEnv = struct { return self.raise(error.IllegalState, "out of bounds local get"); } - return self.acquire(self.local_refs.values[local]); + return self.acquire(self.local_refs.values[local] orelse return null); } pub fn pop_local(self: *RuntimeEnv) RuntimeError!?*RuntimeRef { @@ -248,15 +266,15 @@ pub const RuntimeEnv = struct { } pub fn push_ref(self: *RuntimeEnv, ref: ?*RuntimeRef) RuntimeError!void { - return self.local_refs.push_one(self.acquire(ref)); + return self.local_refs.push_one(if (ref) |live_ref| self.acquire(live_ref) else null); } - pub fn set_local(self: *RuntimeEnv, local: u8, value: ?*RuntimeRef) RuntimeError!void { + pub fn set_local(self: *RuntimeEnv, local: u8, ref: ?*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); + self.local_refs.values[local] = if (ref) |live_ref| self.acquire(live_ref) else null; } pub fn make(allocator: coral.io.Allocator, error_handler: ErrorHandler) coral.io.AllocationError!RuntimeEnv { @@ -265,12 +283,13 @@ pub const RuntimeEnv = struct { .ref_values = RefSlab.make(allocator), .frames = FrameStack.make(allocator), .syscallers = SyscallerTable.make(allocator), + .interned_symbols = SymbolTable.make(allocator), .error_handler = error_handler, .allocator = allocator, }; } - pub fn new_boolean(self: *RuntimeEnv, value: bool) RuntimeError!?*RuntimeRef { + 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, @@ -299,14 +318,14 @@ pub const RuntimeEnv = struct { })); } - pub fn new_number(self: *RuntimeEnv, value: Float) RuntimeError!?*RuntimeRef { + 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 { + 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); @@ -317,6 +336,29 @@ pub const RuntimeEnv = struct { })); } + pub fn new_symbol(self: *RuntimeEnv, symbol_data: []const coral.io.Byte) RuntimeError!*RuntimeRef { + if (self.interned_symbols.lookup(symbol_data)) |symbol_ref| { + return self.acquire(symbol_ref); + } else { + const symbol_copy = try coral.io.allocate_copy(self.allocator, symbol_data); + + const symbol_ref = @as(*RuntimeRef, new: { + errdefer self.allocator.deallocate(symbol_copy); + + break: new @ptrFromInt(try self.ref_values.insert(.{ + .ref_count = 1, + .object = .{.symbol = symbol_copy}, + })); + }); + + errdefer self.discard(symbol_ref); + + coral.debug.assert(try self.interned_symbols.insert(symbol_copy, symbol_ref)); + + return self.acquire(symbol_ref); + } + } + pub fn raise(self: *RuntimeEnv, error_value: RuntimeError, message: []const coral.io.Byte) RuntimeError { self.error_handler.invoke(.{ .message = message, @@ -330,22 +372,19 @@ pub const RuntimeEnv = struct { return self.syscallers.lookup(name) orelse self.raise(error.BadOperation, "attempt to call undefined syscall"); } - pub fn unbox(self: *RuntimeEnv, ref: ?*const RuntimeRef) Any { - if (ref) |live_ref| { - const ref_data = self.ref_values.lookup(@intFromPtr(live_ref)); + pub fn unbox(self: *RuntimeEnv, ref: *const RuntimeRef) Any { + const ref_data = self.ref_values.lookup(@intFromPtr(ref)); - coral.debug.assert(ref_data != null); + 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}, - }; - } - - return .nil; + return switch (ref_data.?.object) { + .false => .{.boolean = false}, + .true => .{.boolean = true}, + .number => |number| .{.number = number}, + .string => |string| .{.string = string}, + .symbol => |symbol| .{.symbol = symbol}, + .dynamic => |dynamic| .{.dynamic = dynamic}, + }; } pub fn view_arg(self: *RuntimeEnv, index: usize) RuntimeError!?*const RuntimeRef { @@ -368,43 +407,37 @@ pub const RuntimeError = coral.io.AllocationError || error { pub const RuntimeRef = opaque {}; -pub const TestContext = struct { - env: *RuntimeEnv, - userdata: []const coral.io.Byte, - other_ref: ?*const RuntimeRef, -}; - pub const Typeinfo = struct { name: []const coral.io.Byte, - call: *const fn (env: *RuntimeEnv) RuntimeError!?*RuntimeRef = default_call, - 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, - test_equality: *const fn (context: TestContext) RuntimeError!bool = default_test_equality, + call: *const fn (context: Context) RuntimeError!?*RuntimeRef = default_call, + clean: *const fn (context: Context) void = default_clean, + get: *const fn (context: Context, index: *const RuntimeRef) RuntimeError!?*RuntimeRef = default_get, + set: *const fn (context: Context, index: *const RuntimeRef, value: ?*const RuntimeRef) RuntimeError!void = default_set, + test_difference: *const fn (context: Context, other_ref: *const RuntimeRef) RuntimeError!Float = default_test_difference, + test_equality: *const fn (context: Context, other_ref: *const RuntimeRef) RuntimeError!bool = default_test_equality, - fn default_call(env: *RuntimeEnv) RuntimeError!?*RuntimeRef { - return env.raise(error.TypeMismatch, "object is not callable"); + fn default_call(context: Context) RuntimeError!?*RuntimeRef { + return context.env.raise(error.TypeMismatch, "object is not callable"); } - fn default_clean(_: *RuntimeEnv, _: []coral.io.Byte) void { + fn default_clean(_: Context) void { // Nothing to clean by default. } - fn default_get(context: IndexContext) RuntimeError!?*RuntimeRef { + fn default_get(context: Context, _: *const RuntimeRef) RuntimeError!?*RuntimeRef { return context.env.raise(error.TypeMismatch, "object is not indexable"); } - fn default_set(context: IndexContext, _: ?*const RuntimeRef) RuntimeError!void { + fn default_set(context: Context, _: *const RuntimeRef, _: ?*const RuntimeRef) RuntimeError!void { return context.env.raise(error.TypeMismatch, "object is not indexable"); } - fn default_test_difference(context: TestContext) RuntimeError!Float { + fn default_test_difference(context: Context, _: *const RuntimeRef) 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)) { + fn default_test_equality(context: Context, other_ref: *const RuntimeRef) RuntimeError!bool { + return switch (context.env.unbox(other_ref)) { .dynamic => |dynamic| context.userdata.ptr == dynamic.userdata.ptr, else => false, }; @@ -414,7 +447,7 @@ pub const Typeinfo = struct { pub fn call( env: *RuntimeEnv, caller_ref: ?*const RuntimeRef, - callable_ref: ?*const RuntimeRef, + callable_ref: *const RuntimeRef, arg_refs: []const ?*const RuntimeRef, ) RuntimeError!?*RuntimeRef { for (arg_refs) |arg_ref| { @@ -436,21 +469,20 @@ pub fn call( pub fn get( env: *RuntimeEnv, - indexable_ref: ?*const RuntimeRef, - index_ref: ?*const RuntimeRef, + indexable_ref: *const RuntimeRef, + index_ref: *const RuntimeRef, ) RuntimeError!?*RuntimeRef { const dynamic = try unbox_dynamic(env, indexable_ref); return dynamic.typeinfo.get(.{ .userdata = dynamic.userdata, .env = env, - .index_ref = index_ref, - }); + }, index_ref); } pub fn get_field( env: *RuntimeEnv, - indexable_ref: ?*const RuntimeRef, + indexable_ref: *const RuntimeRef, field_name: []const coral.io.Byte, ) RuntimeError!?*RuntimeRef { const field_name_ref = try env.new_string(field_name); @@ -462,8 +494,8 @@ pub fn get_field( pub fn set( env: *RuntimeEnv, - indexable_ref: ?*const RuntimeRef, - index_ref: ?*const RuntimeRef, + indexable_ref: *const RuntimeRef, + index_ref: *const RuntimeRef, value_ref: ?*const RuntimeRef, ) RuntimeError!void { const dynamic = try unbox_dynamic(env, indexable_ref); @@ -477,7 +509,7 @@ pub fn set( pub fn set_field( env: *RuntimeEnv, - indexable_ref: ?*const RuntimeRef, + indexable_ref: *const RuntimeRef, field_name: []const coral.io.Byte, value_ref: ?*const RuntimeRef, ) RuntimeError!void { @@ -490,9 +522,8 @@ pub fn set_field( pub const new_table = Table.new; -pub fn test_difference(env: *RuntimeEnv, lhs_ref: ?*const RuntimeRef, rhs_ref: ?*const RuntimeRef) RuntimeError!Float { +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)) { @@ -502,21 +533,25 @@ pub fn test_difference(env: *RuntimeEnv, lhs_ref: ?*const RuntimeRef, rhs_ref: ? .string => |lhs_string| switch (env.unbox(rhs_ref)) { .string => |rhs_string| @floatFromInt(coral.io.compare(lhs_string, rhs_string)), + .symbol => |rhs_symbol| @floatFromInt(coral.io.compare(lhs_string, rhs_symbol)), else => env.raise(error.TypeMismatch, "right-hand object is not comparable with string objects"), }, + .symbol => |lhs_symbol| switch (env.unbox(rhs_ref)) { + .symbol => env.raise(error.TypeMismatch, "cannot compare symbol objects"), + .string => |rhs_string| @floatFromInt(coral.io.compare(lhs_symbol, rhs_string)), + else => env.raise(error.TypeMismatch, "right-hand object is not comparable with symbol objects"), + }, + .dynamic => |lhs_dynamic| lhs_dynamic.typeinfo.test_difference(.{ .env = env, .userdata = lhs_dynamic.userdata, - .other_ref = rhs_ref, - }), + }, rhs_ref), }; } -pub fn test_equality(env: *RuntimeEnv, lhs_ref: ?*const RuntimeRef, rhs_ref: ?*const RuntimeRef) RuntimeError!bool { +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, @@ -529,21 +564,27 @@ pub fn test_equality(env: *RuntimeEnv, lhs_ref: ?*const RuntimeRef, rhs_ref: ?*c .string => |lhs_string| switch (env.unbox(rhs_ref)) { .string => |rhs_string| coral.io.equals(lhs_string, rhs_string), + .symbol => |rhs_symbol| coral.io.equals(lhs_string, rhs_symbol), else => false, }, - .dynamic => |lhs_dynamic| try lhs_dynamic.typeinfo.test_equality(.{ + .symbol => |lhs_symbol| switch (env.unbox(rhs_ref)) { + .symbol => |rhs_symbol| lhs_symbol.ptr == rhs_symbol.ptr, + .string => |rhs_string| coral.io.equals(lhs_symbol, rhs_string), + else => false, + }, + + .dynamic => |lhs_dynamic| lhs_dynamic.typeinfo.test_equality(.{ .env = env, .userdata = lhs_dynamic.userdata, - .other_ref = rhs_ref, - }), + }, rhs_ref), }; } -pub fn unbox_dynamic(env: *RuntimeEnv, ref: ?*const RuntimeRef) RuntimeError!*DynamicObject { +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 { +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/Ast.zig b/source/ona/kym/Ast.zig index 2580606..bcc7c4c 100755 --- a/source/ona/kym/Ast.zig +++ b/source/ona/kym/Ast.zig @@ -15,7 +15,8 @@ pub const Expression = union (enum) { false_literal, number_literal: []const coral.io.Byte, string_literal: []const coral.io.Byte, - table_literal: NamedList, + symbol_literal: []const coral.io.Byte, + table_literal: TableLiteral, grouped_expression: *Expression, get_local: []const coral.io.Byte, @@ -61,9 +62,9 @@ pub const Expression = union (enum) { } }; - pub const NamedList = coral.list.Stack(struct { - identifier: []const coral.io.Byte, - expression: Expression, + pub const TableLiteral = coral.list.Stack(struct { + key_expression: Expression, + value_expression: Expression, }); pub const List = coral.list.Stack(Expression); @@ -397,7 +398,7 @@ fn parse_factor(self: *Self) ParseError!Expression { }, .symbol_brace_left => { - var table_fields = Expression.NamedList.make(allocator); + var table_literal = Expression.TableLiteral.make(allocator); self.tokenizer.skip(.newline); @@ -406,14 +407,30 @@ fn parse_factor(self: *Self) ParseError!Expression { .symbol_brace_right => { self.tokenizer.step(); - return Expression{.table_literal = table_fields}; + return Expression{.table_literal = table_literal}; }, - .identifier => |identifier| { + .symbol_bracket_left => { self.tokenizer.skip(.newline); if (!self.tokenizer.is_token(.symbol_equals)) { - return self.report("expected `=` after identifier"); + return self.report("expected expression after identifier"); + } + }, + + .symbol_period => { + self.tokenizer.step(); + + if (!self.tokenizer.is_token(.identifier)) { + return self.report("expected identifier after `.`"); + } + + const identifier = self.tokenizer.token.?.identifier; + + self.tokenizer.skip(.newline); + + if (!self.tokenizer.is_token(.symbol_equals)) { + return self.report("expected `=` after key"); } self.tokenizer.skip(.newline); @@ -422,9 +439,9 @@ fn parse_factor(self: *Self) ParseError!Expression { return self.report("unexpected end after `=`"); } - try table_fields.push_one(.{ - .expression = try self.parse_expression(), - .identifier = identifier, + try table_literal.push_one(.{ + .value_expression = try self.parse_expression(), + .key_expression = .{.symbol_literal = identifier}, }); switch (self.tokenizer.token orelse return self.report("unexpected end of table")) { @@ -433,7 +450,7 @@ fn parse_factor(self: *Self) ParseError!Expression { .symbol_brace_right => { self.tokenizer.step(); - return Expression{.table_literal = table_fields}; + return Expression{.table_literal = table_literal}; }, else => return self.report("expected `,` or `}` after expression"), diff --git a/source/ona/kym/Chunk.zig b/source/ona/kym/Chunk.zig index c58346f..e0d91c4 100644 --- a/source/ona/kym/Chunk.zig +++ b/source/ona/kym/Chunk.zig @@ -38,17 +38,18 @@ const AstCompiler = struct { try self.chunk.append_opcode(.{.push_const = try self.chunk.declare_constant_string(literal)}); }, + .symbol_literal => |literal| { + try self.chunk.append_opcode(.{.push_const = try self.chunk.declare_constant_symbol(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.compile_expression(field.value_expression); + try self.compile_expression(field.key_expression); } try self.chunk.append_opcode(.{.push_table = @intCast(fields.values.len)}); @@ -165,8 +166,6 @@ const AstCompiler = struct { } }; -pub const Constant = u16; - const RefList = coral.list.Stack(?*kym.RuntimeRef); const LocalsList = coral.list.Stack([]const u8); @@ -176,7 +175,7 @@ pub const Opcode = union (enum) { push_nil, push_true, push_false, - push_const: Constant, + push_const: u16, push_local: u8, push_table: u32, set_local: u8, @@ -223,7 +222,7 @@ pub fn compile_ast(self: *Self, ast: Ast) kym.RuntimeError!void { try self.opcodes.pack(); } -pub fn declare_constant_number(self: *Self, constant: kym.Float) kym.RuntimeError!Constant { +pub fn declare_constant_number(self: *Self, constant: kym.Float) kym.RuntimeError!u16 { const tail = self.constant_refs.values.len; if (tail == coral.math.max_int(@typeInfo(u16).Int)) { @@ -239,7 +238,7 @@ pub fn declare_constant_number(self: *Self, constant: kym.Float) kym.RuntimeErro return @intCast(tail); } -pub fn declare_constant_string(self: *Self, constant: []const coral.io.Byte) kym.RuntimeError!Constant { +pub fn declare_constant_string(self: *Self, constant: []const coral.io.Byte) kym.RuntimeError!u16 { const tail = self.constant_refs.values.len; if (tail == coral.math.max_int(@typeInfo(u16).Int)) { @@ -255,6 +254,22 @@ pub fn declare_constant_string(self: *Self, constant: []const coral.io.Byte) kym return @intCast(tail); } +pub fn declare_constant_symbol(self: *Self, constant: []const coral.io.Byte) kym.RuntimeError!u16 { + 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"); + } + + const constant_ref = try self.env.new_symbol(constant); + + errdefer self.env.discard(constant_ref); + + try self.constant_refs.push_one(constant_ref); + + return @intCast(tail); +} + fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef { for (self.opcodes.values) |opcode| { switch (opcode) { @@ -274,7 +289,9 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef var popped = @as(usize, 0); while (popped < field_count) : (popped += 1) { - const index_ref = try env.pop_local(); + const index_ref = try env.pop_local() orelse { + return env.raise(error.TypeMismatch, "nil is not a valid index"); + }; defer env.discard(index_ref); @@ -285,8 +302,7 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef try dynamic.typeinfo.set(.{ .userdata = dynamic.userdata, .env = env, - .index_ref = index_ref, - }, value_ref); + }, index_ref, value_ref); } } @@ -311,7 +327,9 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef .call => |arg_count| { const result_ref = call: { - const callable_ref = try env.pop_local(); + const callable_ref = try env.pop_local() orelse { + return env.raise(error.TypeMismatch, "nil is not callable"); + }; defer env.discard(callable_ref); @@ -325,7 +343,9 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef .syscall => |arg_count| { const result_ref = call: { - const identifier_ref = try env.pop_local(); + const identifier_ref = try env.pop_local() orelse { + return env.raise(error.TypeMismatch, "nil is not syscallable"); + }; defer env.discard(identifier_ref); @@ -339,22 +359,42 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef try env.push_ref(result_ref); }, - .neg => try env.push_number(switch (env.unbox(try env.pop_local())) { - .number => |number| -number, - else => return env.raise(error.TypeMismatch, "object is not scalar negatable"), - }), + .neg => { + const ref = (try env.pop_local()) orelse { + return env.raise(error.TypeMismatch, "null is not scalar negatable"); + }; - .not => try env.push_boolean(switch (env.unbox(try env.pop_local())) { - .boolean => |boolean| !boolean, - else => return env.raise(error.TypeMismatch, "object is not boolean negatable"), - }), + defer env.discard(ref); + + try env.push_number(switch (env.unbox(ref)) { + .number => |number| -number, + else => return env.raise(error.TypeMismatch, "object is not scalar negatable"), + }); + }, + + .not => { + const ref = (try env.pop_local()) orelse { + return env.raise(error.TypeMismatch, "null is not boolean negatable"); + }; + + defer env.discard(ref); + + try env.push_boolean(switch (env.unbox(ref)) { + .boolean => |boolean| !boolean, + else => return env.raise(error.TypeMismatch, "object is not boolean negatable"), + }); + }, .add => { - const rhs_ref = try env.pop_local(); + const rhs_ref = (try env.pop_local()) orelse { + return env.raise(error.TypeMismatch, "nil is not addable"); + }; defer env.discard(rhs_ref); - const lhs_ref = try env.pop_local(); + const lhs_ref = (try env.pop_local()) orelse { + return env.raise(error.TypeMismatch, "nil is not addable"); + }; defer env.discard(lhs_ref); @@ -369,11 +409,15 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef }, .sub => { - const rhs_ref = try env.pop_local(); + const rhs_ref = (try env.pop_local()) orelse { + return env.raise(error.TypeMismatch, "nil is not subtractable"); + }; defer env.discard(rhs_ref); - const lhs_ref = try env.pop_local(); + const lhs_ref = (try env.pop_local()) orelse { + return env.raise(error.TypeMismatch, "nil is not subtractable"); + }; defer env.discard(lhs_ref); @@ -388,11 +432,15 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef }, .mul => { - const rhs_ref = try env.pop_local(); + const rhs_ref = (try env.pop_local()) orelse { + return env.raise(error.TypeMismatch, "nil is not multiplicable"); + }; defer env.discard(rhs_ref); - const lhs_ref = try env.pop_local(); + const lhs_ref = (try env.pop_local()) orelse { + return env.raise(error.TypeMismatch, "nil is not multiplicable"); + }; defer env.discard(lhs_ref); @@ -407,11 +455,15 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef }, .div => { - const rhs_ref = try env.pop_local(); + const rhs_ref = (try env.pop_local()) orelse { + return env.raise(error.TypeMismatch, "nil is not divisible"); + }; defer env.discard(rhs_ref); - const lhs_ref = try env.pop_local(); + const lhs_ref = (try env.pop_local()) orelse { + return env.raise(error.TypeMismatch, "nil is not divisible"); + }; defer env.discard(lhs_ref); @@ -426,11 +478,15 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef }, .eql => { - const rhs_ref = try env.pop_local(); + const rhs_ref = try env.pop_local() orelse { + return env.raise(error.TypeMismatch, "nil is not equatable"); + }; defer env.discard(rhs_ref); - const lhs_ref = try env.pop_local(); + const lhs_ref = try env.pop_local() orelse { + return env.raise(error.TypeMismatch, "nil is not equatable"); + }; defer env.discard(lhs_ref); @@ -438,11 +494,15 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef }, .cgt => { - const rhs_ref = try env.pop_local(); + const rhs_ref = try env.pop_local() orelse { + return env.raise(error.TypeMismatch, "nil is not comparable"); + }; defer env.discard(rhs_ref); - const lhs_ref = try env.pop_local(); + const lhs_ref = try env.pop_local() orelse { + return env.raise(error.TypeMismatch, "nil is not comparable"); + }; defer env.discard(lhs_ref); @@ -450,11 +510,15 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef }, .clt => { - const rhs_ref = try env.pop_local(); + const rhs_ref = try env.pop_local() orelse { + return env.raise(error.TypeMismatch, "nil is not comparable"); + }; defer env.discard(rhs_ref); - const lhs_ref = try env.pop_local(); + const lhs_ref = try env.pop_local() orelse { + return env.raise(error.TypeMismatch, "nil is not comparable"); + }; defer env.discard(lhs_ref); @@ -462,11 +526,15 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef }, .cge => { - const rhs_ref = try env.pop_local(); + const rhs_ref = try env.pop_local() orelse { + return env.raise(error.TypeMismatch, "nil is not comparable"); + }; defer env.discard(rhs_ref); - const lhs_ref = try env.pop_local(); + const lhs_ref = try env.pop_local() orelse { + return env.raise(error.TypeMismatch, "nil is not comparable"); + }; defer env.discard(lhs_ref); @@ -474,11 +542,15 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef }, .cle => { - const rhs_ref = try env.pop_local(); + const rhs_ref = try env.pop_local() orelse { + return env.raise(error.TypeMismatch, "nil is not comparable"); + }; defer env.discard(rhs_ref); - const lhs_ref = try env.pop_local(); + const lhs_ref = try env.pop_local() orelse { + return env.raise(error.TypeMismatch, "nil is not comparable"); + }; defer env.discard(lhs_ref); diff --git a/source/ona/kym/Table.zig b/source/ona/kym/Table.zig index 11506ed..75eda12 100644 --- a/source/ona/kym/Table.zig +++ b/source/ona/kym/Table.zig @@ -2,22 +2,43 @@ const coral = @import("coral"); const kym = @import("../kym.zig"); -fields: FieldTable, +associative: AssociativeTable, +contiguous: ContiguousList, -const FieldTable = coral.map.StringTable(struct { - key_ref: ?*kym.RuntimeRef, - value_ref: ?*kym.RuntimeRef, +// TODO: Modify hash traits to be support fat contexts rather than passing Envs into the table. +const AssociativeTable = coral.map.Table(Field, *kym.RuntimeRef, .{ + .hash = Field.hash, + .match = Field.match, }); +const ContiguousList = coral.list.Stack(?*kym.RuntimeRef); + +const Field = struct { + env: *kym.RuntimeEnv, + ref: *kym.RuntimeRef, + + fn hash(field: Field) usize { + return switch (field.env.unbox(field.ref)) { + + }; + } + + fn match(self: Field, other: Field) bool { + return kym.test_equality(self.env, self.ref, other.ref); + } +}; + const Self = @This(); -pub fn new(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef { +pub fn new(env: *kym.RuntimeEnv) kym.RuntimeError!*kym.RuntimeRef { var self = Self{ - .fields = FieldTable.make(env.allocator), + .associative = AssociativeTable.make(env.allocator), + .contiguous = ContiguousList.make(env.allocator), }; errdefer { - self.fields.free(); + self.associative.free(); + self.contiguous.free(); } return try env.new_dynamic(coral.io.bytes_of(&self), &typeinfo); @@ -30,62 +51,109 @@ const typeinfo = kym.Typeinfo{ .set = typeinfo_set, }; -fn typeinfo_clean(env: *kym.RuntimeEnv, userdata: []coral.io.Byte) void { - const table = @as(*Self, @ptrCast(@alignCast(userdata.ptr))); +fn typeinfo_clean(context: kym.Context) void { + const table = @as(*Self, @ptrCast(@alignCast(context.userdata.ptr))); { - var field_iterable = table.fields.as_iterable(); + var field_iterable = table.associative.as_iterable(); while (field_iterable.next()) |entry| { - env.discard(entry.value.key_ref); - env.discard(entry.value.value_ref); + context.env.discard(entry.key.ref); + context.env.discard(entry.value); } } - table.fields.free(); + table.associative.free(); + + while (table.contiguous.pop()) |ref| { + context.env.discard(ref); + } + + table.contiguous.free(); } -fn typeinfo_get(context: kym.IndexContext) kym.RuntimeError!?*kym.RuntimeRef { +fn typeinfo_get(context: kym.Context, index_ref: *const kym.RuntimeRef) 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"), - }; + switch (context.env.unbox(index_ref)) { + .symbol, .string => { + return context.env.acquire(table.associative.lookup(index_ref)); + }, + + .number => |number| { + // TODO: Implement dedicated integer type within VM internals for this sort of indexing. + if (@trunc(number) == number) { + const index = @as(usize, @intFromFloat(number)); + + if (index < table.contiguous.values.len) { + return context.env.acquire(table.contiguous.values[index] orelse return null); + } + } + + return context.env.acquire(table.associative.lookup(.{ + .env = context.env, + .ref = index_ref, + })); + }, + + else => return context.env.raise(error.TypeMismatch, "expected symbol, string, or number index"), + } } -fn typeinfo_set(context: kym.IndexContext, value_ref: ?*const kym.RuntimeRef) kym.RuntimeError!void { +fn typeinfo_set(context: kym.Context, index_ref: *const kym.RuntimeRef, value_ref: ?*const kym.RuntimeRef) kym.RuntimeError!void { const table = @as(*Self, @ptrCast(@alignCast(context.userdata.ptr))); - const acquired_value_ref = context.env.acquire(value_ref); - - errdefer context.env.discard(acquired_value_ref); switch (context.env.unbox(context.index_ref)) { - .string => |string| { - const acquired_index_ref = context.env.acquire(context.index_ref); + .symbol, .string => { + const acquired_index_ref = context.env.acquire(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); + const acquired_value_ref = context.env.acquire(value_ref); - if (displaced_table_entry) |*entry| { - context.env.discard(entry.value.key_ref); - context.env.discard(entry.value.value_ref); + errdefer context.env.discard(acquired_value_ref); + + if (try table.associative.replace(acquired_index_ref, acquired_value_ref)) |replaced| { + context.env.discard(replaced.key); + context.env.discard(replaced.value); } }, - .number => |_| { - // TODO: Implement number indices in tables. - unreachable; + .number => |number| { + const index = coral.math.clamped_cast(@typeInfo(usize), number); + + if (index == number) { + if (index < table.contiguous.values.len) { + table.contiguous.values[index] = context.env.acquire(value_ref); + + return; + } + + if (index == table.contiguous.values.len) { + const acquired_index_ref = context.env.acquire(value_ref); + + errdefer context.env.discard(acquired_index_ref); + + try table.contiguous.push_one(acquired_index_ref); + + return; + } + } + + const acquired_index_ref = context.env.acquire(index_ref); + + errdefer context.env.discard(index_ref); + + const acquired_value_ref = context.env.acquire(value_ref); + + errdefer context.env.discard(value_ref); + + if (try table.associative.replace(acquired_index_ref, acquired_value_ref)) |replaced| { + context.env.discard(replaced.key); + context.env.discard(replaced.value); + } }, - else => { - return context.env.raise(error.TypeMismatch, "table objects may only be indexed with strings or numbers"); - }, + else => return context.env.raise(error.TypeMismatch, "expected symbol, string, or number index"), } } diff --git a/source/ona/ona.zig b/source/ona/ona.zig index 35a0445..43897b1 100644 --- a/source/ona/ona.zig +++ b/source/ona/ona.zig @@ -27,19 +27,25 @@ fn kym_handle_errors(info: kym.ErrorInfo) void { } fn kym_log_info(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef { - app.log_info(try kym.unbox_string(env, try env.view_arg(0))); + if (try env.view_arg(0)) |arg| { + app.log_info(try kym.unbox_string(env, arg)); + } return null; } fn kym_log_warn(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef { - app.log_warn(try kym.unbox_string(env, try env.view_arg(0))); + if (try env.view_arg(0)) |arg| { + app.log_warn(try kym.unbox_string(env, arg)); + } return null; } fn kym_log_fail(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef { - app.log_fail(try kym.unbox_string(env, try env.view_arg(0))); + if (try env.view_arg(0)) |arg| { + app.log_fail(try kym.unbox_string(env, arg)); + } return null; } -- 2.34.1 From 8cb0f007d55c22b9fc986669c19245cfcd5d83c5 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sat, 29 Jul 2023 16:04:27 +0100 Subject: [PATCH 03/12] Fix compilation errors --- source/coral/io.zig | 33 +++++ source/coral/list.zig | 4 + source/coral/map.zig | 103 +++++++-------- source/coral/math.zig | 20 +++ source/coral/utf8.zig | 7 +- source/ona/kym.zig | 277 +++++++++++++++++++-------------------- source/ona/kym/Chunk.zig | 101 +++++++++----- source/ona/kym/Table.zig | 186 ++++++++++++++------------ 8 files changed, 410 insertions(+), 321 deletions(-) diff --git a/source/coral/io.zig b/source/coral/io.zig index ad969c9..6410072 100644 --- a/source/coral/io.zig +++ b/source/coral/io.zig @@ -2,6 +2,8 @@ const debug = @import("./debug.zig"); const math = @import("./math.zig"); +const std = @import("std"); + pub const AllocationError = error { OutOfMemory, }; @@ -213,6 +215,27 @@ pub fn allocate_copy(allocator: Allocator, source: []const Byte) AllocationError return allocation; } +pub fn allocate_many(allocator: Allocator, count: usize, value: anytype) AllocationError![]@TypeOf(value) { + const Type = @TypeOf(value); + const typeSize = @sizeOf(Type); + + if (typeSize == 0) { + @compileError("Cannot allocate memory for 0-byte sized type " ++ @typeName(Type)); + } + + const allocations = @as([*]Type, @ptrCast(@alignCast(try allocator.actions.reallocate( + allocator.context, + @returnAddress(), + null, + typeSize * count))))[0 .. count]; + + for (allocations) |*allocation| { + allocation.* = value; + } + + return allocations; +} + pub fn allocate_one(allocator: Allocator, value: anytype) AllocationError!*@TypeOf(value) { const Type = @TypeOf(value); const typeSize = @sizeOf(Type); @@ -265,6 +288,16 @@ pub fn compare(this: []const Byte, that: []const Byte) isize { return @as(isize, @intCast(this.len)) - @as(isize, @intCast(that.len)); } +pub fn djb2_hash(comptime int: std.builtin.Type.Int, target: []const Byte) math.Int(int) { + var hash_code = @as(math.Int(int), 5381); + + for (target) |byte| { + hash_code = ((hash_code << 5) +% hash_code) +% byte; + } + + return hash_code; +} + 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 f02b6ee..56797d8 100644 --- a/source/coral/list.zig +++ b/source/coral/list.zig @@ -41,6 +41,10 @@ pub fn Stack(comptime Value: type) type { self.capacity = grown_capacity; } + pub fn is_empty(self: Self) bool { + return self.values.len == 0; + } + pub fn make(allocator: io.Allocator) Self { return .{ .allocator = allocator, diff --git a/source/coral/map.zig b/source/coral/map.zig index 148fd15..81c11d4 100644 --- a/source/coral/map.zig +++ b/source/coral/map.zig @@ -104,13 +104,20 @@ 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, + return Table([]const io.Byte, Value, struct { + const Self = @This(); + + fn equals(_: Self, key_a: []const io.Byte, key_b: []const io.Byte) bool { + return io.equals(key_a, key_b); + } + + fn hash(_: Self, key: []const io.Byte) usize { + return io.djb2_hash(@typeInfo(usize).Int, key); + } }); } -pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTraits(Key)) type { +pub fn Table(comptime Key: type, comptime Value: type, comptime Traits: type) type { const load_max = 0.75; const hash_int = @typeInfo(usize).Int; const max_int = math.max_int(hash_int); @@ -118,6 +125,7 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra return struct { allocator: io.Allocator, + traits: Traits, count: usize, entries: []?Entry, @@ -125,24 +133,26 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra key: Key, value: Value, - fn write_into(self: Entry, entry_table: []?Entry) bool { - const hash_max = @min(max_int, entry_table.len); - var hashed_key = math.wrap(traits.hash(self.key), min_int, hash_max); + fn write_into(self: Entry, table: *Self) bool { + const hash_max = @min(max_int, table.entries.len); + var hashed_key = math.wrap(table.traits.hash(self.key), min_int, hash_max); var iterations = @as(usize, 0); while (true) : (iterations += 1) { - debug.assert(iterations < entry_table.len); + debug.assert(iterations < table.entries.len); - const table_entry = &(entry_table[hashed_key] orelse { - entry_table[hashed_key] = .{ + const table_entry = &(table.entries[hashed_key] orelse { + table.entries[hashed_key] = .{ .key = self.key, .value = self.value, }; + table.count += 1; + return true; }); - if (traits.match(table_entry.key, self.key)) { + if (table.traits.equals(table_entry.key, self.key)) { return false; } @@ -179,12 +189,12 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra pub fn remove(self: *Self, key: Key) ?Entry { const hash_max = @min(max_int, self.entries.len); - var hashed_key = math.wrap(traits.hash(key), min_int, hash_max); + var hashed_key = math.wrap(self.traits.hash(key), min_int, hash_max); while (true) { const entry = &(self.entries[hashed_key] orelse continue); - if (traits.match(entry.key, key)) { + if (self.traits.equals(entry.key, key)) { const original_entry = entry.*; self.entries[hashed_key] = null; @@ -203,7 +213,7 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra { const hash_max = @min(max_int, self.entries.len); - var hashed_key = math.wrap(traits.hash(key), min_int, hash_max); + var hashed_key = math.wrap(self.traits.hash(key), min_int, hash_max); while (true) { const entry = &(self.entries[hashed_key] orelse { @@ -217,7 +227,7 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra return null; }); - if (traits.match(entry.key, key)) { + if (self.traits.equals(entry.key, key)) { const original_entry = entry.*; entry.* = .{ @@ -261,14 +271,12 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra debug.assert(self.entries.len > self.count); - defer self.count += 1; - const entry = Entry{ .key = key, .value = value, }; - return entry.write_into(self.entries); + return entry.write_into(self); } pub fn lookup(self: Self, key: Key) ?Value { @@ -277,13 +285,13 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra } const hash_max = @min(max_int, self.entries.len); - var hashed_key = math.wrap(traits.hash(key), min_int, hash_max); + var hashed_key = math.wrap(self.traits.hash(key), min_int, hash_max); var iterations = @as(usize, 0); while (iterations < self.count) : (iterations += 1) { const entry = &(self.entries[hashed_key] orelse return null); - if (traits.match(entry.key, key)) { + if (self.traits.equals(entry.key, key)) { return entry.value; } @@ -293,11 +301,12 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra return null; } - pub fn make(allocator: io.Allocator) Self { + pub fn make(allocator: io.Allocator, traits: Traits) Self { return .{ .allocator = allocator, .count = 0, .entries = &.{}, + .traits = traits, }; } @@ -306,46 +315,26 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra return; } - 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]; + var table = make(self.allocator, self.traits); - errdefer self.allocator.deallocate(allocation); + errdefer table.free(); - self.entries = replace_table: { - for (allocation) |*entry| { - entry.* = null; - } + table.entries = allocate: { + const min_count = @max(1, self.count); + const table_size = min_count * 2; - if (self.entries.len != 0) { - for (self.entries) |maybe_entry| { - if (maybe_entry) |entry| { - debug.assert(entry.write_into(allocation)); - } - } - - self.allocator.deallocate(self.entries); - } - - break: replace_table allocation; + break: allocate try io.allocate_many(self.allocator, table_size, @as(?Entry, null)); }; + + for (self.entries) |maybe_entry| { + if (maybe_entry) |entry| { + debug.assert(entry.write_into(&table)); + } + } + + self.free(); + + self.* = table; } }; } - -pub fn TableTraits(comptime Key: type) type { - return struct { - hash: fn (key: Key) usize, - match: fn (self: Key, other: Key) bool, - }; -} - -fn hash_string(key: []const io.Byte) usize { - var hash_code = @as(usize, 5381); - - for (key) |byte| { - hash_code = ((hash_code << 5) +% hash_code) +% byte; - } - - return hash_code; -} diff --git a/source/coral/math.zig b/source/coral/math.zig index de8d37f..3f0de17 100644 --- a/source/coral/math.zig +++ b/source/coral/math.zig @@ -8,6 +8,24 @@ pub fn clamp(value: anytype, lower: anytype, upper: anytype) @TypeOf(value, lowe return @max(lower, @min(upper, value)); } +pub fn checked_add(a: anytype, b: anytype) ?@TypeOf(a + b) { + const result = @addWithOverflow(a, b); + + return if (result.@"1" == 0) result.@"0" else null; +} + +pub fn checked_mul(a: anytype, b: anytype) ?@TypeOf(a * b) { + const result = @mulWithOverflow(a, b); + + return if (result.@"1" == 0) result.@"0" else null; +} + +pub fn checked_sub(a: anytype, b: anytype) ?@TypeOf(a - b) { + const result = @subWithOverflow(a, b); + + return if (result.@"1" == 0) result.@"0" else null; +} + pub fn clamped_cast(comptime dest_int: std.builtin.Type.Int, value: anytype) Int(dest_int) { const Value = @TypeOf(value); @@ -17,6 +35,8 @@ pub fn clamped_cast(comptime dest_int: std.builtin.Type.Int, value: anytype) Int .unsigned => @intCast(@min(value, max_int(dest_int))), }, + .Float => @intFromFloat(clamp(value, min_int(dest_int), max_int(dest_int))), + else => @compileError("`" ++ @typeName(Value) ++ "` cannot be cast to an int"), }; } diff --git a/source/coral/utf8.zig b/source/coral/utf8.zig index 62fccbc..97fb597 100644 --- a/source/coral/utf8.zig +++ b/source/coral/utf8.zig @@ -33,9 +33,10 @@ pub const DecimalFormat = struct { switch (code) { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => { - result = try math.checked_add( - try math.checked_mul(result, radix), - try math.checked_sub(code, '0')); + const offset_code = math.checked_sub(code, '0') orelse return null; + + result = math.checked_mul(result, radix) orelse return null; + result = math.checked_add(result, offset_code) orelse return null; }, else => { diff --git a/source/ona/kym.zig b/source/ona/kym.zig index 798ec1e..3e054db 100644 --- a/source/ona/kym.zig +++ b/source/ona/kym.zig @@ -10,12 +10,13 @@ const file = @import("./file.zig"); pub const Any = union (enum) { boolean: bool, - number: Float, + fixed: Fixed, + float: Float, string: []const coral.io.Byte, symbol: []const coral.io.Byte, - dynamic: *DynamicObject, + dynamic: *const DynamicObject, - pub fn expect_dynamic(self: Any) ?*DynamicObject { + pub fn expect_dynamic(self: Any) ?*const DynamicObject { return switch (self) { .dynamic => |dynamic| dynamic, else => null, @@ -24,7 +25,8 @@ pub const Any = union (enum) { pub fn expect_number(self: Any) ?Float { return switch (self) { - .number => |number| number, + .fixed => |fixed| @floatFromInt(fixed), + .float => |float| float, else => null, }; } @@ -39,21 +41,15 @@ pub const Any = union (enum) { pub const Caller = coral.io.Generator(RuntimeError!?*RuntimeRef, *RuntimeEnv); -pub const Context = struct { - env: *RuntimeEnv, - userdata: []coral.io.Byte -}; - 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); + return self.typeinfo.call(.{ + .env = env, + .userdata = self.userdata, + }); } }; @@ -64,6 +60,8 @@ pub const ErrorInfo = struct { frames: []const Frame, }; +pub const Fixed = i32; + pub const Float = f64; pub const Frame = struct { @@ -72,6 +70,11 @@ pub const Frame = struct { locals_top: usize, }; +pub const Method = struct { + env: *RuntimeEnv, + userdata: []coral.io.Byte, +}; + pub const RuntimeEnv = struct { interned_symbols: SymbolTable, allocator: coral.io.Allocator, @@ -95,7 +98,8 @@ pub const RuntimeEnv = struct { object: union (enum) { false, true, - number: Float, + float: Float, + fixed: Fixed, string: []coral.io.Byte, symbol: []coral.io.Byte, dynamic: *DynamicObject, @@ -128,9 +132,9 @@ pub const RuntimeEnv = struct { pub fn call( self: *RuntimeEnv, - name: []const coral.io.Byte, - arg_count: u8, caller: Caller, + arg_count: u8, + name: []const coral.io.Byte, ) RuntimeError!?*RuntimeRef { try self.frames.push_one(.{ .name = name, @@ -159,6 +163,13 @@ pub const RuntimeEnv = struct { return caller.invoke(self); } + pub fn callable(self: *RuntimeEnv, ref: *const RuntimeRef) RuntimeError!Caller { + return switch ((self.ref_values.lookup(@intFromPtr(ref)) orelse unreachable).object) { + .dynamic => |dynamic| Caller.bind(DynamicObject, dynamic, DynamicObject.call), + else => self.raise(error.TypeMismatch, "object is not callable"), + }; + } + pub fn discard(self: *RuntimeEnv, ref: ?*RuntimeRef) void { const key = @intFromPtr(ref orelse return); var ref_data = self.ref_values.remove(key) orelse unreachable; @@ -169,9 +180,7 @@ pub const RuntimeEnv = struct { if (ref_data.ref_count == 0) { switch (ref_data.object) { - .false => {}, - .true => {}, - .number => {}, + .false, .true, .float, .fixed => {}, .string => |string| self.allocator.deallocate(string), .symbol => |symbol| self.allocator.deallocate(symbol), @@ -222,7 +231,7 @@ pub const RuntimeEnv = struct { try chunk.compile_ast(ast); - return self.call(name, 0, chunk.as_caller()); + return self.call(chunk.as_caller(), 0, name); } pub fn free(self: *RuntimeEnv) void { @@ -245,6 +254,17 @@ pub const RuntimeEnv = struct { self.interned_symbols.free(); } + pub fn get_dynamic( + self: *RuntimeEnv, + indexable: *const DynamicObject, + index_ref: *const RuntimeRef, + ) RuntimeError!?*RuntimeRef { + return indexable.typeinfo.get(.{ + .env = self, + .userdata = indexable.userdata, + }, index_ref); + } + 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"); @@ -258,14 +278,34 @@ pub const RuntimeEnv = struct { } pub fn push_boolean(self: *RuntimeEnv, boolean: bool) RuntimeError!void { + if (self.frames.is_empty()) { + return self.raise(error.IllegalState, "attempt to push boolean outside a call frame"); + } + return self.local_refs.push_one(try self.new_boolean(boolean)); } - pub fn push_number(self: *RuntimeEnv, number: Float) RuntimeError!void { - return self.local_refs.push_one(try self.new_number(number)); + pub fn push_fixed(self: *RuntimeEnv, fixed: Fixed) RuntimeError!void { + if (self.frames.is_empty()) { + return self.raise(error.IllegalState, "attempt to push fixed outside a call frame"); + } + + return self.local_refs.push_one(try self.new_fixed(fixed)); + } + + pub fn push_float(self: *RuntimeEnv, float: Float) RuntimeError!void { + if (self.frames.is_empty()) { + return self.raise(error.IllegalState, "attempt to push float outside a call frame"); + } + + return self.local_refs.push_one(try self.new_float(float)); } pub fn push_ref(self: *RuntimeEnv, ref: ?*RuntimeRef) RuntimeError!void { + if (self.frames.is_empty()) { + return self.raise(error.IllegalState, "attempt to push ref outside a call frame"); + } + return self.local_refs.push_one(if (ref) |live_ref| self.acquire(live_ref) else null); } @@ -282,8 +322,8 @@ pub const RuntimeEnv = struct { .local_refs = RefStack.make(allocator), .ref_values = RefSlab.make(allocator), .frames = FrameStack.make(allocator), - .syscallers = SyscallerTable.make(allocator), - .interned_symbols = SymbolTable.make(allocator), + .syscallers = SyscallerTable.make(allocator, .{}), + .interned_symbols = SymbolTable.make(allocator, .{}), .error_handler = error_handler, .allocator = allocator, }; @@ -318,10 +358,17 @@ pub const RuntimeEnv = struct { })); } - pub fn new_number(self: *RuntimeEnv, value: Float) RuntimeError!*RuntimeRef { + pub fn new_fixed(self: *RuntimeEnv, value: Fixed) RuntimeError!*RuntimeRef { return @ptrFromInt(try self.ref_values.insert(.{ .ref_count = 1, - .object = .{.number = value}, + .object = .{.fixed = value}, + })); + } + + pub fn new_float(self: *RuntimeEnv, value: Float) RuntimeError!*RuntimeRef { + return @ptrFromInt(try self.ref_values.insert(.{ + .ref_count = 1, + .object = .{.float = value}, })); } @@ -368,19 +415,16 @@ pub const RuntimeEnv = struct { return error_value; } - 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 syscallable(self: *RuntimeEnv, name: []const coral.io.Byte) RuntimeError!Caller { + return self.syscallers.lookup(name) orelse self.raise(error.BadOperation, "attempt to get undefined syscall"); } pub fn unbox(self: *RuntimeEnv, ref: *const RuntimeRef) Any { - const ref_data = self.ref_values.lookup(@intFromPtr(ref)); - - coral.debug.assert(ref_data != null); - - return switch (ref_data.?.object) { + return switch ((self.ref_values.lookup(@intFromPtr(ref)) orelse unreachable).object) { .false => .{.boolean = false}, .true => .{.boolean = true}, - .number => |number| .{.number = number}, + .fixed => |fixed| .{.fixed = fixed}, + .float => |float| .{.float = float}, .string => |string| .{.string = string}, .symbol => |symbol| .{.symbol = symbol}, .dynamic => |dynamic| .{.dynamic = dynamic}, @@ -409,156 +453,99 @@ pub const RuntimeRef = opaque {}; pub const Typeinfo = struct { name: []const coral.io.Byte, - call: *const fn (context: Context) RuntimeError!?*RuntimeRef = default_call, - clean: *const fn (context: Context) void = default_clean, - get: *const fn (context: Context, index: *const RuntimeRef) RuntimeError!?*RuntimeRef = default_get, - set: *const fn (context: Context, index: *const RuntimeRef, value: ?*const RuntimeRef) RuntimeError!void = default_set, - test_difference: *const fn (context: Context, other_ref: *const RuntimeRef) RuntimeError!Float = default_test_difference, - test_equality: *const fn (context: Context, other_ref: *const RuntimeRef) RuntimeError!bool = default_test_equality, + call: ?*const fn (method: Method) RuntimeError!?*RuntimeRef = null, + clean: *const fn (method: Method) void = default_clean, + get: *const fn (method: Method, index: *const RuntimeRef) RuntimeError!?*RuntimeRef = default_get, + set: *const fn (method: Method, index: *const RuntimeRef, value: ?*const RuntimeRef) RuntimeError!void = default_set, - fn default_call(context: Context) RuntimeError!?*RuntimeRef { - return context.env.raise(error.TypeMismatch, "object is not callable"); - } - - fn default_clean(_: Context) void { + fn default_clean(_: Method) void { // Nothing to clean by default. } - fn default_get(context: Context, _: *const RuntimeRef) RuntimeError!?*RuntimeRef { - return context.env.raise(error.TypeMismatch, "object is not indexable"); + fn default_get(method: Method, _: *const RuntimeRef) RuntimeError!?*RuntimeRef { + return method.env.raise(error.TypeMismatch, "object is not indexable"); } - fn default_set(context: Context, _: *const RuntimeRef, _: ?*const RuntimeRef) RuntimeError!void { - return context.env.raise(error.TypeMismatch, "object is not indexable"); - } - - fn default_test_difference(context: Context, _: *const RuntimeRef) RuntimeError!Float { - return context.env.raise(error.TypeMismatch, "object is not comparable"); - } - - fn default_test_equality(context: Context, other_ref: *const RuntimeRef) RuntimeError!bool { - return switch (context.env.unbox(other_ref)) { - .dynamic => |dynamic| context.userdata.ptr == dynamic.userdata.ptr, - else => false, - }; + fn default_set(method: Method, _: *const RuntimeRef, _: ?*const RuntimeRef) RuntimeError!void { + return method.env.raise(error.TypeMismatch, "object is not indexable"); } }; -pub fn call( - env: *RuntimeEnv, - caller_ref: ?*const RuntimeRef, - callable_ref: *const RuntimeRef, - arg_refs: []const ?*const RuntimeRef, -) 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 unbox_dynamic(env, indexable_ref); - - return dynamic.typeinfo.get(.{ - .userdata = dynamic.userdata, - .env = env, - }, 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); + const field_name_ref = try env.new_symbol(field_name); defer env.discard(field_name_ref); - return get(env, indexable_ref, field_name_ref); + return env.get_dynamic(try unbox_dynamic(env, indexable_ref), field_name_ref); } -pub fn set( - env: *RuntimeEnv, - indexable_ref: *const RuntimeRef, - index_ref: *const RuntimeRef, - value_ref: ?*const RuntimeRef, -) RuntimeError!void { - const dynamic = try unbox_dynamic(env, indexable_ref); - - 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 hash(env: *RuntimeEnv, ref: *const RuntimeRef) usize { + return switch (env.unbox(ref)) { + .boolean => 0, + .float => 0, + .fixed => 0, + .string => |string| coral.io.djb2_hash(@typeInfo(usize).Int, string), + .symbol => 0, + .dynamic => |dynamic| @intFromPtr(dynamic), + }; } 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)) { - .boolean => env.raise(error.TypeMismatch, "cannot compare boolean objects"), +pub fn test_difference( + env: *RuntimeEnv, + lhs_comparable_ref: *const RuntimeRef, + rhs_comparable_ref: *const RuntimeRef, +) RuntimeError!Float { + return switch (env.unbox(lhs_comparable_ref)) { + .boolean => env.raise(error.TypeMismatch, "boolean objects are not comparable"), - .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"), + .fixed => |lhs_fixed| switch (env.unbox(rhs_comparable_ref)) { + .fixed => |rhs_fixed| @as(Float, @floatFromInt(rhs_fixed)) - @as(Float, @floatFromInt(lhs_fixed)), + else => env.raise(error.TypeMismatch, "right-hand object is not comparable with fixed objects"), }, - .string => |lhs_string| switch (env.unbox(rhs_ref)) { + .float => |lhs_float| switch (env.unbox(rhs_comparable_ref)) { + .float => |rhs_float| rhs_float - lhs_float, + else => env.raise(error.TypeMismatch, "right-hand object is not comparable with float objects"), + }, + + .string => |lhs_string| switch (env.unbox(rhs_comparable_ref)) { .string => |rhs_string| @floatFromInt(coral.io.compare(lhs_string, rhs_string)), .symbol => |rhs_symbol| @floatFromInt(coral.io.compare(lhs_string, rhs_symbol)), else => env.raise(error.TypeMismatch, "right-hand object is not comparable with string objects"), }, - .symbol => |lhs_symbol| switch (env.unbox(rhs_ref)) { + .symbol => |lhs_symbol| switch (env.unbox(rhs_comparable_ref)) { .symbol => env.raise(error.TypeMismatch, "cannot compare symbol objects"), .string => |rhs_string| @floatFromInt(coral.io.compare(lhs_symbol, rhs_string)), else => env.raise(error.TypeMismatch, "right-hand object is not comparable with symbol objects"), }, - .dynamic => |lhs_dynamic| lhs_dynamic.typeinfo.test_difference(.{ - .env = env, - .userdata = lhs_dynamic.userdata, - }, rhs_ref), + .dynamic => env.raise(error.TypeMismatch, "dynamic objects are not comparable"), }; } -pub fn test_equality(env: *RuntimeEnv, lhs_ref: *const RuntimeRef, rhs_ref: *const RuntimeRef) RuntimeError!bool { +pub fn test_equality(env: *RuntimeEnv, lhs_ref: *const RuntimeRef, rhs_ref: *const RuntimeRef) bool { return switch (env.unbox(lhs_ref)) { .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, + .fixed => |lhs_fixed| switch (env.unbox(rhs_ref)) { + .fixed => |rhs_fixed| rhs_fixed == lhs_fixed, + .float => |rhs_float| rhs_float == @as(Float, @floatFromInt(lhs_fixed)), + else => false, + }, + + .float => |lhs_float| switch (env.unbox(rhs_ref)) { + .float => |rhs_float| rhs_float == lhs_float, + .fixed => |rhs_fixed| @as(Float, @floatFromInt(rhs_fixed)) == lhs_float, else => false, }, @@ -574,14 +561,14 @@ pub fn test_equality(env: *RuntimeEnv, lhs_ref: *const RuntimeRef, rhs_ref: *con else => false, }, - .dynamic => |lhs_dynamic| lhs_dynamic.typeinfo.test_equality(.{ - .env = env, - .userdata = lhs_dynamic.userdata, - }, rhs_ref), + .dynamic => |lhs_dynamic| switch (env.unbox(rhs_ref)) { + .dynamic => |rhs_dynamic| rhs_dynamic == lhs_dynamic, + else => false, + }, }; } -pub fn unbox_dynamic(env: *RuntimeEnv, ref: *const RuntimeRef) RuntimeError!*DynamicObject { +pub fn unbox_dynamic(env: *RuntimeEnv, ref: *const RuntimeRef) RuntimeError!*const DynamicObject { return env.unbox(ref).expect_dynamic() orelse env.raise(error.TypeMismatch, "expected dynamic object"); } diff --git a/source/ona/kym/Chunk.zig b/source/ona/kym/Chunk.zig index e0d91c4..095ab96 100644 --- a/source/ona/kym/Chunk.zig +++ b/source/ona/kym/Chunk.zig @@ -25,12 +25,26 @@ const AstCompiler = struct { .false_literal => try self.chunk.append_opcode(.push_false), .number_literal => |literal| { - const parsed_number = number_format.parse(literal, kym.Float); + for (literal) |codepoint| { + if (codepoint == '.') { + const parsed = number_format.parse(literal, kym.Float); - coral.debug.assert(parsed_number != null); + coral.debug.assert(parsed != null); + + try self.chunk.append_opcode(.{ + .push_const = try self.chunk.declare_constant_float(parsed.?), + }); + + return; + } + } + + const parsed = number_format.parse(literal, kym.Fixed); + + coral.debug.assert(parsed != null); try self.chunk.append_opcode(.{ - .push_const = try self.chunk.declare_constant_number(parsed_number.?), + .push_const = try self.chunk.declare_constant_fixed(parsed.?), }); }, @@ -222,14 +236,30 @@ pub fn compile_ast(self: *Self, ast: Ast) kym.RuntimeError!void { try self.opcodes.pack(); } -pub fn declare_constant_number(self: *Self, constant: kym.Float) kym.RuntimeError!u16 { +pub fn declare_constant_fixed(self: *Self, constant: kym.Fixed) kym.RuntimeError!u16 { 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"); } - const constant_ref = try self.env.new_number(constant); + const constant_ref = try self.env.new_fixed(constant); + + errdefer self.env.discard(constant_ref); + + try self.constant_refs.push_one(constant_ref); + + return @intCast(tail); +} + +pub fn declare_constant_float(self: *Self, constant: kym.Float) kym.RuntimeError!u16 { + 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"); + } + + const constant_ref = try self.env.new_float(constant); errdefer self.env.discard(constant_ref); @@ -333,7 +363,7 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef defer env.discard(callable_ref); - break: call try env.call("", arg_count, (try kym.unbox_dynamic(env, callable_ref)).as_caller()); + break: call try env.call(try env.callable(callable_ref), arg_count, ""); }; defer env.discard(result_ref); @@ -351,7 +381,7 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef const identifier = try kym.unbox_string(env, identifier_ref); - break: call try env.call(identifier, arg_count, try env.syscaller(identifier)); + break: call try env.call(try env.syscallable(identifier), arg_count, identifier); }; defer env.discard(result_ref); @@ -366,10 +396,11 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef defer env.discard(ref); - try env.push_number(switch (env.unbox(ref)) { - .number => |number| -number, + switch (env.unbox(ref)) { + .fixed => |fixed| try env.push_fixed(-fixed), + .float => |float| try env.push_float(-float), else => return env.raise(error.TypeMismatch, "object is not scalar negatable"), - }); + } }, .not => { @@ -398,14 +429,14 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef defer env.discard(lhs_ref); - 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"), + try switch (env.unbox(lhs_ref)) { + .float => |lhs_float| switch (env.unbox(rhs_ref)) { + .float => |rhs_float| env.push_float(lhs_float + rhs_float), + else => env.raise(error.TypeMismatch, "right-hand object is not addable"), }, - else => return env.raise(error.TypeMismatch, "left-hand object is not addable"), - }); + else => env.raise(error.TypeMismatch, "left-hand object is not addable"), + }; }, .sub => { @@ -421,14 +452,14 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef defer env.discard(lhs_ref); - 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"), + try switch (env.unbox(lhs_ref)) { + .float => |lhs_float| switch (env.unbox(rhs_ref)) { + .float => |rhs_float| env.push_float(lhs_float - rhs_float), + else => env.raise(error.TypeMismatch, "right-hand object is not subtractable"), }, - else => return env.raise(error.TypeMismatch, "left-hand object is not subtractable"), - }); + else => env.raise(error.TypeMismatch, "left-hand object is not subtractable"), + }; }, .mul => { @@ -444,14 +475,14 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef defer env.discard(lhs_ref); - 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"), + try switch (env.unbox(lhs_ref)) { + .float => |lhs_float| switch (env.unbox(rhs_ref)) { + .float => |rhs_float| env.push_float(lhs_float * rhs_float), + else => env.raise(error.TypeMismatch, "right-hand object is not multipliable"), }, - else => return env.raise(error.TypeMismatch, "left-hand object is not multiplyable"), - }); + else => env.raise(error.TypeMismatch, "left-hand object is not multipliable"), + }; }, .div => { @@ -467,14 +498,14 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef defer env.discard(lhs_ref); - 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"), + try switch (env.unbox(lhs_ref)) { + .float => |lhs_float| switch (env.unbox(rhs_ref)) { + .float => |rhs_float| env.push_float(lhs_float / rhs_float), + else => env.raise(error.TypeMismatch, "right-hand object is not divisible"), }, - else => return env.raise(error.TypeMismatch, "left-hand object is not divisable"), - }); + else => env.raise(error.TypeMismatch, "left-hand object is not divisible"), + }; }, .eql => { @@ -490,7 +521,7 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef defer env.discard(lhs_ref); - try env.push_boolean(try kym.test_equality(env, lhs_ref, rhs_ref)); + try env.push_boolean(kym.test_equality(env, lhs_ref, rhs_ref)); }, .cgt => { diff --git a/source/ona/kym/Table.zig b/source/ona/kym/Table.zig index 75eda12..28ab78b 100644 --- a/source/ona/kym/Table.zig +++ b/source/ona/kym/Table.zig @@ -5,34 +5,28 @@ const kym = @import("../kym.zig"); associative: AssociativeTable, contiguous: ContiguousList, -// TODO: Modify hash traits to be support fat contexts rather than passing Envs into the table. -const AssociativeTable = coral.map.Table(Field, *kym.RuntimeRef, .{ - .hash = Field.hash, - .match = Field.match, +const AssociativeTable = coral.map.Table(*kym.RuntimeRef, *kym.RuntimeRef, struct { + env: *kym.RuntimeEnv, + + pub fn hash(self: @This(), key_ref: *kym.RuntimeRef) usize { + return kym.hash(self.env, key_ref); + } + + pub fn equals(self: @This(), key_ref_a: *kym.RuntimeRef, key_ref_b: *kym.RuntimeRef) bool { + return kym.test_equality(self.env, key_ref_a, key_ref_b); + } }); const ContiguousList = coral.list.Stack(?*kym.RuntimeRef); -const Field = struct { - env: *kym.RuntimeEnv, - ref: *kym.RuntimeRef, - - fn hash(field: Field) usize { - return switch (field.env.unbox(field.ref)) { - - }; - } - - fn match(self: Field, other: Field) bool { - return kym.test_equality(self.env, self.ref, other.ref); - } -}; - const Self = @This(); pub fn new(env: *kym.RuntimeEnv) kym.RuntimeError!*kym.RuntimeRef { var self = Self{ - .associative = AssociativeTable.make(env.allocator), + .associative = AssociativeTable.make(env.allocator, .{ + .env = env, + }), + .contiguous = ContiguousList.make(env.allocator), }; @@ -51,109 +45,139 @@ const typeinfo = kym.Typeinfo{ .set = typeinfo_set, }; -fn typeinfo_clean(context: kym.Context) void { - const table = @as(*Self, @ptrCast(@alignCast(context.userdata.ptr))); +fn typeinfo_clean(method: kym.Method) void { + const table = @as(*Self, @ptrCast(@alignCast(method.userdata.ptr))); { var field_iterable = table.associative.as_iterable(); while (field_iterable.next()) |entry| { - context.env.discard(entry.key.ref); - context.env.discard(entry.value); + method.env.discard(entry.key); + method.env.discard(entry.value); } } table.associative.free(); while (table.contiguous.pop()) |ref| { - context.env.discard(ref); + method.env.discard(ref); } table.contiguous.free(); } -fn typeinfo_get(context: kym.Context, index_ref: *const kym.RuntimeRef) kym.RuntimeError!?*kym.RuntimeRef { - const table = @as(*Self, @ptrCast(@alignCast(context.userdata.ptr))); +fn typeinfo_get(method: kym.Method, index_ref: *const kym.RuntimeRef) kym.RuntimeError!?*kym.RuntimeRef { + const table = @as(*Self, @ptrCast(@alignCast(method.userdata.ptr))); - switch (context.env.unbox(index_ref)) { - .symbol, .string => { - return context.env.acquire(table.associative.lookup(index_ref)); + switch (method.env.unbox(index_ref)) { + .symbol, .string, .float => { + const mutable_index_ref = method.env.acquire(index_ref); + + defer method.env.discard(mutable_index_ref); + + if (table.associative.lookup(mutable_index_ref)) |value_ref| { + return method.env.acquire(value_ref); + } }, - .number => |number| { - // TODO: Implement dedicated integer type within VM internals for this sort of indexing. - if (@trunc(number) == number) { - const index = @as(usize, @intFromFloat(number)); - - if (index < table.contiguous.values.len) { - return context.env.acquire(table.contiguous.values[index] orelse return null); - } + .fixed => |fixed| { + if (fixed < 0) { + // TODO: Negative indexing. + unreachable; } - return context.env.acquire(table.associative.lookup(.{ - .env = context.env, - .ref = index_ref, - })); + if (fixed < table.contiguous.values.len) { + return method.env.acquire(table.contiguous.values[@intCast(fixed)] orelse return null); + } + + const mutable_index_ref = method.env.acquire(index_ref); + + defer method.env.discard(mutable_index_ref); + + if (table.associative.lookup(mutable_index_ref)) |value_ref| { + return method.env.acquire(value_ref); + } }, - else => return context.env.raise(error.TypeMismatch, "expected symbol, string, or number index"), + else => return method.env.raise(error.TypeMismatch, "expected symbol, string, or number index"), } + + return null; } -fn typeinfo_set(context: kym.Context, index_ref: *const kym.RuntimeRef, value_ref: ?*const kym.RuntimeRef) kym.RuntimeError!void { - const table = @as(*Self, @ptrCast(@alignCast(context.userdata.ptr))); +fn typeinfo_set(method: kym.Method, index_ref: *const kym.RuntimeRef, value_ref: ?*const kym.RuntimeRef) kym.RuntimeError!void { + const table = @as(*Self, @ptrCast(@alignCast(method.userdata.ptr))); - switch (context.env.unbox(context.index_ref)) { - .symbol, .string => { - const acquired_index_ref = context.env.acquire(index_ref); + switch (method.env.unbox(index_ref)) { + .symbol, .string, .float => { + const acquired_index_ref = method.env.acquire(index_ref); - errdefer context.env.discard(acquired_index_ref); + errdefer method.env.discard(acquired_index_ref); - const acquired_value_ref = context.env.acquire(value_ref); + if (value_ref) |ref| { + const acquired_ref = method.env.acquire(ref); - errdefer context.env.discard(acquired_value_ref); + errdefer method.env.discard(acquired_ref); - if (try table.associative.replace(acquired_index_ref, acquired_value_ref)) |replaced| { - context.env.discard(replaced.key); - context.env.discard(replaced.value); + if (try table.associative.replace(acquired_index_ref, acquired_ref)) |replaced| { + method.env.discard(replaced.key); + method.env.discard(replaced.value); + } + } else if (table.associative.remove(acquired_index_ref)) |removed| { + method.env.discard(removed.key); + method.env.discard(removed.value); } }, - .number => |number| { - const index = coral.math.clamped_cast(@typeInfo(usize), number); - - if (index == number) { - if (index < table.contiguous.values.len) { - table.contiguous.values[index] = context.env.acquire(value_ref); - - return; - } - - if (index == table.contiguous.values.len) { - const acquired_index_ref = context.env.acquire(value_ref); - - errdefer context.env.discard(acquired_index_ref); - - try table.contiguous.push_one(acquired_index_ref); - - return; - } + .fixed => |fixed| { + if (fixed < 0) { + // TODO: Negative indexing. + unreachable; } - const acquired_index_ref = context.env.acquire(index_ref); + if (fixed < table.contiguous.values.len) { + const contiguous_value = &table.contiguous.values[@intCast(fixed)]; - errdefer context.env.discard(index_ref); + method.env.discard(contiguous_value.*); - const acquired_value_ref = context.env.acquire(value_ref); + contiguous_value.* = if (value_ref) |ref| method.env.acquire(ref) else null; - errdefer context.env.discard(value_ref); + return; + } - if (try table.associative.replace(acquired_index_ref, acquired_value_ref)) |replaced| { - context.env.discard(replaced.key); - context.env.discard(replaced.value); + if (fixed == table.contiguous.values.len) { + if (value_ref) |ref| { + const acquired_ref = method.env.acquire(ref); + + errdefer method.env.discard(acquired_ref); + + try table.contiguous.push_one(acquired_ref); + } else { + try table.contiguous.push_one(null); + } + + return; + } + + const acquired_index_ref = method.env.acquire(index_ref); + + errdefer method.env.discard(acquired_index_ref); + + if (value_ref) |ref| { + const acquired_ref = method.env.acquire(ref); + + errdefer method.env.discard(acquired_ref); + + if (try table.associative.replace(acquired_index_ref, acquired_ref)) |replaced| { + method.env.discard(replaced.key); + method.env.discard(replaced.value); + } + } else if (table.associative.remove(acquired_index_ref)) |removed| { + method.env.discard(removed.key); + method.env.discard(removed.value); } }, - else => return context.env.raise(error.TypeMismatch, "expected symbol, string, or number index"), + else => return method.env.raise(error.TypeMismatch, "expected symbol, string, or number index"), } } -- 2.34.1 From a62588dfe46b70450ea142373358ce74886a45b7 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 30 Jul 2023 14:33:41 +0100 Subject: [PATCH 04/12] Fix compilation error in speed and size-optimized release builds --- source/coral/io.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/coral/io.zig b/source/coral/io.zig index 6410072..8da89fe 100644 --- a/source/coral/io.zig +++ b/source/coral/io.zig @@ -29,7 +29,7 @@ pub const Allocator = struct { const ErasedActions = struct { fn deallocate(context: *anyopaque, allocation: []Byte) void { if (is_zero_aligned) { - return actions.deallocator(@ptrCast(context), allocation); + return actions.deallocate(@ptrCast(context), allocation); } return actions.deallocate(@ptrCast(@alignCast(context)), allocation); @@ -37,7 +37,7 @@ pub const Allocator = struct { fn reallocate(context: *anyopaque, return_address: usize, existing_allocation: ?[]Byte, size: usize) AllocationError![]Byte { if (is_zero_aligned) { - return actions.reallocator(@ptrCast(context), return_address, existing_allocation, size); + return actions.reallocate(@ptrCast(context), return_address, existing_allocation, size); } return actions.reallocate(@ptrCast(@alignCast(context)), return_address, existing_allocation, size); -- 2.34.1 From b6f7ab1edb73508ed826661f8e00baa3b47e85dd Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 30 Jul 2023 14:34:19 +0100 Subject: [PATCH 05/12] Add support for field get access of objects --- debug/app.ona | 9 +- source/ona/kym.zig | 46 ++-- source/ona/kym/Ast.zig | 459 +++++++++++++++++++++----------------- source/ona/kym/Chunk.zig | 71 +++++- source/ona/kym/tokens.zig | 24 +- 5 files changed, 351 insertions(+), 258 deletions(-) diff --git a/debug/app.ona b/debug/app.ona index c66b16b..c445d84 100644 --- a/debug/app.ona +++ b/debug/app.ona @@ -1,6 +1,13 @@ # Test comment. -@log_info("game is loading") + +test = { + .message = "don't you lecture me with your 30 dollar scripting language" +} + +# test.message = "game is loading" + +@log_info(test.message) return { .title = "Afterglow", diff --git a/source/ona/kym.zig b/source/ona/kym.zig index 3e054db..79f4af8 100644 --- a/source/ona/kym.zig +++ b/source/ona/kym.zig @@ -143,19 +143,13 @@ pub const RuntimeEnv = struct { }); defer { - const frame = self.frames.pop(); - - coral.debug.assert(frame != null); + const frame = self.frames.pop() orelse unreachable; { - var pops_remaining = (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.?); + self.discard(self.local_refs.pop() orelse unreachable); } } } @@ -278,34 +272,18 @@ pub const RuntimeEnv = struct { } pub fn push_boolean(self: *RuntimeEnv, boolean: bool) RuntimeError!void { - if (self.frames.is_empty()) { - return self.raise(error.IllegalState, "attempt to push boolean outside a call frame"); - } - return self.local_refs.push_one(try self.new_boolean(boolean)); } pub fn push_fixed(self: *RuntimeEnv, fixed: Fixed) RuntimeError!void { - if (self.frames.is_empty()) { - return self.raise(error.IllegalState, "attempt to push fixed outside a call frame"); - } - return self.local_refs.push_one(try self.new_fixed(fixed)); } pub fn push_float(self: *RuntimeEnv, float: Float) RuntimeError!void { - if (self.frames.is_empty()) { - return self.raise(error.IllegalState, "attempt to push float outside a call frame"); - } - return self.local_refs.push_one(try self.new_float(float)); } pub fn push_ref(self: *RuntimeEnv, ref: ?*RuntimeRef) RuntimeError!void { - if (self.frames.is_empty()) { - return self.raise(error.IllegalState, "attempt to push ref outside a call frame"); - } - return self.local_refs.push_one(if (ref) |live_ref| self.acquire(live_ref) else null); } @@ -415,6 +393,18 @@ pub const RuntimeEnv = struct { return error_value; } + pub fn set_dynamic( + self: *RuntimeEnv, + indexable: *const DynamicObject, + index_ref: *const RuntimeRef, + value_ref: ?*const RuntimeRef, + ) RuntimeError!void { + return indexable.typeinfo.set(.{ + .env = self, + .userdata = indexable.userdata, + }, index_ref, value_ref); + } + pub fn syscallable(self: *RuntimeEnv, name: []const coral.io.Byte) RuntimeError!Caller { return self.syscallers.lookup(name) orelse self.raise(error.BadOperation, "attempt to get undefined syscall"); } @@ -453,11 +443,15 @@ pub const RuntimeRef = opaque {}; pub const Typeinfo = struct { name: []const coral.io.Byte, - call: ?*const fn (method: Method) RuntimeError!?*RuntimeRef = null, + call: *const fn (method: Method) RuntimeError!?*RuntimeRef = default_call, clean: *const fn (method: Method) void = default_clean, get: *const fn (method: Method, index: *const RuntimeRef) RuntimeError!?*RuntimeRef = default_get, set: *const fn (method: Method, index: *const RuntimeRef, value: ?*const RuntimeRef) RuntimeError!void = default_set, + fn default_call(method: Method) RuntimeError!?*RuntimeRef { + return method.env.raise(error.TypeMismatch, "object is not callable"); + } + fn default_clean(_: Method) void { // Nothing to clean by default. } diff --git a/source/ona/kym/Ast.zig b/source/ona/kym/Ast.zig index bcc7c4c..74f0688 100755 --- a/source/ona/kym/Ast.zig +++ b/source/ona/kym/Ast.zig @@ -18,7 +18,18 @@ pub const Expression = union (enum) { symbol_literal: []const coral.io.Byte, table_literal: TableLiteral, grouped_expression: *Expression, - get_local: []const coral.io.Byte, + resolve_local: []const coral.io.Byte, + + get_field: struct { + object_expression: *Expression, + identifier: []const coral.io.Byte, + }, + + set_field: struct { + object_expression: *Expression, + identifier: []const coral.io.Byte, + value_expression: *Expression, + }, call_system: struct { identifier: []const coral.io.Byte, @@ -80,10 +91,10 @@ pub const ParseError = error { const Self = @This(); pub const Statement = union (enum) { - return_expression: Expression, return_nothing, + return_expression: Expression, - set_local: struct { + assign_local: struct { identifier: []const coral.io.Byte, expression: Expression, }, @@ -113,10 +124,10 @@ fn binary_operation_parser( inline for (operators) |operator| { const token = comptime operator.token(); - if (self.tokenizer.is_token(coral.io.tag_of(token))) { + if (self.tokenizer.token == coral.io.tag_of(token)) { self.tokenizer.step(); - if (self.tokenizer.token == null) { + if (self.tokenizer.token == .end) { return self.report("expected other half of expression after `" ++ comptime token.text() ++ "`"); } @@ -178,10 +189,12 @@ pub fn parse(self: *Self, data: []const coral.io.Byte) ParseError!void { const allocator = self.arena.as_allocator(); var has_returned = false; - while (true) { - self.tokenizer.skip(.newline); + self.tokenizer.skip_newlines(); + + while (true) { + switch (self.tokenizer.token) { + .end => return, - switch (self.tokenizer.token orelse return) { .keyword_return => { if (has_returned) { return self.report("multiple returns in function scope but expected only one"); @@ -190,12 +203,12 @@ pub fn parse(self: *Self, data: []const coral.io.Byte) ParseError!void { try self.statements.push_one(get_statement: { self.tokenizer.step(); - if (!self.tokenizer.is_token_null_or(.newline)) { + if (self.tokenizer.token != .end and self.tokenizer.token != .newline) { break: get_statement .{.return_expression = try self.parse_expression()}; } - if (!self.tokenizer.is_token_null_or(.newline)) { - return self.report("unexpected token after return"); + if (self.tokenizer.token != .end and self.tokenizer.token != .newline) { + return self.report("expected end or newline after return statement"); } break: get_statement .return_nothing; @@ -207,28 +220,22 @@ pub fn parse(self: *Self, data: []const coral.io.Byte) ParseError!void { .identifier => |identifier| { self.tokenizer.step(); - const no_effect_message = "statement has no effect"; - - switch (self.tokenizer.token orelse return self.report(no_effect_message)) { - .newline => return self.report(no_effect_message), + switch (self.tokenizer.token) { + .end, .newline => return self.report("statement has no effect"), .symbol_equals => { self.tokenizer.step(); - if (self.tokenizer.token == null) { + if (self.tokenizer.token == .end) { return self.report("expected expression after `=`"); } try self.statements.push_one(.{ - .set_local = .{ + .assign_local = .{ .expression = try self.parse_expression(), .identifier = identifier, }, }); - - if (!self.tokenizer.is_token_null_or(.newline)) { - return self.report("unexpected token after assignment"); - } }, else => return self.report("expected `=` after local"), @@ -238,10 +245,8 @@ pub fn parse(self: *Self, data: []const coral.io.Byte) ParseError!void { .special_identifier => |identifier| { self.tokenizer.step(); - const missing_arguments_message = "system call is missing arguments"; - - switch (self.tokenizer.token orelse return self.report(missing_arguments_message)) { - .newline => return self.report(missing_arguments_message), + switch (self.tokenizer.token) { + .end, .newline => return self.report("system call is missing arguments"), .symbol_paren_left => { self.tokenizer.step(); @@ -249,20 +254,20 @@ pub fn parse(self: *Self, data: []const coral.io.Byte) ParseError!void { var expressions_list = Expression.List.make(allocator); while (true) { - if (self.tokenizer.is_token(.symbol_paren_right)) { + if (self.tokenizer.token == .symbol_paren_right) { break; } try expressions_list.push_one(try self.parse_expression()); - switch (self.tokenizer.token orelse return self.report("unexpected end after after `(`")) { + switch (self.tokenizer.token) { .symbol_comma => continue, .symbol_paren_right => break, else => return self.report("expected `)` or argument after `(`"), } } - self.tokenizer.step(); + self.tokenizer.skip_newlines(); try self.statements.push_one(.{ .call_system = .{ @@ -300,200 +305,234 @@ const parse_expression = binary_operation_parser(parse_equality, &.{ fn parse_factor(self: *Self) ParseError!Expression { const allocator = self.arena.as_allocator(); - switch (self.tokenizer.token orelse return self.report("expected operand after operator")) { - .symbol_paren_left => { - self.tokenizer.skip(.newline); + var expression = @as(Expression, get: { + switch (self.tokenizer.token) { + .symbol_paren_left => { + self.tokenizer.skip_newlines(); - if (self.tokenizer.token == null) { - return self.report("expected an expression after `(`"); - } - - const expression = try self.parse_expression(); - - if (!self.tokenizer.is_token(.symbol_paren_right)) { - return self.report("expected a closing `)` after expression"); - } - - self.tokenizer.step(); - - return Expression{.grouped_expression = try coral.io.allocate_one(allocator, expression)}; - }, - - .keyword_nil => { - self.tokenizer.step(); - - return .nil_literal; - }, - - .keyword_true => { - self.tokenizer.step(); - - return .true_literal; - }, - - .keyword_false => { - self.tokenizer.step(); - - return .false_literal; - }, - - .number => |value| { - self.tokenizer.step(); - - return Expression{.number_literal = value}; - }, - - .string => |value| { - self.tokenizer.step(); - - return Expression{.string_literal = value}; - }, - - .special_identifier => |identifier| { - self.tokenizer.skip(.newline); - - var expression_list = Expression.List.make(allocator); - - while (true) { - switch (self.tokenizer.token orelse return self.report("expected expression or `)` after `(`")) { - .symbol_paren_right => { - self.tokenizer.step(); - - return Expression{ - .call_system = .{ - .identifier = identifier, - .argument_expressions = expression_list, - }, - }; - }, - - else => { - try expression_list.push_one(try self.parse_expression()); - - switch (self.tokenizer.token orelse return self.report("expected `,` or `)` after argument")) { - .symbol_comma => continue, - - .symbol_paren_right => { - self.tokenizer.step(); - - return Expression{ - .call_system = .{ - .identifier = identifier, - .argument_expressions = expression_list, - }, - }; - }, - - else => return self.report("expected `,` or `)` after argument"), - } - }, + if (self.tokenizer.token == .end) { + return self.report("expected an expression after `(`"); } - } - }, - .identifier => |identifier| { - self.tokenizer.step(); + const expression = try self.parse_expression(); - return Expression{.get_local = identifier}; - }, - - .symbol_brace_left => { - var table_literal = Expression.TableLiteral.make(allocator); - - self.tokenizer.skip(.newline); - - while (true) { - switch (self.tokenizer.token orelse return self.report("unexpected end of table literal")) { - .symbol_brace_right => { - self.tokenizer.step(); - - return Expression{.table_literal = table_literal}; - }, - - .symbol_bracket_left => { - self.tokenizer.skip(.newline); - - if (!self.tokenizer.is_token(.symbol_equals)) { - return self.report("expected expression after identifier"); - } - }, - - .symbol_period => { - self.tokenizer.step(); - - if (!self.tokenizer.is_token(.identifier)) { - return self.report("expected identifier after `.`"); - } - - const identifier = self.tokenizer.token.?.identifier; - - self.tokenizer.skip(.newline); - - if (!self.tokenizer.is_token(.symbol_equals)) { - return self.report("expected `=` after key"); - } - - self.tokenizer.skip(.newline); - - if (self.tokenizer.token == null) { - return self.report("unexpected end after `=`"); - } - - try table_literal.push_one(.{ - .value_expression = try self.parse_expression(), - .key_expression = .{.symbol_literal = identifier}, - }); - - switch (self.tokenizer.token orelse return self.report("unexpected end of table")) { - .symbol_comma => self.tokenizer.skip(.newline), - - .symbol_brace_right => { - self.tokenizer.step(); - - return Expression{.table_literal = table_literal}; - }, - - else => return self.report("expected `,` or `}` after expression"), - } - }, - - else => return self.report("expected `}` or fields in table literal"), + if (self.tokenizer.token != .symbol_paren_right) { + return self.report("expected a closing `)` after expression"); } - } - }, - .symbol_minus => { - self.tokenizer.skip(.newline); + self.tokenizer.skip_newlines(); - if (self.tokenizer.token == null) { - return self.report("expected expression after numeric negation (`-`)"); - } + break: get Expression{.grouped_expression = try coral.io.allocate_one(allocator, expression)}; + }, - return Expression{ - .unary_operation = .{ - .expression = try coral.io.allocate_one(allocator, try self.parse_factor()), - .operator = .numeric_negation, + .keyword_nil => { + self.tokenizer.skip_newlines(); + + break: get .nil_literal; + }, + + .keyword_true => { + self.tokenizer.skip_newlines(); + + break: get .true_literal; + }, + + .keyword_false => { + self.tokenizer.skip_newlines(); + + break: get .false_literal; + }, + + .number => |value| { + self.tokenizer.skip_newlines(); + + break: get .{.number_literal = value}; + }, + + .string => |value| { + self.tokenizer.skip_newlines(); + + break: get .{.string_literal = value}; + }, + + .special_identifier => |identifier| { + self.tokenizer.skip_newlines(); + + var expression_list = Expression.List.make(allocator); + + while (true) { + switch (self.tokenizer.token) { + .end => return self.report("expected expression or `)` after `(`"), + + .symbol_paren_right => { + self.tokenizer.skip_newlines(); + + break: get .{ + .call_system = .{ + .identifier = identifier, + .argument_expressions = expression_list, + }, + }; + }, + + else => { + try expression_list.push_one(try self.parse_expression()); + + switch (self.tokenizer.token) { + .end => return self.report("expected `,` or `)` after argument"), + .symbol_comma => continue, + + .symbol_paren_right => { + self.tokenizer.skip_newlines(); + + break: get .{ + .call_system = .{ + .identifier = identifier, + .argument_expressions = expression_list, + }, + }; + }, + + else => return self.report("expected `,` or `)` after argument"), + } + }, + } + } + }, + + .identifier => |identifier| { + self.tokenizer.skip_newlines(); + + break: get .{.resolve_local = identifier}; + }, + + .symbol_brace_left => { + var table_literal = Expression.TableLiteral.make(allocator); + + self.tokenizer.skip_newlines(); + + while (true) { + switch (self.tokenizer.token) { + .symbol_brace_right => { + self.tokenizer.skip_newlines(); + + break: get .{.table_literal = table_literal}; + }, + + .symbol_bracket_left => { + self.tokenizer.skip_newlines(); + + if (self.tokenizer.token != .symbol_equals) { + return self.report("expected expression after identifier"); + } + }, + + .symbol_period => { + self.tokenizer.step(); + + const identifier = switch (self.tokenizer.token) { + .identifier => |identifier| identifier, + else => return self.report("expected identifier after `.`"), + }; + + self.tokenizer.skip_newlines(); + + if (self.tokenizer.token != .symbol_equals) { + return self.report("expected `=` after key"); + } + + self.tokenizer.skip_newlines(); + + if (self.tokenizer.token == .end) { + return self.report("unexpected end after `=`"); + } + + try table_literal.push_one(.{ + .value_expression = try self.parse_expression(), + .key_expression = .{.symbol_literal = identifier}, + }); + + switch (self.tokenizer.token) { + .symbol_comma => self.tokenizer.skip_newlines(), + + .symbol_brace_right => { + self.tokenizer.skip_newlines(); + + break: get .{.table_literal = table_literal}; + }, + + else => return self.report("expected `,` or `}` after expression"), + } + }, + + else => return self.report("expected `}` or fields in table literal"), + } + } + }, + + .symbol_minus => { + self.tokenizer.skip_newlines(); + + if (self.tokenizer.token == .end) { + return self.report("expected expression after numeric negation (`-`)"); + } + + break: get .{ + .unary_operation = .{ + .expression = try coral.io.allocate_one(allocator, try self.parse_factor()), + .operator = .numeric_negation, + }, + }; + }, + + .symbol_bang => { + self.tokenizer.skip_newlines(); + + if (self.tokenizer.token == .end) { + return self.report("expected expression after boolean negation (`!`)"); + } + + break: get .{ + .unary_operation = .{ + .expression = try coral.io.allocate_one(allocator, try self.parse_factor()), + .operator = .boolean_negation, + }, + }; + }, + + else => return self.report("unexpected token in expression"), + } + }); + + while (self.tokenizer.token == .symbol_period) { + self.tokenizer.skip_newlines(); + + const identifier = switch (self.tokenizer.token) { + .identifier => |identifier| identifier, + else => return self.report("expected identifier after `.`"), + }; + + self.tokenizer.skip_newlines(); + + expression = switch (self.tokenizer.token) { + .symbol_equals => .{ + .set_field = .{ + .value_expression = try coral.io.allocate_one(allocator, try self.parse_expression()), + .object_expression = try coral.io.allocate_one(allocator, expression), + .identifier = identifier, }, - }; - }, + }, - .symbol_bang => { - self.tokenizer.skip(.newline); - - if (self.tokenizer.token == null) { - return self.report("expected expression after boolean negation (`!`)"); - } - - return Expression{ - .unary_operation = .{ - .expression = try coral.io.allocate_one(allocator, try self.parse_factor()), - .operator = .boolean_negation, + else => .{ + .get_field = .{ + .object_expression = try coral.io.allocate_one(allocator, expression), + .identifier = identifier, }, - }; - }, - - else => return self.report("unexpected token in expression"), + }, + }; } + + return expression; } const parse_term = binary_operation_parser(parse_factor, &.{ diff --git a/source/ona/kym/Chunk.zig b/source/ona/kym/Chunk.zig index 095ab96..0c115db 100644 --- a/source/ona/kym/Chunk.zig +++ b/source/ona/kym/Chunk.zig @@ -99,12 +99,35 @@ const AstCompiler = struct { try self.compile_expression(grouped_expression.*); }, - .get_local => |local| { + .resolve_local => |local| { try self.chunk.append_opcode(.{ - .push_local = 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"); + }, }); }, + .get_field => |get_field| { + try self.compile_expression(get_field.object_expression.*); + + try self.chunk.append_opcode(.{ + .push_const = try self.chunk.declare_constant_symbol(get_field.identifier), + }); + + try self.chunk.append_opcode(.get_dynamic); + }, + + .set_field => |set_field| { + try self.compile_expression(set_field.object_expression.*); + + try self.chunk.append_opcode(.{ + .push_const = try self.chunk.declare_constant_symbol(set_field.identifier), + }); + + try self.compile_expression(set_field.value_expression.*); + try self.chunk.append_opcode(.set_dynamic); + }, + .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"); @@ -126,7 +149,7 @@ const AstCompiler = struct { .return_expression => |return_expression| try self.compile_expression(return_expression), .return_nothing => try self.chunk.append_opcode(.push_nil), - .set_local => |local| { + .assign_local => |local| { try self.compile_expression(local.expression); if (self.resolve_local(local.identifier)) |index| { @@ -193,6 +216,8 @@ pub const Opcode = union (enum) { push_local: u8, push_table: u32, set_local: u8, + get_dynamic, + set_dynamic, call: u8, syscall: u8, @@ -355,6 +380,46 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef try env.set_local(local, ref); }, + .get_dynamic => { + const index_ref = try env.pop_local() orelse { + return env.raise(error.TypeMismatch, "nil is not a valid index"); + }; + + defer env.discard(index_ref); + + const indexable_ref = try env.pop_local() orelse { + return env.raise(error.TypeMismatch, "nil is not a valid indexable"); + }; + + defer env.discard(indexable_ref); + + const value_ref = try env.get_dynamic(try kym.unbox_dynamic(env, indexable_ref), index_ref); + + defer env.discard(value_ref); + + try env.push_ref(value_ref); + }, + + .set_dynamic => { + const index_ref = try env.pop_local() orelse { + return env.raise(error.TypeMismatch, "nil is not a valid index"); + }; + + defer env.discard(index_ref); + + const indexable_ref = try env.pop_local() orelse { + return env.raise(error.TypeMismatch, "nil is not a valid indexable"); + }; + + defer env.discard(indexable_ref); + + const value_ref = try env.pop_local(); + + defer env.discard(value_ref); + + try env.set_dynamic(try kym.unbox_dynamic(env, indexable_ref), index_ref, value_ref); + }, + .call => |arg_count| { const result_ref = call: { const callable_ref = try env.pop_local() orelse { diff --git a/source/ona/kym/tokens.zig b/source/ona/kym/tokens.zig index 76fac23..7aa0e61 100755 --- a/source/ona/kym/tokens.zig +++ b/source/ona/kym/tokens.zig @@ -1,6 +1,7 @@ const coral = @import("coral"); pub const Token = union(enum) { + end, unknown: coral.io.Byte, newline, @@ -41,6 +42,7 @@ pub const Token = union(enum) { pub fn text(self: Token) []const coral.io.Byte { return switch (self) { + .end => "end", .unknown => |unknown| @as([*]const coral.io.Byte, @ptrCast(&unknown))[0 .. 1], .newline => "newline", @@ -85,26 +87,12 @@ pub const Token = union(enum) { pub const Tokenizer = struct { source: []const coral.io.Byte, lines_stepped: usize = 1, - token: ?Token = null, + token: Token = .end, - const TokenTag = coral.io.Tag(Token); - - pub fn is_token(self: *Tokenizer, token_tag: TokenTag) bool { - return if (self.token) |token| token == token_tag else false; - } - - pub fn is_token_null_or(self: *Tokenizer, token_tag: TokenTag) bool { - return if (self.token) |token| token == token_tag else true; - } - - pub fn skip(self: *Tokenizer, skip_token_tag: TokenTag) void { + pub fn skip_newlines(self: *Tokenizer) void { self.step(); - while (self.token) |token| { - if (token != skip_token_tag) { - return; - } - + while (self.token == .newline) { self.step(); } } @@ -429,7 +417,7 @@ pub const Tokenizer = struct { } } - self.token = null; + self.token = .end; return; } -- 2.34.1 From 3173dd52febff141ec41dfa38d9f962407241641 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 30 Jul 2023 23:18:43 +0100 Subject: [PATCH 06/12] Consolidate variable operations into expressions --- debug/app.ona | 2 +- source/ona/kym/Ast.zig | 539 ++++++++++++++++----------------------- source/ona/kym/Chunk.zig | 53 ++-- 3 files changed, 239 insertions(+), 355 deletions(-) diff --git a/debug/app.ona b/debug/app.ona index c445d84..19d8065 100644 --- a/debug/app.ona +++ b/debug/app.ona @@ -5,7 +5,7 @@ test = { .message = "don't you lecture me with your 30 dollar scripting language" } -# test.message = "game is loading" +test.message = "game is loading" @log_info(test.message) diff --git a/source/ona/kym/Ast.zig b/source/ona/kym/Ast.zig index 74f0688..3890f54 100755 --- a/source/ona/kym/Ast.zig +++ b/source/ona/kym/Ast.zig @@ -18,7 +18,8 @@ pub const Expression = union (enum) { symbol_literal: []const coral.io.Byte, table_literal: TableLiteral, grouped_expression: *Expression, - resolve_local: []const coral.io.Byte, + get_local: []const coral.io.Byte, + set_local: []const coral.io.Byte, get_field: struct { object_expression: *Expression, @@ -31,11 +32,6 @@ pub const Expression = union (enum) { value_expression: *Expression, }, - call_system: struct { - identifier: []const coral.io.Byte, - argument_expressions: List, - }, - binary_operation: struct { operator: BinaryOperator, lhs_expression: *Expression, @@ -91,18 +87,11 @@ pub const ParseError = error { const Self = @This(); pub const Statement = union (enum) { - return_nothing, - return_expression: Expression, - - assign_local: struct { - identifier: []const coral.io.Byte, - expression: Expression, + @"return": struct { + expression: ?Expression, }, - call_system: struct { - identifier: []const coral.io.Byte, - argument_expressions: Expression.List, - }, + expression: Expression, const List = coral.list.Stack(Statement); }; @@ -186,106 +175,58 @@ pub fn list_statements(self: Self) []const Statement { pub fn parse(self: *Self, data: []const coral.io.Byte) ParseError!void { self.tokenizer = .{.source = data}; - const allocator = self.arena.as_allocator(); var has_returned = false; self.tokenizer.skip_newlines(); while (true) { - switch (self.tokenizer.token) { - .end => return, + try self.statements.push_one(parse_statement: { + switch (self.tokenizer.token) { + .end => return, - .keyword_return => { - if (has_returned) { - return self.report("multiple returns in function scope but expected only one"); - } + .keyword_return => { + if (has_returned) { + return self.report("multiple returns in function scope but expected only one"); + } - try self.statements.push_one(get_statement: { self.tokenizer.step(); if (self.tokenizer.token != .end and self.tokenizer.token != .newline) { - break: get_statement .{.return_expression = try self.parse_expression()}; + break: parse_statement .{ + .@"return" = .{ + .expression = try self.parse_expression(), + }, + }; } if (self.tokenizer.token != .end and self.tokenizer.token != .newline) { return self.report("expected end or newline after return statement"); } - break: get_statement .return_nothing; - }); + has_returned = true; - has_returned = true; - }, + break: parse_statement .{ + .@"return" = .{ + .expression = null, + }, + }; + }, - .identifier => |identifier| { - self.tokenizer.step(); - - switch (self.tokenizer.token) { - .end, .newline => return self.report("statement has no effect"), - - .symbol_equals => { - self.tokenizer.step(); - - if (self.tokenizer.token == .end) { - return self.report("expected expression after `=`"); - } - - try self.statements.push_one(.{ - .assign_local = .{ - .expression = try self.parse_expression(), - .identifier = identifier, - }, - }); - }, - - else => return self.report("expected `=` after local"), - } - }, - - .special_identifier => |identifier| { - self.tokenizer.step(); - - switch (self.tokenizer.token) { - .end, .newline => return self.report("system call is missing arguments"), - - .symbol_paren_left => { - self.tokenizer.step(); - - var expressions_list = Expression.List.make(allocator); - - while (true) { - if (self.tokenizer.token == .symbol_paren_right) { - break; - } - - try expressions_list.push_one(try self.parse_expression()); - - switch (self.tokenizer.token) { - .symbol_comma => continue, - .symbol_paren_right => break, - else => return self.report("expected `)` or argument after `(`"), - } - } - - self.tokenizer.skip_newlines(); - - try self.statements.push_one(.{ - .call_system = .{ - .argument_expressions = expressions_list, - .identifier = identifier, - }, - }); - }, - - else => return self.report("expected `=` after local"), - } - }, - - else => return self.report("invalid statement"), - } + else => { + break: parse_statement .{ + .expression = try self.parse_expression() + }; + }, + } + }); } } +const parse_additive = binary_operation_parser(parse_equality, &.{ + .addition, + .subtraction, +}); + const parse_comparison = binary_operation_parser(parse_term, &.{ .greater_than_comparison, .greater_equals_comparison, @@ -297,244 +238,210 @@ const parse_equality = binary_operation_parser(parse_comparison, &.{ .equals_comparison, }); -const parse_expression = binary_operation_parser(parse_equality, &.{ - .addition, - .subtraction, -}); - -fn parse_factor(self: *Self) ParseError!Expression { +pub fn parse_expression(self: *Self) ParseError!Expression { const allocator = self.arena.as_allocator(); + const expression = try parse_additive(self); - var expression = @as(Expression, get: { - switch (self.tokenizer.token) { - .symbol_paren_left => { - self.tokenizer.skip_newlines(); + if (self.tokenizer.token == .symbol_equals) { + self.tokenizer.skip_newlines(); - if (self.tokenizer.token == .end) { - return self.report("expected an expression after `(`"); - } - - const expression = try self.parse_expression(); - - if (self.tokenizer.token != .symbol_paren_right) { - return self.report("expected a closing `)` after expression"); - } - - self.tokenizer.skip_newlines(); - - break: get Expression{.grouped_expression = try coral.io.allocate_one(allocator, expression)}; - }, - - .keyword_nil => { - self.tokenizer.skip_newlines(); - - break: get .nil_literal; - }, - - .keyword_true => { - self.tokenizer.skip_newlines(); - - break: get .true_literal; - }, - - .keyword_false => { - self.tokenizer.skip_newlines(); - - break: get .false_literal; - }, - - .number => |value| { - self.tokenizer.skip_newlines(); - - break: get .{.number_literal = value}; - }, - - .string => |value| { - self.tokenizer.skip_newlines(); - - break: get .{.string_literal = value}; - }, - - .special_identifier => |identifier| { - self.tokenizer.skip_newlines(); - - var expression_list = Expression.List.make(allocator); - - while (true) { - switch (self.tokenizer.token) { - .end => return self.report("expected expression or `)` after `(`"), - - .symbol_paren_right => { - self.tokenizer.skip_newlines(); - - break: get .{ - .call_system = .{ - .identifier = identifier, - .argument_expressions = expression_list, - }, - }; - }, - - else => { - try expression_list.push_one(try self.parse_expression()); - - switch (self.tokenizer.token) { - .end => return self.report("expected `,` or `)` after argument"), - .symbol_comma => continue, - - .symbol_paren_right => { - self.tokenizer.skip_newlines(); - - break: get .{ - .call_system = .{ - .identifier = identifier, - .argument_expressions = expression_list, - }, - }; - }, - - else => return self.report("expected `,` or `)` after argument"), - } - }, - } - } - }, - - .identifier => |identifier| { - self.tokenizer.skip_newlines(); - - break: get .{.resolve_local = identifier}; - }, - - .symbol_brace_left => { - var table_literal = Expression.TableLiteral.make(allocator); - - self.tokenizer.skip_newlines(); - - while (true) { - switch (self.tokenizer.token) { - .symbol_brace_right => { - self.tokenizer.skip_newlines(); - - break: get .{.table_literal = table_literal}; - }, - - .symbol_bracket_left => { - self.tokenizer.skip_newlines(); - - if (self.tokenizer.token != .symbol_equals) { - return self.report("expected expression after identifier"); - } - }, - - .symbol_period => { - self.tokenizer.step(); - - const identifier = switch (self.tokenizer.token) { - .identifier => |identifier| identifier, - else => return self.report("expected identifier after `.`"), - }; - - self.tokenizer.skip_newlines(); - - if (self.tokenizer.token != .symbol_equals) { - return self.report("expected `=` after key"); - } - - self.tokenizer.skip_newlines(); - - if (self.tokenizer.token == .end) { - return self.report("unexpected end after `=`"); - } - - try table_literal.push_one(.{ - .value_expression = try self.parse_expression(), - .key_expression = .{.symbol_literal = identifier}, - }); - - switch (self.tokenizer.token) { - .symbol_comma => self.tokenizer.skip_newlines(), - - .symbol_brace_right => { - self.tokenizer.skip_newlines(); - - break: get .{.table_literal = table_literal}; - }, - - else => return self.report("expected `,` or `}` after expression"), - } - }, - - else => return self.report("expected `}` or fields in table literal"), - } - } - }, - - .symbol_minus => { - self.tokenizer.skip_newlines(); - - if (self.tokenizer.token == .end) { - return self.report("expected expression after numeric negation (`-`)"); - } - - break: get .{ - .unary_operation = .{ - .expression = try coral.io.allocate_one(allocator, try self.parse_factor()), - .operator = .numeric_negation, - }, - }; - }, - - .symbol_bang => { - self.tokenizer.skip_newlines(); - - if (self.tokenizer.token == .end) { - return self.report("expected expression after boolean negation (`!`)"); - } - - break: get .{ - .unary_operation = .{ - .expression = try coral.io.allocate_one(allocator, try self.parse_factor()), - .operator = .boolean_negation, - }, - }; - }, - - else => return self.report("unexpected token in expression"), + if (self.tokenizer.token == .end) { + return self.report("expected assignment after `=`"); } - }); - while (self.tokenizer.token == .symbol_period) { - self.tokenizer.skip_newlines(); + return switch (expression) { + .get_local => |get_local| .{.set_local = get_local}, - const identifier = switch (self.tokenizer.token) { - .identifier => |identifier| identifier, - else => return self.report("expected identifier after `.`"), - }; - - self.tokenizer.skip_newlines(); - - expression = switch (self.tokenizer.token) { - .symbol_equals => .{ + .get_field => |get_field| .{ .set_field = .{ + .object_expression = get_field.object_expression, + .identifier = get_field.identifier, .value_expression = try coral.io.allocate_one(allocator, try self.parse_expression()), - .object_expression = try coral.io.allocate_one(allocator, expression), - .identifier = identifier, }, }, - else => .{ - .get_field = .{ - .object_expression = try coral.io.allocate_one(allocator, expression), - .identifier = identifier, - }, - }, + else => self.report("expected local or field on left-hand side of expression"), }; } return expression; } +fn parse_factor(self: *Self) ParseError!Expression { + const allocator = self.arena.as_allocator(); + + switch (self.tokenizer.token) { + .symbol_paren_left => { + self.tokenizer.skip_newlines(); + + if (self.tokenizer.token == .end) { + return self.report("expected an expression after `(`"); + } + + const expression = try self.parse_expression(); + + if (self.tokenizer.token != .symbol_paren_right) { + return self.report("expected a closing `)` after expression"); + } + + self.tokenizer.skip_newlines(); + + return Expression{.grouped_expression = try coral.io.allocate_one(allocator, expression)}; + }, + + .keyword_nil => { + self.tokenizer.skip_newlines(); + + return .nil_literal; + }, + + .keyword_true => { + self.tokenizer.skip_newlines(); + + return .true_literal; + }, + + .keyword_false => { + self.tokenizer.skip_newlines(); + + return .false_literal; + }, + + .number => |value| { + self.tokenizer.skip_newlines(); + + return .{.number_literal = value}; + }, + + .string => |value| { + self.tokenizer.skip_newlines(); + + return .{.string_literal = value}; + }, + + .identifier => |local_identifier| { + var expression = Expression{.get_local = local_identifier}; + + self.tokenizer.skip_newlines(); + + while (self.tokenizer.token == .symbol_period) { + self.tokenizer.skip_newlines(); + + expression = .{ + .get_field = .{ + .identifier = switch (self.tokenizer.token) { + .identifier => |field_identifier| field_identifier, + else => return self.report("expected identifier after `.`"), + }, + + .object_expression = try coral.io.allocate_one(allocator, expression), + }, + }; + + self.tokenizer.skip_newlines(); + } + + return expression; + }, + + .symbol_brace_left => { + var table_literal = Expression.TableLiteral.make(allocator); + + self.tokenizer.skip_newlines(); + + while (true) { + switch (self.tokenizer.token) { + .symbol_brace_right => { + self.tokenizer.skip_newlines(); + + return .{.table_literal = table_literal}; + }, + + .symbol_bracket_left => { + self.tokenizer.skip_newlines(); + + if (self.tokenizer.token != .symbol_equals) { + return self.report("expected expression after identifier"); + } + }, + + .symbol_period => { + self.tokenizer.step(); + + const identifier = switch (self.tokenizer.token) { + .identifier => |identifier| identifier, + else => return self.report("expected identifier after `.`"), + }; + + self.tokenizer.skip_newlines(); + + if (self.tokenizer.token != .symbol_equals) { + return self.report("expected `=` after key"); + } + + self.tokenizer.skip_newlines(); + + if (self.tokenizer.token == .end) { + return self.report("unexpected end after `=`"); + } + + try table_literal.push_one(.{ + .value_expression = try self.parse_expression(), + .key_expression = .{.symbol_literal = identifier}, + }); + + switch (self.tokenizer.token) { + .symbol_comma => self.tokenizer.skip_newlines(), + + .symbol_brace_right => { + self.tokenizer.skip_newlines(); + + return .{.table_literal = table_literal}; + }, + + else => return self.report("expected `,` or `}` after expression"), + } + }, + + else => return self.report("expected `}` or fields in table literal"), + } + } + }, + + .symbol_minus => { + self.tokenizer.skip_newlines(); + + if (self.tokenizer.token == .end) { + return self.report("expected expression after numeric negation (`-`)"); + } + + return .{ + .unary_operation = .{ + .expression = try coral.io.allocate_one(allocator, try self.parse_factor()), + .operator = .numeric_negation, + }, + }; + }, + + .symbol_bang => { + self.tokenizer.skip_newlines(); + + if (self.tokenizer.token == .end) { + return self.report("expected expression after boolean negation (`!`)"); + } + + return .{ + .unary_operation = .{ + .expression = try coral.io.allocate_one(allocator, try self.parse_factor()), + .operator = .boolean_negation, + }, + }; + }, + + else => return self.report("unexpected token in expression"), + } +} + const parse_term = binary_operation_parser(parse_factor, &.{ .multiplication, .divsion, diff --git a/source/ona/kym/Chunk.zig b/source/ona/kym/Chunk.zig index 0c115db..7f4cdd2 100644 --- a/source/ona/kym/Chunk.zig +++ b/source/ona/kym/Chunk.zig @@ -99,14 +99,22 @@ const AstCompiler = struct { try self.compile_expression(grouped_expression.*); }, - .resolve_local => |local| { + .get_local => |get_local| { try self.chunk.append_opcode(.{ - .push_local = self.resolve_local(local) orelse { + .push_local = self.resolve_local(get_local) orelse { return self.chunk.env.raise(error.OutOfMemory, "undefined local"); }, }); }, + .set_local => |set_local| { + if (self.resolve_local(set_local)) |index| { + try self.chunk.append_opcode(.{.set_local = index}); + } else { + try self.declare_local(set_local); + } + }, + .get_field => |get_field| { try self.compile_expression(get_field.object_expression.*); @@ -127,51 +135,20 @@ const AstCompiler = struct { try self.compile_expression(set_field.value_expression.*); try self.chunk.append_opcode(.set_dynamic); }, - - .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"); - } - - 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(.{.syscall = @intCast(call.argument_expressions.values.len)}); - try self.chunk.append_opcode(.pop); - }, } } 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), - - .assign_local => |local| { - try self.compile_expression(local.expression); - - if (self.resolve_local(local.identifier)) |index| { - try self.chunk.append_opcode(.{.set_local = index}); + .@"return" => |@"return"| { + if (@"return".expression) |expression| { + try self.compile_expression(expression); } else { - try self.declare_local(local.identifier); + try self.chunk.append_opcode(.push_nil); } }, - .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"); - } - - 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(.{.syscall = @intCast(call.argument_expressions.values.len)}); - try self.chunk.append_opcode(.pop); - } + .expression => |expression| try self.compile_expression(expression), } } -- 2.34.1 From 9435348877869c4619336010925fb55bbd131c07 Mon Sep 17 00:00:00 2001 From: kayomn Date: Mon, 31 Jul 2023 00:12:16 +0100 Subject: [PATCH 07/12] Apply workaround for Zig struct re-assign miscompilation --- source/ona/kym/Ast.zig | 321 +++++++++++++++++++++-------------------- 1 file changed, 166 insertions(+), 155 deletions(-) diff --git a/source/ona/kym/Ast.zig b/source/ona/kym/Ast.zig index 3890f54..8faa1b2 100755 --- a/source/ona/kym/Ast.zig +++ b/source/ona/kym/Ast.zig @@ -270,63 +270,168 @@ pub fn parse_expression(self: *Self) ParseError!Expression { fn parse_factor(self: *Self) ParseError!Expression { const allocator = self.arena.as_allocator(); - switch (self.tokenizer.token) { - .symbol_paren_left => { - self.tokenizer.skip_newlines(); - - if (self.tokenizer.token == .end) { - return self.report("expected an expression after `(`"); - } - - const expression = try self.parse_expression(); - - if (self.tokenizer.token != .symbol_paren_right) { - return self.report("expected a closing `)` after expression"); - } - - self.tokenizer.skip_newlines(); - - return Expression{.grouped_expression = try coral.io.allocate_one(allocator, expression)}; - }, - - .keyword_nil => { - self.tokenizer.skip_newlines(); - - return .nil_literal; - }, - - .keyword_true => { - self.tokenizer.skip_newlines(); - - return .true_literal; - }, - - .keyword_false => { - self.tokenizer.skip_newlines(); - - return .false_literal; - }, - - .number => |value| { - self.tokenizer.skip_newlines(); - - return .{.number_literal = value}; - }, - - .string => |value| { - self.tokenizer.skip_newlines(); - - return .{.string_literal = value}; - }, - - .identifier => |local_identifier| { - var expression = Expression{.get_local = local_identifier}; - - self.tokenizer.skip_newlines(); - - while (self.tokenizer.token == .symbol_period) { + var expression = @as(Expression, parse: { + switch (self.tokenizer.token) { + .symbol_paren_left => { self.tokenizer.skip_newlines(); + if (self.tokenizer.token == .end) { + return self.report("expected an expression after `(`"); + } + + const expression = try self.parse_expression(); + + if (self.tokenizer.token != .symbol_paren_right) { + return self.report("expected a closing `)` after expression"); + } + + self.tokenizer.skip_newlines(); + + break: parse .{.grouped_expression = try coral.io.allocate_one(allocator, expression)}; + }, + + .keyword_nil => { + self.tokenizer.skip_newlines(); + + break: parse .nil_literal; + }, + + .keyword_true => { + self.tokenizer.skip_newlines(); + + break: parse .true_literal; + }, + + .keyword_false => { + self.tokenizer.skip_newlines(); + + break: parse .false_literal; + }, + + .number => |value| { + self.tokenizer.skip_newlines(); + + break: parse .{.number_literal = value}; + }, + + .string => |value| { + self.tokenizer.skip_newlines(); + + break: parse .{.string_literal = value}; + }, + + .identifier => |local_identifier| { + self.tokenizer.skip_newlines(); + + break: parse .{.get_local = local_identifier}; + }, + + .symbol_brace_left => { + var table_literal = Expression.TableLiteral.make(allocator); + + self.tokenizer.skip_newlines(); + + while (true) { + switch (self.tokenizer.token) { + .symbol_brace_right => { + self.tokenizer.skip_newlines(); + + break: parse .{.table_literal = table_literal}; + }, + + .symbol_bracket_left => { + self.tokenizer.skip_newlines(); + + if (self.tokenizer.token != .symbol_equals) { + return self.report("expected expression after identifier"); + } + }, + + .symbol_period => { + self.tokenizer.step(); + + const identifier = switch (self.tokenizer.token) { + .identifier => |identifier| identifier, + else => return self.report("expected identifier after `.`"), + }; + + self.tokenizer.skip_newlines(); + + if (self.tokenizer.token != .symbol_equals) { + return self.report("expected `=` after key"); + } + + self.tokenizer.skip_newlines(); + + if (self.tokenizer.token == .end) { + return self.report("unexpected end after `=`"); + } + + try table_literal.push_one(.{ + .value_expression = try self.parse_expression(), + .key_expression = .{.symbol_literal = identifier}, + }); + + switch (self.tokenizer.token) { + .symbol_comma => self.tokenizer.skip_newlines(), + + .symbol_brace_right => { + self.tokenizer.skip_newlines(); + + break: parse .{.table_literal = table_literal}; + }, + + else => return self.report("expected `,` or `}` after expression"), + } + }, + + else => return self.report("expected `}` or fields in table literal"), + } + } + }, + + .symbol_minus => { + self.tokenizer.skip_newlines(); + + if (self.tokenizer.token == .end) { + return self.report("expected expression after numeric negation (`-`)"); + } + + break: parse .{ + .unary_operation = .{ + .expression = try coral.io.allocate_one(allocator, try self.parse_factor()), + .operator = .numeric_negation, + }, + }; + }, + + .symbol_bang => { + self.tokenizer.skip_newlines(); + + if (self.tokenizer.token == .end) { + return self.report("expected expression after boolean negation (`!`)"); + } + + break: parse .{ + .unary_operation = .{ + .expression = try coral.io.allocate_one(allocator, try self.parse_factor()), + .operator = .boolean_negation, + }, + }; + }, + + else => return self.report("unexpected token in expression"), + } + }); + + while (true) { + switch (self.tokenizer.token) { + .symbol_period => { + self.tokenizer.skip_newlines(); + + // TODO: Remove when Zig fixes miscompilation with in-place struct re-assignment. + const unnecessary_temp = try coral.io.allocate_one(allocator, expression); + expression = .{ .get_field = .{ .identifier = switch (self.tokenizer.token) { @@ -334,112 +439,18 @@ fn parse_factor(self: *Self) ParseError!Expression { else => return self.report("expected identifier after `.`"), }, - .object_expression = try coral.io.allocate_one(allocator, expression), + .object_expression = unnecessary_temp, }, }; self.tokenizer.skip_newlines(); - } + }, - return expression; - }, - - .symbol_brace_left => { - var table_literal = Expression.TableLiteral.make(allocator); - - self.tokenizer.skip_newlines(); - - while (true) { - switch (self.tokenizer.token) { - .symbol_brace_right => { - self.tokenizer.skip_newlines(); - - return .{.table_literal = table_literal}; - }, - - .symbol_bracket_left => { - self.tokenizer.skip_newlines(); - - if (self.tokenizer.token != .symbol_equals) { - return self.report("expected expression after identifier"); - } - }, - - .symbol_period => { - self.tokenizer.step(); - - const identifier = switch (self.tokenizer.token) { - .identifier => |identifier| identifier, - else => return self.report("expected identifier after `.`"), - }; - - self.tokenizer.skip_newlines(); - - if (self.tokenizer.token != .symbol_equals) { - return self.report("expected `=` after key"); - } - - self.tokenizer.skip_newlines(); - - if (self.tokenizer.token == .end) { - return self.report("unexpected end after `=`"); - } - - try table_literal.push_one(.{ - .value_expression = try self.parse_expression(), - .key_expression = .{.symbol_literal = identifier}, - }); - - switch (self.tokenizer.token) { - .symbol_comma => self.tokenizer.skip_newlines(), - - .symbol_brace_right => { - self.tokenizer.skip_newlines(); - - return .{.table_literal = table_literal}; - }, - - else => return self.report("expected `,` or `}` after expression"), - } - }, - - else => return self.report("expected `}` or fields in table literal"), - } - } - }, - - .symbol_minus => { - self.tokenizer.skip_newlines(); - - if (self.tokenizer.token == .end) { - return self.report("expected expression after numeric negation (`-`)"); - } - - return .{ - .unary_operation = .{ - .expression = try coral.io.allocate_one(allocator, try self.parse_factor()), - .operator = .numeric_negation, - }, - }; - }, - - .symbol_bang => { - self.tokenizer.skip_newlines(); - - if (self.tokenizer.token == .end) { - return self.report("expected expression after boolean negation (`!`)"); - } - - return .{ - .unary_operation = .{ - .expression = try coral.io.allocate_one(allocator, try self.parse_factor()), - .operator = .boolean_negation, - }, - }; - }, - - else => return self.report("unexpected token in expression"), + else => break, + } } + + return expression; } const parse_term = binary_operation_parser(parse_factor, &.{ -- 2.34.1 From 4ee0bcab671755911ef9705498afd3c299c33c67 Mon Sep 17 00:00:00 2001 From: kayomn Date: Mon, 31 Jul 2023 00:30:44 +0100 Subject: [PATCH 08/12] Fix incorrect stack popping order in set_dynamic opcode --- source/ona/kym/Chunk.zig | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/ona/kym/Chunk.zig b/source/ona/kym/Chunk.zig index 7f4cdd2..d3fd37b 100644 --- a/source/ona/kym/Chunk.zig +++ b/source/ona/kym/Chunk.zig @@ -378,6 +378,10 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef }, .set_dynamic => { + const value_ref = try env.pop_local(); + + defer env.discard(value_ref); + const index_ref = try env.pop_local() orelse { return env.raise(error.TypeMismatch, "nil is not a valid index"); }; @@ -390,10 +394,6 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef defer env.discard(indexable_ref); - const value_ref = try env.pop_local(); - - defer env.discard(value_ref); - try env.set_dynamic(try kym.unbox_dynamic(env, indexable_ref), index_ref, value_ref); }, -- 2.34.1 From 1f8f3fd9dc38687df60105f5f1f2b207f7024686 Mon Sep 17 00:00:00 2001 From: kayomn Date: Mon, 31 Jul 2023 02:29:50 +0100 Subject: [PATCH 09/12] Consolidate syscalls and calls into new expressions tree --- source/ona/kym.zig | 96 ++++++++++++++++++++++++--------------- source/ona/kym/Ast.zig | 50 +++++++++++++++++++- source/ona/kym/Chunk.zig | 58 ++++++++++++----------- source/ona/kym/tokens.zig | 8 ++-- source/ona/ona.zig | 25 ++++------ 5 files changed, 153 insertions(+), 84 deletions(-) diff --git a/source/ona/kym.zig b/source/ona/kym.zig index 79f4af8..42758c9 100644 --- a/source/ona/kym.zig +++ b/source/ona/kym.zig @@ -14,6 +14,7 @@ pub const Any = union (enum) { float: Float, string: []const coral.io.Byte, symbol: []const coral.io.Byte, + lambda, dynamic: *const DynamicObject, pub fn expect_dynamic(self: Any) ?*const DynamicObject { @@ -76,22 +77,20 @@ pub const Method = struct { }; pub const RuntimeEnv = struct { - interned_symbols: SymbolTable, + interned_symbols: RefTable, allocator: coral.io.Allocator, error_handler: ErrorHandler, - syscallers: SyscallerTable, + syscallables: RefTable, local_refs: RefStack, frames: FrameStack, ref_values: RefSlab, const FrameStack = coral.list.Stack(Frame); - const SymbolTable = coral.map.StringTable(*RuntimeRef); - - const SyscallerTable = coral.map.StringTable(Caller); - const RefStack = coral.list.Stack(?*RuntimeRef); + const RefTable = coral.map.StringTable(*RuntimeRef); + const RefSlab = coral.map.Slab(struct { ref_count: usize, @@ -102,15 +101,11 @@ pub const RuntimeEnv = struct { fixed: Fixed, string: []coral.io.Byte, symbol: []coral.io.Byte, + lambda: Caller, dynamic: *DynamicObject, }, }); - pub const Syscall = struct { - name: []const coral.io.Byte, - caller: Caller, - }; - pub fn acquire(self: *RuntimeEnv, ref: *const RuntimeRef) *RuntimeRef { const key = @intFromPtr(ref); var ref_data = self.ref_values.remove(key); @@ -124,15 +119,9 @@ pub const RuntimeEnv = struct { return @ptrFromInt(key); } - pub fn bind_syscalls(self: *RuntimeEnv, syscalls: []const Syscall) RuntimeError!void { - for (syscalls) |syscall| { - _ = try self.syscallers.replace(syscall.name, syscall.caller); - } - } - pub fn call( self: *RuntimeEnv, - caller: Caller, + callable_ref: *const RuntimeRef, arg_count: u8, name: []const coral.io.Byte, ) RuntimeError!?*RuntimeRef { @@ -154,16 +143,26 @@ pub const RuntimeEnv = struct { } } - return caller.invoke(self); - } + return switch ((self.ref_values.lookup(@intFromPtr(callable_ref)) orelse unreachable).object) { + .lambda => |lambda| lambda.invoke(self), + + .dynamic => |dynamic| dynamic.typeinfo.call(.{ + .env = self, + .userdata = dynamic.userdata, + }), - pub fn callable(self: *RuntimeEnv, ref: *const RuntimeRef) RuntimeError!Caller { - return switch ((self.ref_values.lookup(@intFromPtr(ref)) orelse unreachable).object) { - .dynamic => |dynamic| Caller.bind(DynamicObject, dynamic, DynamicObject.call), else => self.raise(error.TypeMismatch, "object is not callable"), }; } + pub fn bind_syscaller(self: *RuntimeEnv, comptime name: []const coral.io.Byte, caller: Caller) RuntimeError!void { + const lambda_ref = try self.new_lambda(caller); + + if (try self.syscallables.replace(name, self.acquire(lambda_ref))) |replaced_entry| { + self.discard(replaced_entry.value); + } + } + pub fn discard(self: *RuntimeEnv, ref: ?*RuntimeRef) void { const key = @intFromPtr(ref orelse return); var ref_data = self.ref_values.remove(key) orelse unreachable; @@ -174,7 +173,7 @@ pub const RuntimeEnv = struct { if (ref_data.ref_count == 0) { switch (ref_data.object) { - .false, .true, .float, .fixed => {}, + .false, .true, .float, .fixed, .lambda => {}, .string => |string| self.allocator.deallocate(string), .symbol => |symbol| self.allocator.deallocate(symbol), @@ -225,7 +224,11 @@ pub const RuntimeEnv = struct { try chunk.compile_ast(ast); - return self.call(chunk.as_caller(), 0, name); + const chunk_ref = try self.new_lambda(chunk.as_caller()); + + defer self.discard(chunk_ref); + + return self.call(chunk_ref, 0, name); } pub fn free(self: *RuntimeEnv) void { @@ -241,9 +244,17 @@ pub const RuntimeEnv = struct { } } + { + var iterable = self.syscallables.as_iterable(); + + while (iterable.next()) |entry| { + self.discard(entry.value); + } + } + self.frames.free(); self.local_refs.free(); - self.syscallers.free(); + self.syscallables.free(); self.ref_values.free(); self.interned_symbols.free(); } @@ -267,6 +278,14 @@ pub const RuntimeEnv = struct { return self.acquire(self.local_refs.values[local] orelse return null); } + pub fn get_syscallable(self: *RuntimeEnv, name: []const coral.io.Byte) ?*RuntimeRef { + if (self.syscallables.lookup(name)) |system_ref| { + return self.acquire(system_ref); + } + + return null; + } + pub fn pop_local(self: *RuntimeEnv) RuntimeError!?*RuntimeRef { return self.local_refs.pop() orelse self.raise(error.IllegalState, "stack underflow"); } @@ -300,8 +319,8 @@ pub const RuntimeEnv = struct { .local_refs = RefStack.make(allocator), .ref_values = RefSlab.make(allocator), .frames = FrameStack.make(allocator), - .syscallers = SyscallerTable.make(allocator, .{}), - .interned_symbols = SymbolTable.make(allocator, .{}), + .syscallables = RefTable.make(allocator, .{}), + .interned_symbols = RefTable.make(allocator, .{}), .error_handler = error_handler, .allocator = allocator, }; @@ -350,6 +369,13 @@ pub const RuntimeEnv = struct { })); } + pub fn new_lambda(self: *RuntimeEnv, caller: Caller) RuntimeError!*RuntimeRef { + return @ptrFromInt(try self.ref_values.insert(.{ + .ref_count = 1, + .object = .{.lambda = caller}, + })); + } + 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); @@ -405,10 +431,6 @@ pub const RuntimeEnv = struct { }, index_ref, value_ref); } - pub fn syscallable(self: *RuntimeEnv, name: []const coral.io.Byte) RuntimeError!Caller { - return self.syscallers.lookup(name) orelse self.raise(error.BadOperation, "attempt to get undefined syscall"); - } - pub fn unbox(self: *RuntimeEnv, ref: *const RuntimeRef) Any { return switch ((self.ref_values.lookup(@intFromPtr(ref)) orelse unreachable).object) { .false => .{.boolean = false}, @@ -417,6 +439,7 @@ pub const RuntimeEnv = struct { .float => |float| .{.float = float}, .string => |string| .{.string = string}, .symbol => |symbol| .{.symbol = symbol}, + .lambda => .lambda, .dynamic => |dynamic| .{.dynamic = dynamic}, }; } @@ -484,7 +507,8 @@ pub fn hash(env: *RuntimeEnv, ref: *const RuntimeRef) usize { .fixed => 0, .string => |string| coral.io.djb2_hash(@typeInfo(usize).Int, string), .symbol => 0, - .dynamic => |dynamic| @intFromPtr(dynamic), + .lambda => 0, + .dynamic => 0, }; } @@ -520,6 +544,7 @@ pub fn test_difference( else => env.raise(error.TypeMismatch, "right-hand object is not comparable with symbol objects"), }, + .lambda => env.raise(error.TypeMismatch, "lambda objects are not comparable"), .dynamic => env.raise(error.TypeMismatch, "dynamic objects are not comparable"), }; } @@ -555,10 +580,7 @@ pub fn test_equality(env: *RuntimeEnv, lhs_ref: *const RuntimeRef, rhs_ref: *con else => false, }, - .dynamic => |lhs_dynamic| switch (env.unbox(rhs_ref)) { - .dynamic => |rhs_dynamic| rhs_dynamic == lhs_dynamic, - else => false, - }, + else => lhs_ref == rhs_ref, }; } diff --git a/source/ona/kym/Ast.zig b/source/ona/kym/Ast.zig index 8faa1b2..2815216 100755 --- a/source/ona/kym/Ast.zig +++ b/source/ona/kym/Ast.zig @@ -18,6 +18,7 @@ pub const Expression = union (enum) { symbol_literal: []const coral.io.Byte, table_literal: TableLiteral, grouped_expression: *Expression, + get_system: []const coral.io.Byte, get_local: []const coral.io.Byte, set_local: []const coral.io.Byte, @@ -43,6 +44,11 @@ pub const Expression = union (enum) { expression: *Expression, }, + call: struct { + object_expression: *Expression, + argument_expressions: List, + }, + pub const BinaryOperator = enum { addition, subtraction, @@ -320,10 +326,16 @@ fn parse_factor(self: *Self) ParseError!Expression { break: parse .{.string_literal = value}; }, - .identifier => |local_identifier| { + .identifier => |identifier| { self.tokenizer.skip_newlines(); - break: parse .{.get_local = local_identifier}; + break: parse .{.get_local = identifier}; + }, + + .system_identifier => |system_identifier| { + self.tokenizer.skip_newlines(); + + break: parse .{.get_system = system_identifier}; }, .symbol_brace_left => { @@ -446,6 +458,40 @@ fn parse_factor(self: *Self) ParseError!Expression { self.tokenizer.skip_newlines(); }, + .symbol_paren_left => { + var argument_expressions = Expression.List.make(allocator); + + while (true) { + self.tokenizer.skip_newlines(); + + switch (self.tokenizer.token) { + .symbol_paren_right => break, + + else => { + try argument_expressions.push_one(try self.parse_expression()); + + switch (self.tokenizer.token) { + .symbol_comma => continue, + .symbol_paren_right => break, + else => return self.report("expected `,` or `)` after function argument expression"), + } + }, + } + } + + self.tokenizer.skip_newlines(); + + // TODO: Remove when Zig fixes miscompilation with in-place struct re-assignment. + const unnecessary_temp = try coral.io.allocate_one(allocator, expression); + + expression = .{ + .call = .{ + .argument_expressions = argument_expressions, + .object_expression = unnecessary_temp, + }, + }; + }, + else => break, } } diff --git a/source/ona/kym/Chunk.zig b/source/ona/kym/Chunk.zig index d3fd37b..899075b 100644 --- a/source/ona/kym/Chunk.zig +++ b/source/ona/kym/Chunk.zig @@ -95,10 +95,27 @@ const AstCompiler = struct { }); }, + .call => |call| { + if (call.argument_expressions.values.len > coral.math.max_int(@typeInfo(u8).Int)) { + return self.chunk.env.raise(error.BadSyntax, "lambdas may contain a maximum of 255 arguments"); + } + + for (call.argument_expressions.values) |argument_expression| { + try self.compile_expression(argument_expression); + } + + try self.compile_expression(call.object_expression.*); + try self.chunk.append_opcode(.{.call = @intCast(call.argument_expressions.values.len)}); + }, + .grouped_expression => |grouped_expression| { try self.compile_expression(grouped_expression.*); }, + .get_system => |get_system| { + try self.chunk.append_opcode(.{.push_system = try self.chunk.declare_constant_string(get_system)}); + }, + .get_local => |get_local| { try self.chunk.append_opcode(.{ .push_local = self.resolve_local(get_local) orelse { @@ -154,7 +171,7 @@ const AstCompiler = struct { 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"); + return self.chunk.env.raise(error.OutOfMemory, "lambdas may contain a maximum of 255 locals"); } self.local_identifiers_buffer[self.local_identifiers_count] = identifier; @@ -180,7 +197,7 @@ const AstCompiler = struct { } }; -const RefList = coral.list.Stack(?*kym.RuntimeRef); +const RefList = coral.list.Stack(*kym.RuntimeRef); const LocalsList = coral.list.Stack([]const u8); @@ -192,11 +209,11 @@ pub const Opcode = union (enum) { push_const: u16, push_local: u8, push_table: u32, + push_system: u16, set_local: u8, get_dynamic, set_dynamic, call: u8, - syscall: u8, not, neg, @@ -341,6 +358,17 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef try env.push_ref(table_ref); }, + .push_system => |push_system| { + const system_ref = self.env.get_syscallable(try kym.unbox_string( + self.env, + self.constant_refs.values[push_system], + )); + + defer self.env.discard(system_ref); + + try self.env.push_ref(system_ref); + }, + .push_local => |local| { const ref = try env.get_local(local); @@ -405,25 +433,7 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef defer env.discard(callable_ref); - break: call try env.call(try env.callable(callable_ref), arg_count, ""); - }; - - defer env.discard(result_ref); - - try env.push_ref(result_ref); - }, - - .syscall => |arg_count| { - const result_ref = call: { - const identifier_ref = try env.pop_local() orelse { - return env.raise(error.TypeMismatch, "nil is not syscallable"); - }; - - defer env.discard(identifier_ref); - - const identifier = try kym.unbox_string(env, identifier_ref); - - break: call try env.call(try env.syscallable(identifier), arg_count, identifier); + break: call try env.call(callable_ref, arg_count, ""); }; defer env.discard(result_ref); @@ -644,10 +654,6 @@ pub fn free(self: *Self) void { 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), diff --git a/source/ona/kym/tokens.zig b/source/ona/kym/tokens.zig index 7aa0e61..bc916d4 100755 --- a/source/ona/kym/tokens.zig +++ b/source/ona/kym/tokens.zig @@ -5,7 +5,7 @@ pub const Token = union(enum) { unknown: coral.io.Byte, newline, - special_identifier: []const coral.io.Byte, + system_identifier: []const coral.io.Byte, identifier: []const coral.io.Byte, symbol_plus, @@ -46,7 +46,7 @@ pub const Token = union(enum) { .unknown => |unknown| @as([*]const coral.io.Byte, @ptrCast(&unknown))[0 .. 1], .newline => "newline", - .special_identifier => |identifier| identifier, + .system_identifier => |identifier| identifier, .identifier => |identifier| identifier, .symbol_plus => "+", @@ -224,7 +224,7 @@ pub const Tokenizer = struct { else => break, }; - self.token = .{.special_identifier = self.source[begin .. cursor]}; + self.token = .{.system_identifier = self.source[begin .. cursor]}; return; }, @@ -241,7 +241,7 @@ pub const Tokenizer = struct { else => cursor += 1, }; - self.token = .{.special_identifier = self.source[begin .. cursor]}; + self.token = .{.system_identifier = self.source[begin .. cursor]}; cursor += 1; return; diff --git a/source/ona/ona.zig b/source/ona/ona.zig index 43897b1..212ef85 100644 --- a/source/ona/ona.zig +++ b/source/ona/ona.zig @@ -69,21 +69,16 @@ pub fn run_app(file_access: file.Access) void { defer script_env.free(); - script_env.bind_syscalls(&.{ - .{ - .name = "log_info", - .caller = kym.Caller.from(kym_log_info), - }, - .{ - .name = "log_warn", - .caller = kym.Caller.from(kym_log_warn), - }, - .{ - .name = "log_fail", - .caller = kym.Caller.from(kym_log_fail), - }, - }) catch { - return app.log_fail("failed to bind syscalls to script runtime"); + script_env.bind_syscaller("log_info", kym.Caller.from(kym_log_info)) catch { + return app.log_fail("failed to bind `log_info` syscall"); + }; + + script_env.bind_syscaller("log_warn", kym.Caller.from(kym_log_warn)) catch { + return app.log_fail("failed to bind `log_warn` syscall"); + }; + + script_env.bind_syscaller("log_fail", kym.Caller.from(kym_log_fail)) catch { + return app.log_fail("failed to bind `log_fail` syscall"); }; var manifest = app.Manifest{}; -- 2.34.1 From 1f9df3fc1d3c2204ba62f8d032a4fa211723031a Mon Sep 17 00:00:00 2001 From: kayomn Date: Sat, 12 Aug 2023 12:57:50 +0100 Subject: [PATCH 10/12] Refactor and clean up virtual machine --- debug/app.ona | 14 +- source/coral/io.zig | 20 + source/coral/map.zig | 168 ++--- source/coral/math.zig | 6 - source/ona/app.zig | 55 +- source/ona/kym.zig | 1479 +++++++++++++++++++++++++++----------- source/ona/kym/Ast.zig | 26 +- source/ona/kym/Chunk.zig | 663 ----------------- source/ona/kym/Table.zig | 174 ++--- source/ona/ona.zig | 24 +- 10 files changed, 1255 insertions(+), 1374 deletions(-) delete mode 100644 source/ona/kym/Chunk.zig diff --git a/debug/app.ona b/debug/app.ona index 19d8065..1cdb540 100644 --- a/debug/app.ona +++ b/debug/app.ona @@ -1,17 +1,13 @@ # Test comment. -test = { - .message = "don't you lecture me with your 30 dollar scripting language" -} - -test.message = "game is loading" - -@log_info(test.message) - -return { +options = { .title = "Afterglow", .width = 1280, .height = 800, .tick_rate = 60, } + +# @log_info(options.title) + +return options diff --git a/source/coral/io.zig b/source/coral/io.zig index 8da89fe..39025ee 100644 --- a/source/coral/io.zig +++ b/source/coral/io.zig @@ -255,6 +255,16 @@ pub fn allocate_one(allocator: Allocator, value: anytype) AllocationError!*@Type return allocation; } +pub fn allocate_string(allocator: Allocator, source: []const Byte) AllocationError![:0]Byte { + const allocation = try allocator.actions.reallocate(allocator.context, @returnAddress(), null, source.len + 1); + + copy(allocation[0 .. source.len], source); + + allocation[source.len] = 0; + + return @ptrCast(allocation); +} + pub fn bytes_of(value: anytype) []const Byte { const pointer_info = @typeInfo(@TypeOf(value)).Pointer; @@ -330,6 +340,16 @@ pub fn equals(target: []const Byte, match: []const Byte) bool { return true; } +pub fn find_first(haystack: []const Byte, needle: Byte) ?usize { + for (0 .. haystack.len) |i| { + if (haystack[i] == needle) { + return i; + } + } + + return null; +} + var null_context = @as(usize, 0); pub const null_writer = Writer.bind(usize, &null_context, struct { diff --git a/source/coral/map.zig b/source/coral/map.zig index 81c11d4..30a8bdb 100644 --- a/source/coral/map.zig +++ b/source/coral/map.zig @@ -6,112 +6,15 @@ const list = @import("./list.zig"); const math = @import("./math.zig"); -pub fn Slab(comptime Value: type) type { - return struct { - next_index: usize, - entries: EntryList, - - const EntryList = list.Stack(union (enum) { - value: Value, - next_index: usize, - }); - - const Self = @This(); - - pub fn lookup(self: Self, key: usize) ?Value { - if (key == 0 or key > self.entries.values.len) { - return null; - } - - return switch (self.entries.values[key - 1]) { - .value => |value| value, - .next_index => null, - }; - } - - pub fn free(self: *Self) void { - self.entries.free(); - - self.next_index = 0; - } - - pub fn insert(self: *Self, value: Value) io.AllocationError!usize { - if (self.next_index < self.entries.values.len) { - const index = self.next_index; - const entry = &self.entries.values[index]; - - debug.assert(entry.* == .next_index); - - self.next_index = entry.next_index; - entry.* = .{.value = value}; - - return index + 1; - } else { - try self.entries.push_one(.{.value = value}); - - self.next_index += 1; - - return self.next_index; - } - } - - 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, - .entries = EntryList.make(allocator), - }; - } - - pub fn remove(self: *Self, key: usize) ?Value { - if (key == 0 or key > self.entries.values.len) { - return null; - } - - const index = key - 1; - const entry = &self.entries.values[index]; - - return switch (entry.*) { - .next_index => null, - - .value => get_value: { - const value = entry.value; - - entry.* = .{.next_index = self.next_index}; - self.next_index = index; - - break: get_value value; - }, - }; - } - }; -} - pub fn StringTable(comptime Value: type) type { return Table([]const io.Byte, Value, struct { const Self = @This(); - fn equals(_: Self, key_a: []const io.Byte, key_b: []const io.Byte) bool { + fn equals(key_a: []const io.Byte, key_b: []const io.Byte) bool { return io.equals(key_a, key_b); } - fn hash(_: Self, key: []const io.Byte) usize { + fn hash(key: []const io.Byte) usize { return io.djb2_hash(@typeInfo(usize).Int, key); } }); @@ -119,9 +22,7 @@ pub fn StringTable(comptime Value: type) type { pub fn Table(comptime Key: type, comptime Value: type, comptime Traits: type) 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); + const max_int = math.max_int(@typeInfo(usize).Int); return struct { allocator: io.Allocator, @@ -135,7 +36,7 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime Traits: type) ty fn write_into(self: Entry, table: *Self) bool { const hash_max = @min(max_int, table.entries.len); - var hashed_key = math.wrap(table.traits.hash(self.key), min_int, hash_max); + var hashed_key = table.hash_key(self.key) % hash_max; var iterations = @as(usize, 0); while (true) : (iterations += 1) { @@ -152,7 +53,7 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime Traits: type) ty return true; }); - if (table.traits.equals(table_entry.key, self.key)) { + if (table.keys_equal(table_entry.key, self.key)) { return false; } @@ -187,14 +88,18 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime Traits: type) ty }; } + fn hash_key(self: Self, key: Key) usize { + return if (@sizeOf(Traits) == 0) Traits.hash(key) else self.traits.hash(key); + } + pub fn remove(self: *Self, key: Key) ?Entry { const hash_max = @min(max_int, self.entries.len); - var hashed_key = math.wrap(self.traits.hash(key), min_int, hash_max); + var hashed_key = self.hash_key(key) % hash_max; while (true) { const entry = &(self.entries[hashed_key] orelse continue); - if (self.traits.equals(entry.key, key)) { + if (self.keys_equal(entry.key, key)) { const original_entry = entry.*; self.entries[hashed_key] = null; @@ -213,7 +118,8 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime Traits: type) ty { const hash_max = @min(max_int, self.entries.len); - var hashed_key = math.wrap(self.traits.hash(key), min_int, hash_max); + const has_context = @sizeOf(Traits) != 0; + var hashed_key = (if (has_context) self.traits.hash(key) else Traits.hash(key)) % hash_max; while (true) { const entry = &(self.entries[hashed_key] orelse { @@ -227,15 +133,28 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime Traits: type) ty return null; }); - if (self.traits.equals(entry.key, key)) { - const original_entry = entry.*; + if (has_context) { + if (self.traits.equals(entry.key, key)) { + const original_entry = entry.*; - entry.* = .{ - .key = key, - .value = value, - }; + entry.* = .{ + .key = key, + .value = value, + }; - return original_entry; + return original_entry; + } + } else { + if (Traits.equals(entry.key, key)) { + const original_entry = entry.*; + + entry.* = .{ + .key = key, + .value = value, + }; + + return original_entry; + } } hashed_key = (hashed_key +% 1) % hash_max; @@ -279,20 +198,35 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime Traits: type) ty return entry.write_into(self); } + fn keys_equal(self: Self, key_a: Key, key_b: Key) bool { + if (@sizeOf(Traits) == 0) { + return Traits.equals(key_a, key_b); + } else { + return self.traits.equals(key_a, key_b); + } + } + pub fn lookup(self: Self, key: Key) ?Value { if (self.count == 0) { return null; } const hash_max = @min(max_int, self.entries.len); - var hashed_key = math.wrap(self.traits.hash(key), min_int, hash_max); + const has_context = @sizeOf(Traits) != 0; + var hashed_key = (if (has_context) self.traits.hash(key) else Traits.hash(key)) % hash_max; var iterations = @as(usize, 0); while (iterations < self.count) : (iterations += 1) { const entry = &(self.entries[hashed_key] orelse return null); - if (self.traits.equals(entry.key, key)) { - return entry.value; + if (has_context) { + if (self.traits.equals(entry.key, key)) { + return entry.value; + } + } else { + if (Traits.equals(entry.key, key)) { + return entry.value; + } } hashed_key = (hashed_key +% 1) % hash_max; diff --git a/source/coral/math.zig b/source/coral/math.zig index 3f0de17..360da13 100644 --- a/source/coral/math.zig +++ b/source/coral/math.zig @@ -60,9 +60,3 @@ pub fn min_int(comptime int: std.builtin.Type.Int) comptime_int { return -(1 << (bit_count - 1)); } - -pub fn wrap(value: anytype, lower: anytype, upper: anytype) @TypeOf(value, lower, upper) { - const range = upper - lower; - - return if (range == 0) lower else lower + @mod((@mod((value - lower), range) + range), range); -} diff --git a/source/ona/app.zig b/source/ona/app.zig index a7d49ba..81d51f5 100644 --- a/source/ona/app.zig +++ b/source/ona/app.zig @@ -13,20 +13,18 @@ pub const Manifest = struct { tick_rate: f32 = 60.0, 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"})) orelse return; + const manifest = try env.expect(try env.execute_file(file_access, file.Path.from(&.{"app.ona"}))); - defer env.discard(manifest_ref); + defer env.discard(manifest); const width = @as(u16, get: { - const ref = try kym.get_field(env, manifest_ref, "width"); + if (try env.get_field(manifest, "width")) |ref| { + defer env.discard(ref); - defer env.discard(ref); + const fixed = try env.unbox_fixed(ref); - if (ref) |live_ref| { - if (env.unbox(live_ref).expect_number()) |number| { - if (number > 0 and number < coral.math.max_int(@typeInfo(@TypeOf(self.width)).Int)) { - break: get @intFromFloat(number); - } + if (fixed > 0 and fixed < coral.math.max_int(@typeInfo(@TypeOf(self.width)).Int)) { + break: get @intCast(fixed); } } @@ -34,15 +32,13 @@ pub const Manifest = struct { }); const height = @as(u16, get: { - const ref = try kym.get_field(env, manifest_ref, "height"); + if (try env.get_field(manifest, "height")) |ref| { + defer env.discard(ref); - defer env.discard(ref); + const fixed = try env.unbox_fixed(ref); - if (ref) |live_ref| { - if (env.unbox(live_ref).expect_number()) |number| { - if (number > 0 and number < coral.math.max_int(@typeInfo(@TypeOf(self.height)).Int)) { - break: get @intFromFloat(number); - } + if (fixed > 0 and fixed < coral.math.max_int(@typeInfo(@TypeOf(self.height)).Int)) { + break: get @intCast(fixed); } } @@ -50,38 +46,25 @@ pub const Manifest = struct { }); const tick_rate = @as(f32, get: { - const ref = try kym.get_field(env, manifest_ref, "tick_rate"); + if (try env.get_field(manifest, "tick_rate")) |ref| { + defer env.discard(ref); - defer env.discard(ref); - - if (ref) |live_ref| { - if (env.unbox(live_ref).expect_number()) |number| { - break: get @floatCast(number); - } + break: get @floatCast(try env.unbox_float(ref)); } break: get self.tick_rate; }); - { - const ref = try kym.get_field(env, manifest_ref, "title"); - + if (try env.get_field(manifest, "title")) |ref| { defer env.discard(ref); - const title_string = unbox: { - if (ref) |live_ref| { - if (env.unbox(live_ref).expect_string()) |string| { - break: unbox string; - } - } - - break: unbox ""; - }; - + const title_string = try env.unbox_string(ref); 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]); + } else { + coral.io.zero(&self.title); } self.tick_rate = tick_rate; diff --git a/source/ona/kym.zig b/source/ona/kym.zig index 42758c9..1b424e4 100644 --- a/source/ona/kym.zig +++ b/source/ona/kym.zig @@ -1,59 +1,13 @@ const Ast = @import("./kym/Ast.zig"); -const Chunk = @import("./kym/Chunk.zig"); - const Table = @import("./kym/Table.zig"); const coral = @import("coral"); const file = @import("./file.zig"); -pub const Any = union (enum) { - boolean: bool, - fixed: Fixed, - float: Float, - string: []const coral.io.Byte, - symbol: []const coral.io.Byte, - lambda, - dynamic: *const DynamicObject, - - pub fn expect_dynamic(self: Any) ?*const DynamicObject { - return switch (self) { - .dynamic => |dynamic| dynamic, - else => null, - }; - } - - pub fn expect_number(self: Any) ?Float { - return switch (self) { - .fixed => |fixed| @floatFromInt(fixed), - .float => |float| float, - 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, - - fn call(self: *DynamicObject, env: *RuntimeEnv) RuntimeError!?*RuntimeRef { - return self.typeinfo.call(.{ - .env = env, - .userdata = self.userdata, - }); - } -}; - pub const ErrorHandler = coral.io.Generator(void, ErrorInfo); pub const ErrorInfo = struct { @@ -71,176 +25,890 @@ pub const Frame = struct { locals_top: usize, }; -pub const Method = struct { - env: *RuntimeEnv, - userdata: []coral.io.Byte, -}; - pub const RuntimeEnv = struct { - interned_symbols: RefTable, + interned_symbols: SymbolSet, allocator: coral.io.Allocator, error_handler: ErrorHandler, syscallables: RefTable, - local_refs: RefStack, + locals: RefList, frames: FrameStack, - ref_values: RefSlab, - const FrameStack = coral.list.Stack(Frame); + const Chunk = struct { + env: *RuntimeEnv, + name: []coral.io.Byte, + opcodes: OpcodeList, + constants: ConstList, - const RefStack = coral.list.Stack(?*RuntimeRef); - - const RefTable = coral.map.StringTable(*RuntimeRef); - - const RefSlab = coral.map.Slab(struct { - ref_count: usize, - - object: union (enum) { - false, - true, - float: Float, + const Constant = union (enum) { fixed: Fixed, - string: []coral.io.Byte, - symbol: []coral.io.Byte, - lambda: Caller, - dynamic: *DynamicObject, - }, - }); + float: Float, + string: []const coral.io.Byte, + symbol: []const coral.io.Byte, + }; - pub fn acquire(self: *RuntimeEnv, ref: *const RuntimeRef) *RuntimeRef { - const key = @intFromPtr(ref); - var ref_data = self.ref_values.remove(key); + const Opcode = union (enum) { + push_nil, + push_true, + push_false, + push_const: u16, + push_local: u8, + push_table: u32, + push_system: u16, + local_set: u8, + object_get, + object_set, + object_call: u8, - coral.debug.assert(ref_data != null); + not, + neg, - ref_data.?.ref_count += 1; + add, + sub, + mul, + div, - coral.debug.assert(self.ref_values.insert_at(key, ref_data.?)); + eql, + cgt, + clt, + cge, + cle, + }; - return @ptrFromInt(key); - } + const OpcodeList = coral.list.Stack(Opcode); - pub fn call( - self: *RuntimeEnv, - callable_ref: *const RuntimeRef, - arg_count: u8, - name: []const coral.io.Byte, - ) RuntimeError!?*RuntimeRef { - try self.frames.push_one(.{ - .name = name, - .arg_count = arg_count, - .locals_top = self.local_refs.values.len, - }); + const CompilationUnit = struct { + local_identifiers_buffer: [255][]const coral.io.Byte = [_][]const coral.io.Byte{""} ** 255, + local_identifiers_count: u8 = 0, - defer { - const frame = self.frames.pop() orelse unreachable; + fn compile_expression(self: *CompilationUnit, chunk: *Chunk, expression: Ast.Expression) RuntimeError!void { + const number_format = coral.utf8.DecimalFormat{ + .delimiter = "_", + .positive_prefix = .none, + }; - { - var pops_remaining = (self.local_refs.values.len - frame.locals_top) + frame.arg_count; + switch (expression) { + .nil_literal => try chunk.opcodes.push_one(.push_nil), + .true_literal => try chunk.opcodes.push_one(.push_true), + .false_literal => try chunk.opcodes.push_one(.push_false), - while (pops_remaining != 0) : (pops_remaining -= 1) { - self.discard(self.local_refs.pop() orelse unreachable); + .number_literal => |literal| { + for (literal) |codepoint| { + if (codepoint == '.') { + return chunk.opcodes.push_one(.{ + .push_const = try chunk.declare_constant(.{ + .float = number_format.parse(literal, Float) orelse unreachable, + }), + }); + } + } + + try chunk.opcodes.push_one(.{ + .push_const = try chunk.declare_constant(.{ + .fixed = number_format.parse(literal, Fixed) orelse unreachable, + }), + }); + }, + + .string_literal => |literal| { + try chunk.opcodes.push_one(.{ + .push_const = try chunk.declare_constant(.{.string = literal}), + }); + }, + + .symbol_literal => |literal| { + try chunk.opcodes.push_one(.{ + .push_const = try chunk.declare_constant(.{.symbol = 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(chunk, field.value_expression); + try self.compile_expression(chunk, field.key_expression); + } + + try chunk.opcodes.push_one(.{.push_table = @intCast(fields.values.len)}); + }, + + .binary_operation => |operation| { + try self.compile_expression(chunk, operation.lhs_expression.*); + try self.compile_expression(chunk, operation.rhs_expression.*); + + try chunk.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(chunk, operation.expression.*); + + try chunk.opcodes.push_one(switch (operation.operator) { + .boolean_negation => .not, + .numeric_negation => .neg, + }); + }, + + .invoke => |invoke| { + if (invoke.argument_expressions.values.len > coral.math.max_int(@typeInfo(u8).Int)) { + return chunk.env.raise(error.BadSyntax, "lambdas may contain a maximum of 255 arguments"); + } + + for (invoke.argument_expressions.values) |argument_expression| { + try self.compile_expression(chunk, argument_expression); + } + + try self.compile_expression(chunk, invoke.object_expression.*); + try chunk.opcodes.push_one(.{.object_call = @intCast(invoke.argument_expressions.values.len)}); + }, + + .grouped_expression => |grouped_expression| { + try self.compile_expression(chunk, grouped_expression.*); + }, + + .get_system => |get_system| { + try chunk.opcodes.push_one(.{ + .push_system = try chunk.declare_constant(.{.symbol = get_system}), + }); + }, + + .local_get => |local_get| { + try chunk.opcodes.push_one(.{ + .push_local = self.resolve_local(local_get) orelse { + return chunk.env.raise(error.OutOfMemory, "undefined local"); + }, + }); + }, + + .local_set => |local_set| { + if (self.resolve_local(local_set)) |index| { + try chunk.opcodes.push_one(.{.local_set = index}); + } else { + if (self.local_identifiers_count == self.local_identifiers_buffer.len) { + return chunk.env.raise(error.BadSyntax, "chunks may have a maximum of 255 locals"); + } + + self.local_identifiers_buffer[self.local_identifiers_count] = local_set; + self.local_identifiers_count += 1; + } + }, + + .field_get => |field_get| { + try self.compile_expression(chunk, field_get.object_expression.*); + + try chunk.opcodes.push_one(.{ + .push_const = try chunk.declare_constant(.{.symbol = field_get.identifier}), + }); + + try chunk.opcodes.push_one(.object_get); + }, + + .field_set => |field_set| { + try self.compile_expression(chunk, field_set.object_expression.*); + + try chunk.opcodes.push_one(.{ + .push_const = try chunk.declare_constant(.{.symbol = field_set.identifier}), + }); + + try self.compile_expression(chunk, field_set.value_expression.*); + try chunk.opcodes.push_one(.object_set); + }, } } + + fn compile_statement(self: *CompilationUnit, chunk: *Chunk, statement: Ast.Statement) RuntimeError!void { + switch (statement) { + .@"return" => |@"return"| { + if (@"return".expression) |expression| { + try self.compile_expression(chunk, expression); + } else { + try chunk.opcodes.push_one(.push_nil); + } + }, + + .expression => |expression| try self.compile_expression(chunk, expression), + } + } + + fn resolve_local(self: *CompilationUnit, 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; + } + } + } + }; + + fn compile(self: *Chunk, ast: Ast) RuntimeError!void { + var unit = CompilationUnit{}; + + for (ast.list_statements()) |statement| { + try unit.compile_statement(self, statement); + } } - return switch ((self.ref_values.lookup(@intFromPtr(callable_ref)) orelse unreachable).object) { - .lambda => |lambda| lambda.invoke(self), + fn declare_constant(self: *Chunk, constant: Constant) RuntimeError!u16 { + if (self.constants.values.len == coral.math.max_int(@typeInfo(u16).Int)) { + return self.env.raise(error.BadSyntax, "chunks cannot contain more than 65,535 constants"); + } + const constant_index = self.constants.values.len; + + try self.constants.push_one(try switch (constant) { + .fixed => |fixed| self.env.new_fixed(fixed), + .float => |float| self.env.new_float(float), + .string => |string| self.env.new_string(string), + .symbol => |symbol| self.env.new_symbol(symbol), + }); + + return @intCast(constant_index); + } + + fn execute(self: *Chunk) RuntimeError!?*RuntimeRef { + try self.env.frames.push_one(.{ + .arg_count = 0, + .locals_top = self.env.locals.values.len, + .name = self.name, + }); + + defer coral.debug.assert(self.env.frames.pop() != null); + + for (self.opcodes.values) |opcode| { + switch (opcode) { + .push_nil => try self.env.locals.push_one(null), + .push_true => try self.env.locals.push_one(try self.env.new_boolean(true)), + .push_false => try self.env.locals.push_one(try self.env.new_boolean(false)), + + .push_const => |push_const| { + if (push_const >= self.constants.values.len) { + return self.env.raise(error.IllegalState, "invalid const"); + } + + try self.env.locals.push_one(try self.env.acquire(self.constants.values[push_const])); + }, + + .push_local => |push_local| { + if (self.env.locals.values[push_local]) |local| { + try self.env.locals.push_one(try self.env.acquire(local)); + } else { + try self.env.locals.push_one(null); + } + }, + + .push_table => |push_table| { + const table = try self.env.new_table(); + + errdefer self.env.discard(table); + + { + const dynamic = table.object().payload.dynamic; + const userdata = dynamic.userdata(); + var popped = @as(usize, 0); + + while (popped < push_table) : (popped += 1) { + const index = try self.env.expect(try self.pop_local()); + + defer self.env.discard(index); + + const maybe_value = try self.pop_local(); + + defer { + if (maybe_value) |value| { + self.env.discard(value); + } + } + + try dynamic.typeinfo().set(.{ + .userdata = userdata, + .env = self.env, + }, index, maybe_value); + } + } + + try self.env.locals.push_one(table); + }, + + .push_system => |push_system| { + if (self.env.syscallables.lookup(self.constants.values[push_system])) |syscallable| { + try self.env.locals.push_one(try self.env.acquire(syscallable)); + } else { + try self.env.locals.push_one(null); + } + }, + + .local_set => |local_set| { + const local = &self.env.locals.values[local_set]; + + if (local.*) |previous_local| { + self.env.discard(previous_local); + } + + local.* = try self.pop_local(); + }, + + .object_get => { + const index = (try self.pop_local()) orelse { + return self.env.raise(error.TypeMismatch, "nil is not a valid index"); + }; + + defer self.env.discard(index); + + const indexable = (try self.pop_local()) orelse { + return self.env.raise(error.TypeMismatch, "nil is not a valid indexable"); + }; + + defer self.env.discard(indexable); + + const value = try self.env.get(indexable, index); + + errdefer { + if (value) |ref| { + self.env.discard(ref); + } + } + + try self.env.locals.push_one(value); + }, + + .object_set => { + const value = try self.pop_local(); + + defer { + if (value) |ref| { + self.env.discard(ref); + } + } + + const index = try self.pop_local() orelse { + return self.env.raise(error.TypeMismatch, "nil is not a valid index"); + }; + + defer self.env.discard(index); + + const indexable = try self.pop_local() orelse { + return self.env.raise(error.TypeMismatch, "nil is not a valid indexable"); + }; + + defer self.env.discard(indexable); + + try self.env.set(indexable, index, value); + }, + + .object_call => { + const result = call: { + const callable = try self.env.expect(try self.pop_local()); + + defer self.env.discard(callable); + + break: call try switch (callable.object().payload) { + .dynamic => |dynamic| dynamic.typeinfo().call(.{ + .userdata = dynamic.userdata(), + .env = self.env, + }), + + else => self.env.raise(error.TypeMismatch, "object is not callable"), + }; + }; + + errdefer { + if (result) |ref| { + self.env.discard(ref); + } + } + + try self.env.locals.push_one(result); + }, + + .not => { + if (try self.pop_local()) |value| { + defer self.env.discard(value); + + try self.env.locals.push_one(try self.env.new_boolean(!value.is_truthy())); + } else { + try self.env.locals.push_one(try self.env.new_boolean(true)); + } + }, + + .neg => { + const value = try self.env.expect(try self.pop_local()); + + defer self.env.discard(value); + + try self.env.locals.push_one(try switch (value.object().payload) { + .fixed => |fixed| self.env.new_fixed(-fixed), + .float => |float| self.env.new_float(-float), + else => self.env.raise(error.TypeMismatch, "object is not negatable"), + }); + }, + + .add => { + const rhs = try self.env.expect(try self.pop_local()); + + defer self.env.discard(rhs); + + const lhs = try self.env.expect(try self.pop_local()); + + defer self.env.discard(lhs); + + try self.env.locals.push_one(try switch (lhs.object().payload) { + .fixed => |lhs_fixed| switch (rhs.object().payload) { + .fixed => |rhs_fixed| add: { + if (coral.math.checked_add(lhs_fixed, rhs_fixed)) |fixed| { + break: add self.env.new_fixed(fixed); + } + + break: add self.env.new_float(@as(Float, + @floatFromInt(lhs_fixed)) + + @as(Float, @floatFromInt(rhs_fixed))); + }, + + .float => |rhs_float| self.env.new_float( + @as(Float, @floatFromInt(lhs_fixed)) + rhs_float), + + else => self.env.raise(error.TypeMismatch, "right-hand object is not addable"), + }, + + .float => |lhs_float| switch (rhs.object().payload) { + .float => |rhs_float| self.env.new_float(lhs_float + rhs_float), + + .fixed => |rhs_fixed| self.env.new_float( + lhs_float + @as(Float, @floatFromInt(rhs_fixed))), + + else => self.env.raise(error.TypeMismatch, "right-hand object is not addable"), + }, + + else => self.env.raise(error.TypeMismatch, "left-hand object is not addable"), + }); + }, + + .sub => { + const rhs = try self.env.expect(try self.pop_local()); + + defer self.env.discard(rhs); + + const lhs = try self.env.expect(try self.pop_local()); + + defer self.env.discard(lhs); + + try self.env.locals.push_one(try switch (lhs.object().payload) { + .fixed => |lhs_fixed| switch (rhs.object().payload) { + .fixed => |rhs_fixed| sub: { + if (coral.math.checked_sub(lhs_fixed, rhs_fixed)) |fixed| { + break: sub self.env.new_fixed(fixed); + } + + break: sub self.env.new_float(@as(Float, + @floatFromInt(lhs_fixed)) - + @as(Float, @floatFromInt(rhs_fixed))); + }, + + .float => |rhs_float| self.env.new_float( + @as(Float, @floatFromInt(lhs_fixed)) - rhs_float), + + else => self.env.raise(error.TypeMismatch, "right-hand object is not subtractable"), + }, + + .float => |lhs_float| switch (rhs.object().payload) { + .float => |rhs_float| self.env.new_float(lhs_float - rhs_float), + + .fixed => |rhs_fixed| self.env.new_float( + lhs_float - @as(Float, @floatFromInt(rhs_fixed))), + + else => self.env.raise(error.TypeMismatch, "right-hand object is not subtractable"), + }, + + else => self.env.raise(error.TypeMismatch, "left-hand object is not subtractable"), + }); + }, + + .mul => { + const rhs = try self.env.expect(try self.pop_local()); + + defer self.env.discard(rhs); + + const lhs = try self.env.expect(try self.pop_local()); + + defer self.env.discard(lhs); + + try self.env.locals.push_one(try switch (lhs.object().payload) { + .fixed => |lhs_fixed| switch (rhs.object().payload) { + .fixed => |rhs_fixed| mul: { + if (coral.math.checked_mul(lhs_fixed, rhs_fixed)) |fixed| { + break: mul self.env.new_fixed(fixed); + } + + break: mul self.env.new_float(@as(Float, + @floatFromInt(lhs_fixed)) * + @as(Float, @floatFromInt(rhs_fixed))); + }, + + .float => |rhs_float| self.env.new_float( + @as(Float, @floatFromInt(lhs_fixed)) * rhs_float), + + else => self.env.raise(error.TypeMismatch, "right-hand object is not multiplicable"), + }, + + .float => |lhs_float| switch (rhs.object().payload) { + .float => |rhs_float| self.env.new_float(lhs_float * rhs_float), + + .fixed => |rhs_fixed| self.env.new_float( + lhs_float * @as(Float, @floatFromInt(rhs_fixed))), + + else => self.env.raise(error.TypeMismatch, "right-hand object is not multiplicable"), + }, + + else => self.env.raise(error.TypeMismatch, "left-hand object is not multiplicable"), + }); + }, + + .div => { + const rhs = try self.env.expect(try self.pop_local()); + + defer self.env.discard(rhs); + + const lhs = try self.env.expect(try self.pop_local()); + + defer self.env.discard(lhs); + + try self.env.locals.push_one(try switch (lhs.object().payload) { + .fixed => |lhs_fixed| switch (rhs.object().payload) { + .fixed => |rhs_fixed| self.env.new_float(@as(Float, + @floatFromInt(lhs_fixed)) / + @as(Float, @floatFromInt(rhs_fixed))), + + .float => |rhs_float| self.env.new_float( + @as(Float, @floatFromInt(lhs_fixed)) / rhs_float), + + else => self.env.raise(error.TypeMismatch, "right-hand object is not divisible"), + }, + + .float => |lhs_float| switch (rhs.object().payload) { + .float => |rhs_float| self.env.new_float(lhs_float / rhs_float), + + .fixed => |rhs_fixed| self.env.new_float( + lhs_float / @as(Float, @floatFromInt(rhs_fixed))), + + else => self.env.raise(error.TypeMismatch, "right-hand object is not divisible"), + }, + + else => self.env.raise(error.TypeMismatch, "left-hand object is not divisible"), + }); + }, + + .eql => { + if (try self.pop_local()) |rhs| { + self.env.discard(rhs); + + if (try self.pop_local()) |lhs| { + self.env.discard(lhs); + + try self.env.locals.push_one(try self.env.new_boolean(lhs.equals(rhs))); + } else { + try self.env.locals.push_one(try self.env.new_boolean(false)); + } + } else { + if (try self.pop_local()) |lhs| { + self.env.discard(lhs); + + try self.env.locals.push_one(try self.env.new_boolean(false)); + } else { + try self.env.locals.push_one(try self.env.new_boolean(true)); + } + } + }, + + .cgt => { + const rhs = try self.env.expect(try self.pop_local()); + + defer self.env.discard(rhs); + + const lhs = try self.env.expect(try self.pop_local()); + + defer self.env.discard(lhs); + + try self.env.locals.push_one(try self.env.new_boolean(switch (lhs.object().payload) { + .fixed => |lhs_fixed| switch (rhs.object().payload) { + .fixed => |rhs_fixed| lhs_fixed > rhs_fixed, + .float => |rhs_float| @as(Float, @floatFromInt(lhs_fixed)) > rhs_float, + else => return self.env.raise(error.TypeMismatch, "right-hand object is not comparable"), + }, + + .float => |lhs_float| switch (rhs.object().payload) { + .float => |rhs_float| lhs_float > rhs_float, + .fixed => |rhs_fixed| lhs_float > @as(Float, @floatFromInt(rhs_fixed)), + else => return self.env.raise(error.TypeMismatch, "right-hand object is not comparable"), + }, + + else => return self.env.raise(error.TypeMismatch, "left-hand object is not comparable"), + })); + }, + + .clt => { + const rhs = try self.env.expect(try self.pop_local()); + + defer self.env.discard(rhs); + + const lhs = try self.env.expect(try self.pop_local()); + + defer self.env.discard(lhs); + + try self.env.locals.push_one(try self.env.new_boolean(switch (lhs.object().payload) { + .fixed => |lhs_fixed| switch (rhs.object().payload) { + .fixed => |rhs_fixed| lhs_fixed < rhs_fixed, + .float => |rhs_float| @as(Float, @floatFromInt(lhs_fixed)) < rhs_float, + else => return self.env.raise(error.TypeMismatch, "right-hand object is not comparable"), + }, + + .float => |lhs_float| switch (rhs.object().payload) { + .float => |rhs_float| lhs_float < rhs_float, + .fixed => |rhs_fixed| lhs_float < @as(Float, @floatFromInt(rhs_fixed)), + else => return self.env.raise(error.TypeMismatch, "right-hand object is not comparable"), + }, + + else => return self.env.raise(error.TypeMismatch, "left-hand object is not comparable"), + })); + }, + + .cge => { + const rhs = try self.env.expect(try self.pop_local()); + + defer self.env.discard(rhs); + + const lhs = try self.env.expect(try self.pop_local()); + + defer self.env.discard(lhs); + + try self.env.locals.push_one(try self.env.new_boolean(switch (lhs.object().payload) { + .fixed => |lhs_fixed| switch (rhs.object().payload) { + .fixed => |rhs_fixed| lhs_fixed >= rhs_fixed, + .float => |rhs_float| @as(Float, @floatFromInt(lhs_fixed)) >= rhs_float, + else => return self.env.raise(error.TypeMismatch, "right-hand object is not comparable"), + }, + + .float => |lhs_float| switch (rhs.object().payload) { + .float => |rhs_float| lhs_float >= rhs_float, + .fixed => |rhs_fixed| lhs_float >= @as(Float, @floatFromInt(rhs_fixed)), + else => return self.env.raise(error.TypeMismatch, "right-hand object is not comparable"), + }, + + else => return self.env.raise(error.TypeMismatch, "left-hand object is not comparable"), + })); + }, + + .cle => { + const rhs = try self.env.expect(try self.pop_local()); + + defer self.env.discard(rhs); + + const lhs = try self.env.expect(try self.pop_local()); + + defer self.env.discard(lhs); + + try self.env.locals.push_one(try self.env.new_boolean(switch (lhs.object().payload) { + .fixed => |lhs_fixed| switch (rhs.object().payload) { + .fixed => |rhs_fixed| lhs_fixed <= rhs_fixed, + .float => |rhs_float| @as(Float, @floatFromInt(lhs_fixed)) <= rhs_float, + else => return self.env.raise(error.TypeMismatch, "right-hand object is not comparable"), + }, + + .float => |lhs_float| switch (rhs.object().payload) { + .float => |rhs_float| lhs_float <= rhs_float, + .fixed => |rhs_fixed| lhs_float <= @as(Float, @floatFromInt(rhs_fixed)), + else => return self.env.raise(error.TypeMismatch, "right-hand object is not comparable"), + }, + + else => return self.env.raise(error.TypeMismatch, "left-hand object is not comparable"), + })); + }, + + } + } + + return self.pop_local(); + } + + fn free(self: *Chunk) void { + while (self.constants.pop()) |constant| { + self.env.discard(constant); + } + + self.constants.free(); + self.opcodes.free(); + self.env.allocator.deallocate(self.name); + } + + fn make(env: *RuntimeEnv, name: []const coral.io.Byte) coral.io.AllocationError!Chunk { + return .{ + .name = try coral.io.allocate_copy(env.allocator, name), + .opcodes = OpcodeList.make(env.allocator), + .constants = ConstList.make(env.allocator), + .env = env, + }; + } + + fn pop_local(self: *Chunk) RuntimeError!?*RuntimeRef { + return self.env.locals.pop() orelse self.env.raise(error.IllegalState, "stack underflow"); + } + }; + + const ConstList = coral.list.Stack(*RuntimeRef); + + const FrameStack = coral.list.Stack(Frame); + + const RefList = coral.list.Stack(?*RuntimeRef); + + const RefTable = coral.map.Table(*RuntimeRef, *RuntimeRef, struct { + pub const hash = RuntimeRef.hash; + + pub const equals = RuntimeRef.equals; + }); + + const SymbolSet = coral.map.StringTable([:0]coral.io.Byte); + + pub fn acquire(self: *RuntimeEnv, value: *const RuntimeRef) RuntimeError!*RuntimeRef { + const object = value.object(); + + object.ref_count = coral.math.checked_add(object.ref_count, 1) orelse { + return self.raise(error.IllegalState, "reference overflow"); + }; + + return @ptrCast(object); + } + + pub fn arg(self: *RuntimeEnv, index: usize) RuntimeError!?*RuntimeRef { + const frame = self.frames.peek() orelse return self.raise(error.IllegalState, "stack underflow"); + + if (index < frame.arg_count) { + if (self.locals.values[frame.locals_top - (1 + index)]) |local| { + return self.acquire(local); + } + } + + return null; + } + + pub fn bind_syscaller(_: *RuntimeEnv, comptime _: []const coral.io.Byte, _: Caller) RuntimeError!void { + + } + + pub fn call(self: *RuntimeEnv, callable: *RuntimeRef, args: []const *RuntimeRef) RuntimeError!?*RuntimeRef { + try self.locals.push_all(args); + + defer coral.io.assert(self.locals.drop(args.len)); + + try self.frames.push_one(.{ + .name = "", + .arg_count = args.len, + .locals_top = self.locals.values.len, + }); + + defer coral.io.assert(self.frames.pop() != null); + + return switch (callable.object().payload) { .dynamic => |dynamic| dynamic.typeinfo.call(.{ + .userdata = dynamic.userdata(), .env = self, - .userdata = dynamic.userdata, }), else => self.raise(error.TypeMismatch, "object is not callable"), }; } - pub fn bind_syscaller(self: *RuntimeEnv, comptime name: []const coral.io.Byte, caller: Caller) RuntimeError!void { - const lambda_ref = try self.new_lambda(caller); + pub fn discard(self: *RuntimeEnv, value: *RuntimeRef) void { + var object = value.object(); - if (try self.syscallables.replace(name, self.acquire(lambda_ref))) |replaced_entry| { - self.discard(replaced_entry.value); - } - } + coral.debug.assert(object.ref_count != 0); - pub fn discard(self: *RuntimeEnv, ref: ?*RuntimeRef) void { - const key = @intFromPtr(ref orelse return); - var ref_data = self.ref_values.remove(key) orelse unreachable; + object.ref_count -= 1; - coral.debug.assert(ref_data.ref_count != 0); + if (object.ref_count == 0) { + switch (object.payload) { + .false, .true, .float, .fixed, .symbol => {}, - ref_data.ref_count -= 1; - - if (ref_data.ref_count == 0) { - switch (ref_data.object) { - .false, .true, .float, .fixed, .lambda => {}, - .string => |string| self.allocator.deallocate(string), - .symbol => |symbol| self.allocator.deallocate(symbol), + .string => |string| { + coral.debug.assert(string.len >= 0); + self.allocator.deallocate(string.ptr[0 .. @intCast(string.len)]); + }, .dynamic => |dynamic| { - dynamic.typeinfo.clean(.{ - .env = self, - .userdata = dynamic.userdata, - }); + if (dynamic.typeinfo().destruct) |destruct| { + destruct(.{ + .userdata = dynamic.userdata(), + .env = self, + }); + } - self.allocator.deallocate(dynamic.userdata); - self.allocator.deallocate(dynamic); + self.allocator.deallocate(dynamic.unpack()); }, } - } else { - coral.debug.assert(self.ref_values.insert_at(key, ref_data)); + + self.allocator.deallocate(object); } } 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); + const file_data = (try file.allocate_and_load(self.allocator, file_access, file_path)) orelse { + return self.raise(error.BadOperation, "failed to open or read file specified"); + }; - if (file_path.to_string()) |string| { - return self.execute_script(string, file_data); - } - } + defer self.allocator.deallocate(file_data); - return self.raise(error.BadOperation, "failed to load file"); - } - - pub fn execute_script( - self: *RuntimeEnv, - name: []const coral.io.Byte, - data: []const coral.io.Byte, - ) RuntimeError!?*RuntimeRef { - var ast = Ast.make(self.allocator, name); + const file_name = file_path.to_string() orelse "