kym-tables-overhaul #30

Merged
kayomn merged 12 commits from kym-tables-overhaul into main 2023-08-12 15:20:18 +02:00
9 changed files with 409 additions and 189 deletions
Showing only changes of commit 0141c9c2ed - Show all commits

3
.vscode/launch.json vendored
View File

@ -7,7 +7,8 @@
"request": "launch", "request": "launch",
"target": "${workspaceRoot}/zig-out/bin/runner", "target": "${workspaceRoot}/zig-out/bin/runner",
"cwd": "${workspaceRoot}/debug/", "cwd": "${workspaceRoot}/debug/",
"valuesFormatting": "parseText" "valuesFormatting": "parseText",
"preLaunchTask": "Build All"
}, },
] ]
} }

View File

@ -3,8 +3,8 @@
@log_info("game is loading") @log_info("game is loading")
return { return {
title = "Afterglow", .title = "Afterglow",
width = 1280, .width = 1280,
height = 800, .height = 800,
tick_rate = 60, .tick_rate = 60,
} }

View File

@ -336,7 +336,7 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra
pub fn TableTraits(comptime Key: type) type { pub fn TableTraits(comptime Key: type) type {
return struct { return struct {
hash: fn (key: Key) usize, hash: fn (key: Key) usize,
match: fn (key: Key, key: Key) bool, match: fn (self: Key, other: Key) bool,
}; };
} }

View File

@ -13,7 +13,7 @@ pub const Manifest = struct {
tick_rate: f32 = 60.0, tick_rate: f32 = 60.0,
pub fn load(self: *Manifest, env: *kym.RuntimeEnv, file_access: file.Access) kym.RuntimeError!void { 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); defer env.discard(manifest_ref);
@ -22,11 +22,13 @@ pub const Manifest = struct {
defer env.discard(ref); defer env.discard(ref);
if (env.unbox(ref).expect_number()) |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)) { if (number > 0 and number < coral.math.max_int(@typeInfo(@TypeOf(self.width)).Int)) {
break: get @intFromFloat(number); break: get @intFromFloat(number);
} }
} }
}
break: get self.width; break: get self.width;
}); });
@ -36,11 +38,13 @@ pub const Manifest = struct {
defer env.discard(ref); defer env.discard(ref);
if (env.unbox(ref).expect_number()) |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)) { if (number > 0 and number < coral.math.max_int(@typeInfo(@TypeOf(self.height)).Int)) {
break: get @intFromFloat(number); break: get @intFromFloat(number);
} }
} }
}
break: get self.height; break: get self.height;
}); });
@ -50,19 +54,30 @@ pub const Manifest = struct {
defer env.discard(ref); defer env.discard(ref);
if (env.unbox(ref).expect_number()) |number| { if (ref) |live_ref| {
if (env.unbox(live_ref).expect_number()) |number| {
break: get @floatCast(number); break: get @floatCast(number);
} }
}
break: get self.tick_rate; 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); const limited_title_len = @min(title_string.len, self.title.len);
coral.io.copy(&self.title, title_string[0 .. limited_title_len]); coral.io.copy(&self.title, title_string[0 .. limited_title_len]);

View File

@ -9,10 +9,10 @@ const coral = @import("coral");
const file = @import("./file.zig"); const file = @import("./file.zig");
pub const Any = union (enum) { pub const Any = union (enum) {
nil,
boolean: bool, boolean: bool,
number: Float, number: Float,
string: []const coral.io.Byte, string: []const coral.io.Byte,
symbol: []const coral.io.Byte,
dynamic: *DynamicObject, dynamic: *DynamicObject,
pub fn expect_dynamic(self: Any) ?*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 Caller = coral.io.Generator(RuntimeError!?*RuntimeRef, *RuntimeEnv);
pub const Context = struct {
env: *RuntimeEnv,
userdata: []coral.io.Byte
};
pub const DynamicObject = struct { pub const DynamicObject = struct {
userdata: []coral.io.Byte, userdata: []coral.io.Byte,
typeinfo: *const Typeinfo, typeinfo: *const Typeinfo,
@ -67,13 +72,8 @@ pub const Frame = struct {
locals_top: usize, locals_top: usize,
}; };
pub const IndexContext = struct {
env: *RuntimeEnv,
userdata: []coral.io.Byte,
index_ref: ?*const RuntimeRef,
};
pub const RuntimeEnv = struct { pub const RuntimeEnv = struct {
interned_symbols: SymbolTable,
allocator: coral.io.Allocator, allocator: coral.io.Allocator,
error_handler: ErrorHandler, error_handler: ErrorHandler,
syscallers: SyscallerTable, syscallers: SyscallerTable,
@ -83,6 +83,8 @@ pub const RuntimeEnv = struct {
const FrameStack = coral.list.Stack(Frame); const FrameStack = coral.list.Stack(Frame);
const SymbolTable = coral.map.StringTable(*RuntimeRef);
const SyscallerTable = coral.map.StringTable(Caller); const SyscallerTable = coral.map.StringTable(Caller);
const RefStack = coral.list.Stack(?*RuntimeRef); const RefStack = coral.list.Stack(?*RuntimeRef);
@ -95,6 +97,7 @@ pub const RuntimeEnv = struct {
true, true,
number: Float, number: Float,
string: []coral.io.Byte, string: []coral.io.Byte,
symbol: []coral.io.Byte,
dynamic: *DynamicObject, dynamic: *DynamicObject,
}, },
}); });
@ -104,8 +107,8 @@ pub const RuntimeEnv = struct {
caller: Caller, caller: Caller,
}; };
pub fn acquire(self: *RuntimeEnv, ref: ?*const RuntimeRef) ?*RuntimeRef { pub fn acquire(self: *RuntimeEnv, ref: *const RuntimeRef) *RuntimeRef {
const key = @intFromPtr(ref orelse return null); const key = @intFromPtr(ref);
var ref_data = self.ref_values.remove(key); var ref_data = self.ref_values.remove(key);
coral.debug.assert(ref_data != null); coral.debug.assert(ref_data != null);
@ -166,15 +169,21 @@ pub const RuntimeEnv = struct {
if (ref_data.ref_count == 0) { if (ref_data.ref_count == 0) {
switch (ref_data.object) { switch (ref_data.object) {
.false => {},
.true => {},
.number => {},
.string => |string| self.allocator.deallocate(string), .string => |string| self.allocator.deallocate(string),
.symbol => |symbol| self.allocator.deallocate(symbol),
.dynamic => |dynamic| { .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.userdata);
self.allocator.deallocate(dynamic); self.allocator.deallocate(dynamic);
}, },
else => {},
} }
} else { } else {
coral.debug.assert(self.ref_values.insert_at(key, ref_data)); 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 { pub fn free(self: *RuntimeEnv) void {
for (self.local_refs.values) |ref| { while (self.local_refs.pop()) |ref| {
self.discard(ref); self.discard(ref);
} }
{
var iterable = self.interned_symbols.as_iterable();
while (iterable.next()) |entry| {
self.discard(entry.value);
}
}
self.frames.free(); self.frames.free();
self.syscallers.free();
self.local_refs.free(); self.local_refs.free();
self.syscallers.free();
self.ref_values.free(); self.ref_values.free();
self.interned_symbols.free();
} }
pub fn get_local(self: *RuntimeEnv, local: u8) RuntimeError!?*RuntimeRef { 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.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 { 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 { 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) { if (local >= self.local_refs.values.len) {
return self.raise(error.IllegalState, "out of bounds local set"); 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 { 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), .ref_values = RefSlab.make(allocator),
.frames = FrameStack.make(allocator), .frames = FrameStack.make(allocator),
.syscallers = SyscallerTable.make(allocator), .syscallers = SyscallerTable.make(allocator),
.interned_symbols = SymbolTable.make(allocator),
.error_handler = error_handler, .error_handler = error_handler,
.allocator = allocator, .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(.{ return @ptrFromInt(try self.ref_values.insert(.{
.ref_count = 1, .ref_count = 1,
.object = if (value) .true else .false, .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(.{ return @ptrFromInt(try self.ref_values.insert(.{
.ref_count = 1, .ref_count = 1,
.object = .{.number = value}, .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); const string_copy = try coral.io.allocate_copy(self.allocator, string_data);
errdefer self.allocator.deallocate(string_copy); 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 { pub fn raise(self: *RuntimeEnv, error_value: RuntimeError, message: []const coral.io.Byte) RuntimeError {
self.error_handler.invoke(.{ self.error_handler.invoke(.{
.message = message, .message = message,
@ -330,9 +372,8 @@ pub const RuntimeEnv = struct {
return self.syscallers.lookup(name) orelse self.raise(error.BadOperation, "attempt to call undefined syscall"); return self.syscallers.lookup(name) orelse self.raise(error.BadOperation, "attempt to call undefined syscall");
} }
pub fn unbox(self: *RuntimeEnv, ref: ?*const RuntimeRef) Any { pub fn unbox(self: *RuntimeEnv, ref: *const RuntimeRef) Any {
if (ref) |live_ref| { const ref_data = self.ref_values.lookup(@intFromPtr(ref));
const ref_data = self.ref_values.lookup(@intFromPtr(live_ref));
coral.debug.assert(ref_data != null); coral.debug.assert(ref_data != null);
@ -341,13 +382,11 @@ pub const RuntimeEnv = struct {
.true => .{.boolean = true}, .true => .{.boolean = true},
.number => |number| .{.number = number}, .number => |number| .{.number = number},
.string => |string| .{.string = string}, .string => |string| .{.string = string},
.symbol => |symbol| .{.symbol = symbol},
.dynamic => |dynamic| .{.dynamic = dynamic}, .dynamic => |dynamic| .{.dynamic = dynamic},
}; };
} }
return .nil;
}
pub fn view_arg(self: *RuntimeEnv, index: usize) RuntimeError!?*const RuntimeRef { pub fn view_arg(self: *RuntimeEnv, index: usize) RuntimeError!?*const RuntimeRef {
const frame = self.frames.peek() orelse return self.raise(error.IllegalState, "stack underflow"); const frame = self.frames.peek() orelse return self.raise(error.IllegalState, "stack underflow");
@ -368,43 +407,37 @@ pub const RuntimeError = coral.io.AllocationError || error {
pub const RuntimeRef = opaque {}; pub const RuntimeRef = opaque {};
pub const TestContext = struct {
env: *RuntimeEnv,
userdata: []const coral.io.Byte,
other_ref: ?*const RuntimeRef,
};
pub const Typeinfo = struct { pub const Typeinfo = struct {
name: []const coral.io.Byte, name: []const coral.io.Byte,
call: *const fn (env: *RuntimeEnv) RuntimeError!?*RuntimeRef = default_call, call: *const fn (context: Context) RuntimeError!?*RuntimeRef = default_call,
clean: *const fn (env: *RuntimeEnv, userdata: []coral.io.Byte) void = default_clean, clean: *const fn (context: Context) void = default_clean,
get: *const fn (context: IndexContext) RuntimeError!?*RuntimeRef = default_get, get: *const fn (context: Context, index: *const RuntimeRef) RuntimeError!?*RuntimeRef = default_get,
set: *const fn (context: IndexContext, value: ?*const RuntimeRef) RuntimeError!void = default_set, set: *const fn (context: Context, index: *const RuntimeRef, value: ?*const RuntimeRef) RuntimeError!void = default_set,
test_difference: *const fn (context: TestContext) RuntimeError!Float = default_test_difference, test_difference: *const fn (context: Context, other_ref: *const RuntimeRef) RuntimeError!Float = default_test_difference,
test_equality: *const fn (context: TestContext) RuntimeError!bool = default_test_equality, test_equality: *const fn (context: Context, other_ref: *const RuntimeRef) RuntimeError!bool = default_test_equality,
fn default_call(env: *RuntimeEnv) RuntimeError!?*RuntimeRef { fn default_call(context: Context) RuntimeError!?*RuntimeRef {
return env.raise(error.TypeMismatch, "object is not callable"); 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. // 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"); 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"); 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"); return context.env.raise(error.TypeMismatch, "object is not comparable");
} }
fn default_test_equality(context: TestContext) RuntimeError!bool { fn default_test_equality(context: Context, other_ref: *const RuntimeRef) RuntimeError!bool {
return switch (context.env.unbox(context.other_ref)) { return switch (context.env.unbox(other_ref)) {
.dynamic => |dynamic| context.userdata.ptr == dynamic.userdata.ptr, .dynamic => |dynamic| context.userdata.ptr == dynamic.userdata.ptr,
else => false, else => false,
}; };
@ -414,7 +447,7 @@ pub const Typeinfo = struct {
pub fn call( pub fn call(
env: *RuntimeEnv, env: *RuntimeEnv,
caller_ref: ?*const RuntimeRef, caller_ref: ?*const RuntimeRef,
callable_ref: ?*const RuntimeRef, callable_ref: *const RuntimeRef,
arg_refs: []const ?*const RuntimeRef, arg_refs: []const ?*const RuntimeRef,
) RuntimeError!?*RuntimeRef { ) RuntimeError!?*RuntimeRef {
for (arg_refs) |arg_ref| { for (arg_refs) |arg_ref| {
@ -436,21 +469,20 @@ pub fn call(
pub fn get( pub fn get(
env: *RuntimeEnv, env: *RuntimeEnv,
indexable_ref: ?*const RuntimeRef, indexable_ref: *const RuntimeRef,
index_ref: ?*const RuntimeRef, index_ref: *const RuntimeRef,
) RuntimeError!?*RuntimeRef { ) RuntimeError!?*RuntimeRef {
const dynamic = try unbox_dynamic(env, indexable_ref); const dynamic = try unbox_dynamic(env, indexable_ref);
return dynamic.typeinfo.get(.{ return dynamic.typeinfo.get(.{
.userdata = dynamic.userdata, .userdata = dynamic.userdata,
.env = env, .env = env,
.index_ref = index_ref, }, index_ref);
});
} }
pub fn get_field( pub fn get_field(
env: *RuntimeEnv, env: *RuntimeEnv,
indexable_ref: ?*const RuntimeRef, indexable_ref: *const RuntimeRef,
field_name: []const coral.io.Byte, field_name: []const coral.io.Byte,
) RuntimeError!?*RuntimeRef { ) RuntimeError!?*RuntimeRef {
const field_name_ref = try env.new_string(field_name); const field_name_ref = try env.new_string(field_name);
@ -462,8 +494,8 @@ pub fn get_field(
pub fn set( pub fn set(
env: *RuntimeEnv, env: *RuntimeEnv,
indexable_ref: ?*const RuntimeRef, indexable_ref: *const RuntimeRef,
index_ref: ?*const RuntimeRef, index_ref: *const RuntimeRef,
value_ref: ?*const RuntimeRef, value_ref: ?*const RuntimeRef,
) RuntimeError!void { ) RuntimeError!void {
const dynamic = try unbox_dynamic(env, indexable_ref); const dynamic = try unbox_dynamic(env, indexable_ref);
@ -477,7 +509,7 @@ pub fn set(
pub fn set_field( pub fn set_field(
env: *RuntimeEnv, env: *RuntimeEnv,
indexable_ref: ?*const RuntimeRef, indexable_ref: *const RuntimeRef,
field_name: []const coral.io.Byte, field_name: []const coral.io.Byte,
value_ref: ?*const RuntimeRef, value_ref: ?*const RuntimeRef,
) RuntimeError!void { ) RuntimeError!void {
@ -490,9 +522,8 @@ pub fn set_field(
pub const new_table = Table.new; 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)) { return switch (env.unbox(lhs_ref)) {
.nil => env.raise(error.TypeMismatch, "cannot compare nil objects"),
.boolean => env.raise(error.TypeMismatch, "cannot compare boolean objects"), .boolean => env.raise(error.TypeMismatch, "cannot compare boolean objects"),
.number => |lhs_number| switch (env.unbox(rhs_ref)) { .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 => |lhs_string| switch (env.unbox(rhs_ref)) {
.string => |rhs_string| @floatFromInt(coral.io.compare(lhs_string, rhs_string)), .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"), 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(.{ .dynamic => |lhs_dynamic| lhs_dynamic.typeinfo.test_difference(.{
.env = env, .env = env,
.userdata = lhs_dynamic.userdata, .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)) { return switch (env.unbox(lhs_ref)) {
.nil => (rhs_ref == null),
.boolean => |lhs_boolean| switch (env.unbox(rhs_ref)) { .boolean => |lhs_boolean| switch (env.unbox(rhs_ref)) {
.boolean => |rhs_boolean| rhs_boolean == lhs_boolean, .boolean => |rhs_boolean| rhs_boolean == lhs_boolean,
else => false, 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 => |lhs_string| switch (env.unbox(rhs_ref)) {
.string => |rhs_string| coral.io.equals(lhs_string, rhs_string), .string => |rhs_string| coral.io.equals(lhs_string, rhs_string),
.symbol => |rhs_symbol| coral.io.equals(lhs_string, rhs_symbol),
else => false, 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, .env = env,
.userdata = lhs_dynamic.userdata, .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"); 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"); return env.unbox(ref).expect_string() orelse env.raise(error.TypeMismatch, "expected string object");
} }

View File

@ -15,7 +15,8 @@ pub const Expression = union (enum) {
false_literal, false_literal,
number_literal: []const coral.io.Byte, number_literal: []const coral.io.Byte,
string_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, grouped_expression: *Expression,
get_local: []const coral.io.Byte, get_local: []const coral.io.Byte,
@ -61,9 +62,9 @@ pub const Expression = union (enum) {
} }
}; };
pub const NamedList = coral.list.Stack(struct { pub const TableLiteral = coral.list.Stack(struct {
identifier: []const coral.io.Byte, key_expression: Expression,
expression: Expression, value_expression: Expression,
}); });
pub const List = coral.list.Stack(Expression); pub const List = coral.list.Stack(Expression);
@ -397,7 +398,7 @@ fn parse_factor(self: *Self) ParseError!Expression {
}, },
.symbol_brace_left => { .symbol_brace_left => {
var table_fields = Expression.NamedList.make(allocator); var table_literal = Expression.TableLiteral.make(allocator);
self.tokenizer.skip(.newline); self.tokenizer.skip(.newline);
@ -406,14 +407,30 @@ fn parse_factor(self: *Self) ParseError!Expression {
.symbol_brace_right => { .symbol_brace_right => {
self.tokenizer.step(); self.tokenizer.step();
return Expression{.table_literal = table_fields}; return Expression{.table_literal = table_literal};
}, },
.identifier => |identifier| { .symbol_bracket_left => {
self.tokenizer.skip(.newline); self.tokenizer.skip(.newline);
if (!self.tokenizer.is_token(.symbol_equals)) { 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); self.tokenizer.skip(.newline);
@ -422,9 +439,9 @@ fn parse_factor(self: *Self) ParseError!Expression {
return self.report("unexpected end after `=`"); return self.report("unexpected end after `=`");
} }
try table_fields.push_one(.{ try table_literal.push_one(.{
.expression = try self.parse_expression(), .value_expression = try self.parse_expression(),
.identifier = identifier, .key_expression = .{.symbol_literal = identifier},
}); });
switch (self.tokenizer.token orelse return self.report("unexpected end of table")) { 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 => { .symbol_brace_right => {
self.tokenizer.step(); self.tokenizer.step();
return Expression{.table_literal = table_fields}; return Expression{.table_literal = table_literal};
}, },
else => return self.report("expected `,` or `}` after expression"), else => return self.report("expected `,` or `}` after expression"),

View File

@ -38,17 +38,18 @@ const AstCompiler = struct {
try self.chunk.append_opcode(.{.push_const = try self.chunk.declare_constant_string(literal)}); 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| { .table_literal => |fields| {
if (fields.values.len > coral.math.max_int(@typeInfo(u32).Int)) { if (fields.values.len > coral.math.max_int(@typeInfo(u32).Int)) {
return error.OutOfMemory; return error.OutOfMemory;
} }
for (fields.values) |field| { for (fields.values) |field| {
try self.compile_expression(field.expression); try self.compile_expression(field.value_expression);
try self.compile_expression(field.key_expression);
try self.chunk.append_opcode(.{
.push_const = try self.chunk.declare_constant_string(field.identifier),
});
} }
try self.chunk.append_opcode(.{.push_table = @intCast(fields.values.len)}); 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 RefList = coral.list.Stack(?*kym.RuntimeRef);
const LocalsList = coral.list.Stack([]const u8); const LocalsList = coral.list.Stack([]const u8);
@ -176,7 +175,7 @@ pub const Opcode = union (enum) {
push_nil, push_nil,
push_true, push_true,
push_false, push_false,
push_const: Constant, push_const: u16,
push_local: u8, push_local: u8,
push_table: u32, push_table: u32,
set_local: u8, set_local: u8,
@ -223,7 +222,7 @@ pub fn compile_ast(self: *Self, ast: Ast) kym.RuntimeError!void {
try self.opcodes.pack(); 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; const tail = self.constant_refs.values.len;
if (tail == coral.math.max_int(@typeInfo(u16).Int)) { 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); 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; const tail = self.constant_refs.values.len;
if (tail == coral.math.max_int(@typeInfo(u16).Int)) { 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); 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 { fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef {
for (self.opcodes.values) |opcode| { for (self.opcodes.values) |opcode| {
switch (opcode) { switch (opcode) {
@ -274,7 +289,9 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef
var popped = @as(usize, 0); var popped = @as(usize, 0);
while (popped < field_count) : (popped += 1) { 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); defer env.discard(index_ref);
@ -285,8 +302,7 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef
try dynamic.typeinfo.set(.{ try dynamic.typeinfo.set(.{
.userdata = dynamic.userdata, .userdata = dynamic.userdata,
.env = env, .env = env,
.index_ref = index_ref, }, index_ref, value_ref);
}, value_ref);
} }
} }
@ -311,7 +327,9 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef
.call => |arg_count| { .call => |arg_count| {
const result_ref = call: { 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); defer env.discard(callable_ref);
@ -325,7 +343,9 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef
.syscall => |arg_count| { .syscall => |arg_count| {
const result_ref = call: { 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); 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); try env.push_ref(result_ref);
}, },
.neg => try env.push_number(switch (env.unbox(try env.pop_local())) { .neg => {
const ref = (try env.pop_local()) orelse {
return env.raise(error.TypeMismatch, "null is not scalar negatable");
};
defer env.discard(ref);
try env.push_number(switch (env.unbox(ref)) {
.number => |number| -number, .number => |number| -number,
else => return env.raise(error.TypeMismatch, "object is not scalar negatable"), else => return env.raise(error.TypeMismatch, "object is not scalar negatable"),
}), });
},
.not => try env.push_boolean(switch (env.unbox(try env.pop_local())) { .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, .boolean => |boolean| !boolean,
else => return env.raise(error.TypeMismatch, "object is not boolean negatable"), else => return env.raise(error.TypeMismatch, "object is not boolean negatable"),
}), });
},
.add => { .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); 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); defer env.discard(lhs_ref);
@ -369,11 +409,15 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef
}, },
.sub => { .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); 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); defer env.discard(lhs_ref);
@ -388,11 +432,15 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef
}, },
.mul => { .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); 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); defer env.discard(lhs_ref);
@ -407,11 +455,15 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef
}, },
.div => { .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); 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); defer env.discard(lhs_ref);
@ -426,11 +478,15 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef
}, },
.eql => { .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); 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); defer env.discard(lhs_ref);
@ -438,11 +494,15 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef
}, },
.cgt => { .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); 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); defer env.discard(lhs_ref);
@ -450,11 +510,15 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef
}, },
.clt => { .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); 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); defer env.discard(lhs_ref);
@ -462,11 +526,15 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef
}, },
.cge => { .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); 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); defer env.discard(lhs_ref);
@ -474,11 +542,15 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef
}, },
.cle => { .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); 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); defer env.discard(lhs_ref);

View File

@ -2,22 +2,43 @@ const coral = @import("coral");
const kym = @import("../kym.zig"); const kym = @import("../kym.zig");
fields: FieldTable, associative: AssociativeTable,
contiguous: ContiguousList,
const FieldTable = coral.map.StringTable(struct { // TODO: Modify hash traits to be support fat contexts rather than passing Envs into the table.
key_ref: ?*kym.RuntimeRef, const AssociativeTable = coral.map.Table(Field, *kym.RuntimeRef, .{
value_ref: ?*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(); 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{ var self = Self{
.fields = FieldTable.make(env.allocator), .associative = AssociativeTable.make(env.allocator),
.contiguous = ContiguousList.make(env.allocator),
}; };
errdefer { errdefer {
self.fields.free(); self.associative.free();
self.contiguous.free();
} }
return try env.new_dynamic(coral.io.bytes_of(&self), &typeinfo); return try env.new_dynamic(coral.io.bytes_of(&self), &typeinfo);
@ -30,62 +51,109 @@ const typeinfo = kym.Typeinfo{
.set = typeinfo_set, .set = typeinfo_set,
}; };
fn typeinfo_clean(env: *kym.RuntimeEnv, userdata: []coral.io.Byte) void { fn typeinfo_clean(context: kym.Context) void {
const table = @as(*Self, @ptrCast(@alignCast(userdata.ptr))); 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| { while (field_iterable.next()) |entry| {
env.discard(entry.value.key_ref); context.env.discard(entry.key.ref);
env.discard(entry.value.value_ref); context.env.discard(entry.value);
} }
} }
table.fields.free(); table.associative.free();
while (table.contiguous.pop()) |ref| {
context.env.discard(ref);
} }
fn typeinfo_get(context: kym.IndexContext) kym.RuntimeError!?*kym.RuntimeRef { 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))); const table = @as(*Self, @ptrCast(@alignCast(context.userdata.ptr)));
return switch (context.env.unbox(context.index_ref)) { switch (context.env.unbox(index_ref)) {
.string => |string| context.env.acquire((table.fields.lookup(string) orelse return null).value_ref), .symbol, .string => {
// TODO: Implement number indices in tables. return context.env.acquire(table.associative.lookup(index_ref));
.number => |_| unreachable, },
else => context.env.raise(error.TypeMismatch, "table objects may only be indexed with strings or numbers"),
}; .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);
}
} }
fn typeinfo_set(context: kym.IndexContext, value_ref: ?*const kym.RuntimeRef) kym.RuntimeError!void { 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.Context, index_ref: *const kym.RuntimeRef, value_ref: ?*const kym.RuntimeRef) kym.RuntimeError!void {
const table = @as(*Self, @ptrCast(@alignCast(context.userdata.ptr))); const table = @as(*Self, @ptrCast(@alignCast(context.userdata.ptr)));
switch (context.env.unbox(context.index_ref)) {
.symbol, .string => {
const acquired_index_ref = context.env.acquire(index_ref);
errdefer context.env.discard(acquired_index_ref);
const acquired_value_ref = context.env.acquire(value_ref); const acquired_value_ref = context.env.acquire(value_ref);
errdefer context.env.discard(acquired_value_ref); errdefer context.env.discard(acquired_value_ref);
switch (context.env.unbox(context.index_ref)) { if (try table.associative.replace(acquired_index_ref, acquired_value_ref)) |replaced| {
.string => |string| { context.env.discard(replaced.key);
const acquired_index_ref = context.env.acquire(context.index_ref); context.env.discard(replaced.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); errdefer context.env.discard(acquired_index_ref);
var displaced_table_entry = if (acquired_value_ref) |ref| try table.fields.replace(string, .{ try table.contiguous.push_one(acquired_index_ref);
.key_ref = acquired_index_ref,
.value_ref = ref,
}) else table.fields.remove(string);
if (displaced_table_entry) |*entry| { return;
context.env.discard(entry.value.key_ref); }
context.env.discard(entry.value.value_ref); }
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);
} }
}, },
.number => |_| { else => return context.env.raise(error.TypeMismatch, "expected symbol, string, or number index"),
// TODO: Implement number indices in tables.
unreachable;
},
else => {
return context.env.raise(error.TypeMismatch, "table objects may only be indexed with strings or numbers");
},
} }
} }

View File

@ -27,19 +27,25 @@ fn kym_handle_errors(info: kym.ErrorInfo) void {
} }
fn kym_log_info(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef { 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; return null;
} }
fn kym_log_warn(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef { 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; return null;
} }
fn kym_log_fail(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef { 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; return null;
} }