From 0141c9c2edd70e052348d28362cb276efc5c224e Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 23 Jul 2023 03:12:45 +0100 Subject: [PATCH] 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; }