Partially complete work on table overhaul
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				continuous-integration/drone/push Build is passing
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	continuous-integration/drone/push Build is passing
				
			This commit is contained in:
		
							parent
							
								
									894dabb2c4
								
							
						
					
					
						commit
						0141c9c2ed
					
				
							
								
								
									
										3
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							@ -7,7 +7,8 @@
 | 
			
		||||
			"request": "launch",
 | 
			
		||||
			"target": "${workspaceRoot}/zig-out/bin/runner",
 | 
			
		||||
			"cwd": "${workspaceRoot}/debug/",
 | 
			
		||||
			"valuesFormatting": "parseText"
 | 
			
		||||
			"valuesFormatting": "parseText",
 | 
			
		||||
			"preLaunchTask": "Build All"
 | 
			
		||||
		},
 | 
			
		||||
	]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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,
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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]);
 | 
			
		||||
 | 
			
		||||
@ -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");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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"),
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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"),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user