Refactor and clean up virtual machine
This commit is contained in:
		
							parent
							
								
									1f8f3fd9dc
								
							
						
					
					
						commit
						1f9df3fc1d
					
				@ -1,17 +1,13 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
# Test comment.
 | 
					# Test comment.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
test = {
 | 
					options = {
 | 
				
			||||||
	.message = "don't you lecture me with your 30 dollar scripting language"
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
test.message = "game is loading"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@log_info(test.message)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
return {
 | 
					 | 
				
			||||||
	.title = "Afterglow",
 | 
						.title = "Afterglow",
 | 
				
			||||||
	.width = 1280,
 | 
						.width = 1280,
 | 
				
			||||||
	.height = 800,
 | 
						.height = 800,
 | 
				
			||||||
	.tick_rate = 60,
 | 
						.tick_rate = 60,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# @log_info(options.title)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					return options
 | 
				
			||||||
 | 
				
			|||||||
@ -255,6 +255,16 @@ pub fn allocate_one(allocator: Allocator, value: anytype) AllocationError!*@Type
 | 
				
			|||||||
	return allocation;
 | 
						return allocation;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn allocate_string(allocator: Allocator, source: []const Byte) AllocationError![:0]Byte {
 | 
				
			||||||
 | 
						const allocation = try allocator.actions.reallocate(allocator.context, @returnAddress(), null, source.len + 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						copy(allocation[0 .. source.len], source);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						allocation[source.len] = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return @ptrCast(allocation);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn bytes_of(value: anytype) []const Byte {
 | 
					pub fn bytes_of(value: anytype) []const Byte {
 | 
				
			||||||
	const pointer_info = @typeInfo(@TypeOf(value)).Pointer;
 | 
						const pointer_info = @typeInfo(@TypeOf(value)).Pointer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -330,6 +340,16 @@ pub fn equals(target: []const Byte, match: []const Byte) bool {
 | 
				
			|||||||
	return true;
 | 
						return true;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn find_first(haystack: []const Byte, needle: Byte) ?usize {
 | 
				
			||||||
 | 
						for (0 .. haystack.len) |i| {
 | 
				
			||||||
 | 
							if (haystack[i] == needle) {
 | 
				
			||||||
 | 
								return i;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return null;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var null_context = @as(usize, 0);
 | 
					var null_context = @as(usize, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub const null_writer = Writer.bind(usize, &null_context, struct {
 | 
					pub const null_writer = Writer.bind(usize, &null_context, struct {
 | 
				
			||||||
 | 
				
			|||||||
@ -6,112 +6,15 @@ const list = @import("./list.zig");
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const math = @import("./math.zig");
 | 
					const math = @import("./math.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn Slab(comptime Value: type) type {
 | 
					 | 
				
			||||||
	return struct {
 | 
					 | 
				
			||||||
		next_index: usize,
 | 
					 | 
				
			||||||
		entries: EntryList,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		const EntryList = list.Stack(union (enum) {
 | 
					 | 
				
			||||||
			value: Value,
 | 
					 | 
				
			||||||
			next_index: usize,
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		const Self = @This();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		pub fn lookup(self: Self, key: usize) ?Value {
 | 
					 | 
				
			||||||
			if (key == 0 or key > self.entries.values.len) {
 | 
					 | 
				
			||||||
				return null;
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			return switch (self.entries.values[key - 1]) {
 | 
					 | 
				
			||||||
				.value => |value| value,
 | 
					 | 
				
			||||||
				.next_index => null,
 | 
					 | 
				
			||||||
			};
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		pub fn free(self: *Self) void {
 | 
					 | 
				
			||||||
			self.entries.free();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			self.next_index = 0;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		pub fn insert(self: *Self, value: Value) io.AllocationError!usize {
 | 
					 | 
				
			||||||
			if (self.next_index < self.entries.values.len) {
 | 
					 | 
				
			||||||
				const index = self.next_index;
 | 
					 | 
				
			||||||
				const entry = &self.entries.values[index];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				debug.assert(entry.* == .next_index);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				self.next_index = entry.next_index;
 | 
					 | 
				
			||||||
				entry.* = .{.value = value};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				return index + 1;
 | 
					 | 
				
			||||||
			} else {
 | 
					 | 
				
			||||||
				try self.entries.push_one(.{.value = value});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				self.next_index += 1;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				return self.next_index;
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		pub fn insert_at(self: *Self, key: usize, value: Value) bool {
 | 
					 | 
				
			||||||
			if (self.entries.values.len < key) {
 | 
					 | 
				
			||||||
				return false;
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			const entry = &self.entries.values[key - 1];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			if (entry.* == .value) {
 | 
					 | 
				
			||||||
				return false;
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			self.next_index = entry.next_index;
 | 
					 | 
				
			||||||
			entry.* = .{.value = value};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			return true;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		pub fn make(allocator: io.Allocator) Self {
 | 
					 | 
				
			||||||
			return .{
 | 
					 | 
				
			||||||
				.next_index = 0,
 | 
					 | 
				
			||||||
				.entries = EntryList.make(allocator),
 | 
					 | 
				
			||||||
			};
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		pub fn remove(self: *Self, key: usize) ?Value {
 | 
					 | 
				
			||||||
			if (key == 0 or key > self.entries.values.len) {
 | 
					 | 
				
			||||||
				return null;
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			const index = key - 1;
 | 
					 | 
				
			||||||
			const entry = &self.entries.values[index];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			return switch (entry.*) {
 | 
					 | 
				
			||||||
				.next_index => null,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				.value => get_value: {
 | 
					 | 
				
			||||||
					const value = entry.value;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					entry.* = .{.next_index = self.next_index};
 | 
					 | 
				
			||||||
					self.next_index = index;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					break: get_value value;
 | 
					 | 
				
			||||||
				},
 | 
					 | 
				
			||||||
			};
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn StringTable(comptime Value: type) type {
 | 
					pub fn StringTable(comptime Value: type) type {
 | 
				
			||||||
	return Table([]const io.Byte, Value, struct {
 | 
						return Table([]const io.Byte, Value, struct {
 | 
				
			||||||
		const Self = @This();
 | 
							const Self = @This();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		fn equals(_: Self, key_a: []const io.Byte, key_b: []const io.Byte) bool {
 | 
							fn equals(key_a: []const io.Byte, key_b: []const io.Byte) bool {
 | 
				
			||||||
			return io.equals(key_a, key_b);
 | 
								return io.equals(key_a, key_b);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		fn hash(_: Self, key: []const io.Byte) usize {
 | 
							fn hash(key: []const io.Byte) usize {
 | 
				
			||||||
			return io.djb2_hash(@typeInfo(usize).Int, key);
 | 
								return io.djb2_hash(@typeInfo(usize).Int, key);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
@ -119,9 +22,7 @@ pub fn StringTable(comptime Value: type) type {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
pub fn Table(comptime Key: type, comptime Value: type, comptime Traits: type) type {
 | 
					pub fn Table(comptime Key: type, comptime Value: type, comptime Traits: type) type {
 | 
				
			||||||
	const load_max = 0.75;
 | 
						const load_max = 0.75;
 | 
				
			||||||
	const hash_int = @typeInfo(usize).Int;
 | 
						const max_int = math.max_int(@typeInfo(usize).Int);
 | 
				
			||||||
	const max_int = math.max_int(hash_int);
 | 
					 | 
				
			||||||
	const min_int = math.min_int(hash_int);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return struct {
 | 
						return struct {
 | 
				
			||||||
		allocator: io.Allocator,
 | 
							allocator: io.Allocator,
 | 
				
			||||||
@ -135,7 +36,7 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime Traits: type) ty
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
			fn write_into(self: Entry, table: *Self) bool {
 | 
								fn write_into(self: Entry, table: *Self) bool {
 | 
				
			||||||
				const hash_max = @min(max_int, table.entries.len);
 | 
									const hash_max = @min(max_int, table.entries.len);
 | 
				
			||||||
				var hashed_key = math.wrap(table.traits.hash(self.key), min_int, hash_max);
 | 
									var hashed_key = table.hash_key(self.key) % hash_max;
 | 
				
			||||||
				var iterations = @as(usize, 0);
 | 
									var iterations = @as(usize, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				while (true) : (iterations += 1) {
 | 
									while (true) : (iterations += 1) {
 | 
				
			||||||
@ -152,7 +53,7 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime Traits: type) ty
 | 
				
			|||||||
						return true;
 | 
											return true;
 | 
				
			||||||
					});
 | 
										});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					if (table.traits.equals(table_entry.key, self.key)) {
 | 
										if (table.keys_equal(table_entry.key, self.key)) {
 | 
				
			||||||
						return false;
 | 
											return false;
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -187,14 +88,18 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime Traits: type) ty
 | 
				
			|||||||
			};
 | 
								};
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							fn hash_key(self: Self, key: Key) usize {
 | 
				
			||||||
 | 
								return if (@sizeOf(Traits) == 0) Traits.hash(key) else self.traits.hash(key);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		pub fn remove(self: *Self, key: Key) ?Entry {
 | 
							pub fn remove(self: *Self, key: Key) ?Entry {
 | 
				
			||||||
			const hash_max = @min(max_int, self.entries.len);
 | 
								const hash_max = @min(max_int, self.entries.len);
 | 
				
			||||||
			var hashed_key = math.wrap(self.traits.hash(key), min_int, hash_max);
 | 
								var hashed_key = self.hash_key(key) % hash_max;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			while (true) {
 | 
								while (true) {
 | 
				
			||||||
				const entry = &(self.entries[hashed_key] orelse continue);
 | 
									const entry = &(self.entries[hashed_key] orelse continue);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				if (self.traits.equals(entry.key, key)) {
 | 
									if (self.keys_equal(entry.key, key)) {
 | 
				
			||||||
					const original_entry = entry.*;
 | 
										const original_entry = entry.*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					self.entries[hashed_key] = null;
 | 
										self.entries[hashed_key] = null;
 | 
				
			||||||
@ -213,7 +118,8 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime Traits: type) ty
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
			{
 | 
								{
 | 
				
			||||||
				const hash_max = @min(max_int, self.entries.len);
 | 
									const hash_max = @min(max_int, self.entries.len);
 | 
				
			||||||
				var hashed_key = math.wrap(self.traits.hash(key), min_int, hash_max);
 | 
									const has_context = @sizeOf(Traits) != 0;
 | 
				
			||||||
 | 
									var hashed_key = (if (has_context) self.traits.hash(key) else Traits.hash(key)) % hash_max;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				while (true) {
 | 
									while (true) {
 | 
				
			||||||
					const entry = &(self.entries[hashed_key] orelse {
 | 
										const entry = &(self.entries[hashed_key] orelse {
 | 
				
			||||||
@ -227,15 +133,28 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime Traits: type) ty
 | 
				
			|||||||
						return null;
 | 
											return null;
 | 
				
			||||||
					});
 | 
										});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					if (self.traits.equals(entry.key, key)) {
 | 
										if (has_context) {
 | 
				
			||||||
						const original_entry = entry.*;
 | 
											if (self.traits.equals(entry.key, key)) {
 | 
				
			||||||
 | 
												const original_entry = entry.*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
						entry.* = .{
 | 
												entry.* = .{
 | 
				
			||||||
							.key = key,
 | 
													.key = key,
 | 
				
			||||||
							.value = value,
 | 
													.value = value,
 | 
				
			||||||
						};
 | 
												};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
						return original_entry;
 | 
												return original_entry;
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										} else {
 | 
				
			||||||
 | 
											if (Traits.equals(entry.key, key)) {
 | 
				
			||||||
 | 
												const original_entry = entry.*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
												entry.* = .{
 | 
				
			||||||
 | 
													.key = key,
 | 
				
			||||||
 | 
													.value = value,
 | 
				
			||||||
 | 
												};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
												return original_entry;
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					hashed_key = (hashed_key +% 1) % hash_max;
 | 
										hashed_key = (hashed_key +% 1) % hash_max;
 | 
				
			||||||
@ -279,20 +198,35 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime Traits: type) ty
 | 
				
			|||||||
			return entry.write_into(self);
 | 
								return entry.write_into(self);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							fn keys_equal(self: Self, key_a: Key, key_b: Key) bool {
 | 
				
			||||||
 | 
								if (@sizeOf(Traits) == 0) {
 | 
				
			||||||
 | 
									return Traits.equals(key_a, key_b);
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									return self.traits.equals(key_a, key_b);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		pub fn lookup(self: Self, key: Key) ?Value {
 | 
							pub fn lookup(self: Self, key: Key) ?Value {
 | 
				
			||||||
			if (self.count == 0) {
 | 
								if (self.count == 0) {
 | 
				
			||||||
				return null;
 | 
									return null;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			const hash_max = @min(max_int, self.entries.len);
 | 
								const hash_max = @min(max_int, self.entries.len);
 | 
				
			||||||
			var hashed_key = math.wrap(self.traits.hash(key), min_int, hash_max);
 | 
								const has_context = @sizeOf(Traits) != 0;
 | 
				
			||||||
 | 
								var hashed_key = (if (has_context) self.traits.hash(key) else Traits.hash(key)) % hash_max;
 | 
				
			||||||
			var iterations = @as(usize, 0);
 | 
								var iterations = @as(usize, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			while (iterations < self.count) : (iterations += 1) {
 | 
								while (iterations < self.count) : (iterations += 1) {
 | 
				
			||||||
				const entry = &(self.entries[hashed_key] orelse return null);
 | 
									const entry = &(self.entries[hashed_key] orelse return null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				if (self.traits.equals(entry.key, key)) {
 | 
									if (has_context) {
 | 
				
			||||||
					return entry.value;
 | 
										if (self.traits.equals(entry.key, key)) {
 | 
				
			||||||
 | 
											return entry.value;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										if (Traits.equals(entry.key, key)) {
 | 
				
			||||||
 | 
											return entry.value;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				hashed_key = (hashed_key +% 1) % hash_max;
 | 
									hashed_key = (hashed_key +% 1) % hash_max;
 | 
				
			||||||
 | 
				
			|||||||
@ -60,9 +60,3 @@ pub fn min_int(comptime int: std.builtin.Type.Int) comptime_int {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	return -(1 << (bit_count - 1));
 | 
						return -(1 << (bit_count - 1));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn wrap(value: anytype, lower: anytype, upper: anytype) @TypeOf(value, lower, upper) {
 | 
					 | 
				
			||||||
	const range = upper - lower;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return if (range == 0) lower else lower + @mod((@mod((value - lower), range) + range), range);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -13,20 +13,18 @@ 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"})) orelse return;
 | 
							const manifest = try env.expect(try env.execute_file(file_access, file.Path.from(&.{"app.ona"})));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		defer env.discard(manifest_ref);
 | 
							defer env.discard(manifest);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const width = @as(u16, get: {
 | 
							const width = @as(u16, get: {
 | 
				
			||||||
			const ref = try kym.get_field(env, manifest_ref, "width");
 | 
								if (try env.get_field(manifest, "width")) |ref| {
 | 
				
			||||||
 | 
									defer env.discard(ref);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			defer env.discard(ref);
 | 
									const fixed = try env.unbox_fixed(ref);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if (ref) |live_ref| {
 | 
									if (fixed > 0 and fixed < coral.math.max_int(@typeInfo(@TypeOf(self.width)).Int)) {
 | 
				
			||||||
				if (env.unbox(live_ref).expect_number()) |number| {
 | 
										break: get @intCast(fixed);
 | 
				
			||||||
					if (number > 0 and number < coral.math.max_int(@typeInfo(@TypeOf(self.width)).Int)) {
 | 
					 | 
				
			||||||
						break: get @intFromFloat(number);
 | 
					 | 
				
			||||||
					}
 | 
					 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -34,15 +32,13 @@ pub const Manifest = struct {
 | 
				
			|||||||
		});
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const height = @as(u16, get: {
 | 
							const height = @as(u16, get: {
 | 
				
			||||||
			const ref = try kym.get_field(env, manifest_ref, "height");
 | 
								if (try env.get_field(manifest, "height")) |ref| {
 | 
				
			||||||
 | 
									defer env.discard(ref);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			defer env.discard(ref);
 | 
									const fixed = try env.unbox_fixed(ref);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if (ref) |live_ref| {
 | 
									if (fixed > 0 and fixed < coral.math.max_int(@typeInfo(@TypeOf(self.height)).Int)) {
 | 
				
			||||||
				if (env.unbox(live_ref).expect_number()) |number| {
 | 
										break: get @intCast(fixed);
 | 
				
			||||||
					if (number > 0 and number < coral.math.max_int(@typeInfo(@TypeOf(self.height)).Int)) {
 | 
					 | 
				
			||||||
						break: get @intFromFloat(number);
 | 
					 | 
				
			||||||
					}
 | 
					 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -50,38 +46,25 @@ pub const Manifest = struct {
 | 
				
			|||||||
		});
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const tick_rate = @as(f32, get: {
 | 
							const tick_rate = @as(f32, get: {
 | 
				
			||||||
			const ref = try kym.get_field(env, manifest_ref, "tick_rate");
 | 
								if (try env.get_field(manifest, "tick_rate")) |ref| {
 | 
				
			||||||
 | 
									defer env.discard(ref);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			defer env.discard(ref);
 | 
									break: get @floatCast(try env.unbox_float(ref));
 | 
				
			||||||
 | 
					 | 
				
			||||||
			if (ref) |live_ref| {
 | 
					 | 
				
			||||||
				if (env.unbox(live_ref).expect_number()) |number| {
 | 
					 | 
				
			||||||
					break: get @floatCast(number);
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			break: get self.tick_rate;
 | 
								break: get self.tick_rate;
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		{
 | 
							if (try env.get_field(manifest, "title")) |ref| {
 | 
				
			||||||
			const ref = try kym.get_field(env, manifest_ref, "title");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			defer env.discard(ref);
 | 
								defer env.discard(ref);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			const title_string = unbox: {
 | 
								const title_string = try env.unbox_string(ref);
 | 
				
			||||||
				if (ref) |live_ref| {
 | 
					 | 
				
			||||||
					if (env.unbox(live_ref).expect_string()) |string| {
 | 
					 | 
				
			||||||
						break: unbox string;
 | 
					 | 
				
			||||||
					}
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				break: unbox "";
 | 
					 | 
				
			||||||
			};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			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]);
 | 
				
			||||||
			coral.io.zero(self.title[limited_title_len .. self.title.len]);
 | 
								coral.io.zero(self.title[limited_title_len .. self.title.len]);
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								coral.io.zero(&self.title);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		self.tick_rate = tick_rate;
 | 
							self.tick_rate = tick_rate;
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										1479
									
								
								source/ona/kym.zig
									
									
									
									
									
								
							
							
						
						
									
										1479
									
								
								source/ona/kym.zig
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -19,15 +19,15 @@ pub const Expression = union (enum) {
 | 
				
			|||||||
	table_literal: TableLiteral,
 | 
						table_literal: TableLiteral,
 | 
				
			||||||
	grouped_expression: *Expression,
 | 
						grouped_expression: *Expression,
 | 
				
			||||||
	get_system: []const coral.io.Byte,
 | 
						get_system: []const coral.io.Byte,
 | 
				
			||||||
	get_local: []const coral.io.Byte,
 | 
						local_get: []const coral.io.Byte,
 | 
				
			||||||
	set_local: []const coral.io.Byte,
 | 
						local_set: []const coral.io.Byte,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	get_field: struct {
 | 
						field_get: struct {
 | 
				
			||||||
		object_expression: *Expression,
 | 
							object_expression: *Expression,
 | 
				
			||||||
		identifier: []const coral.io.Byte,
 | 
							identifier: []const coral.io.Byte,
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	set_field: struct {
 | 
						field_set: struct {
 | 
				
			||||||
		object_expression: *Expression,
 | 
							object_expression: *Expression,
 | 
				
			||||||
		identifier: []const coral.io.Byte,
 | 
							identifier: []const coral.io.Byte,
 | 
				
			||||||
		value_expression: *Expression,
 | 
							value_expression: *Expression,
 | 
				
			||||||
@ -44,7 +44,7 @@ pub const Expression = union (enum) {
 | 
				
			|||||||
		expression: *Expression,
 | 
							expression: *Expression,
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	call: struct {
 | 
						invoke: struct {
 | 
				
			||||||
		object_expression: *Expression,
 | 
							object_expression: *Expression,
 | 
				
			||||||
		argument_expressions: List,
 | 
							argument_expressions: List,
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
@ -256,12 +256,12 @@ pub fn parse_expression(self: *Self) ParseError!Expression {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return switch (expression) {
 | 
							return switch (expression) {
 | 
				
			||||||
			.get_local => |get_local| .{.set_local = get_local},
 | 
								.local_get => |local_get| .{.local_set = local_get},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			.get_field => |get_field| .{
 | 
								.field_get => |field_get| .{
 | 
				
			||||||
				.set_field = .{
 | 
									.field_set = .{
 | 
				
			||||||
					.object_expression = get_field.object_expression,
 | 
										.object_expression = field_get.object_expression,
 | 
				
			||||||
					.identifier = get_field.identifier,
 | 
										.identifier = field_get.identifier,
 | 
				
			||||||
					.value_expression = try coral.io.allocate_one(allocator, try self.parse_expression()),
 | 
										.value_expression = try coral.io.allocate_one(allocator, try self.parse_expression()),
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
@ -329,7 +329,7 @@ fn parse_factor(self: *Self) ParseError!Expression {
 | 
				
			|||||||
			.identifier => |identifier| {
 | 
								.identifier => |identifier| {
 | 
				
			||||||
				self.tokenizer.skip_newlines();
 | 
									self.tokenizer.skip_newlines();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				break: parse .{.get_local = identifier};
 | 
									break: parse .{.local_get = identifier};
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			.system_identifier => |system_identifier| {
 | 
								.system_identifier => |system_identifier| {
 | 
				
			||||||
@ -445,7 +445,7 @@ fn parse_factor(self: *Self) ParseError!Expression {
 | 
				
			|||||||
				const unnecessary_temp = try coral.io.allocate_one(allocator, expression);
 | 
									const unnecessary_temp = try coral.io.allocate_one(allocator, expression);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				expression = .{
 | 
									expression = .{
 | 
				
			||||||
					.get_field = .{
 | 
										.field_get = .{
 | 
				
			||||||
						.identifier = switch (self.tokenizer.token) {
 | 
											.identifier = switch (self.tokenizer.token) {
 | 
				
			||||||
							.identifier => |field_identifier| field_identifier,
 | 
												.identifier => |field_identifier| field_identifier,
 | 
				
			||||||
							else => return self.report("expected identifier after `.`"),
 | 
												else => return self.report("expected identifier after `.`"),
 | 
				
			||||||
@ -485,7 +485,7 @@ fn parse_factor(self: *Self) ParseError!Expression {
 | 
				
			|||||||
				const unnecessary_temp = try coral.io.allocate_one(allocator, expression);
 | 
									const unnecessary_temp = try coral.io.allocate_one(allocator, expression);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				expression = .{
 | 
									expression = .{
 | 
				
			||||||
					.call = .{
 | 
										.invoke = .{
 | 
				
			||||||
						.argument_expressions = argument_expressions,
 | 
											.argument_expressions = argument_expressions,
 | 
				
			||||||
						.object_expression = unnecessary_temp,
 | 
											.object_expression = unnecessary_temp,
 | 
				
			||||||
					},
 | 
										},
 | 
				
			||||||
 | 
				
			|||||||
@ -1,663 +0,0 @@
 | 
				
			|||||||
const Ast = @import("./Ast.zig");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const coral = @import("coral");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const kym = @import("../kym.zig");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
env: *kym.RuntimeEnv,
 | 
					 | 
				
			||||||
constant_refs: RefList,
 | 
					 | 
				
			||||||
opcodes: OpcodeList,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const AstCompiler = struct {
 | 
					 | 
				
			||||||
	chunk: *Self,
 | 
					 | 
				
			||||||
	local_identifiers_buffer: [255][]const coral.io.Byte = [_][]const coral.io.Byte{""} ** 255,
 | 
					 | 
				
			||||||
	local_identifiers_count: u8 = 0,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	fn compile_expression(self: *AstCompiler, expression: Ast.Expression) kym.RuntimeError!void {
 | 
					 | 
				
			||||||
		const number_format = coral.utf8.DecimalFormat{
 | 
					 | 
				
			||||||
			.delimiter = "_",
 | 
					 | 
				
			||||||
			.positive_prefix = .none,
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		switch (expression) {
 | 
					 | 
				
			||||||
			.nil_literal => try self.chunk.append_opcode(.push_nil),
 | 
					 | 
				
			||||||
			.true_literal => try self.chunk.append_opcode(.push_true),
 | 
					 | 
				
			||||||
			.false_literal => try self.chunk.append_opcode(.push_false),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			.number_literal => |literal| {
 | 
					 | 
				
			||||||
				for (literal) |codepoint| {
 | 
					 | 
				
			||||||
					if (codepoint == '.') {
 | 
					 | 
				
			||||||
						const parsed = number_format.parse(literal, kym.Float);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
						coral.debug.assert(parsed != null);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
						try self.chunk.append_opcode(.{
 | 
					 | 
				
			||||||
							.push_const = try self.chunk.declare_constant_float(parsed.?),
 | 
					 | 
				
			||||||
						});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
						return;
 | 
					 | 
				
			||||||
					}
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				const parsed = number_format.parse(literal, kym.Fixed);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				coral.debug.assert(parsed != null);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				try self.chunk.append_opcode(.{
 | 
					 | 
				
			||||||
					.push_const = try self.chunk.declare_constant_fixed(parsed.?),
 | 
					 | 
				
			||||||
				});
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			.string_literal => |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| {
 | 
					 | 
				
			||||||
				if (fields.values.len > coral.math.max_int(@typeInfo(u32).Int)) {
 | 
					 | 
				
			||||||
					return error.OutOfMemory;
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				for (fields.values) |field| {
 | 
					 | 
				
			||||||
					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)});
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			.binary_operation => |operation| {
 | 
					 | 
				
			||||||
				try self.compile_expression(operation.lhs_expression.*);
 | 
					 | 
				
			||||||
				try self.compile_expression(operation.rhs_expression.*);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				try self.chunk.append_opcode(switch (operation.operator) {
 | 
					 | 
				
			||||||
					.addition => .add,
 | 
					 | 
				
			||||||
					.subtraction => .sub,
 | 
					 | 
				
			||||||
					.multiplication => .mul,
 | 
					 | 
				
			||||||
					.divsion => .div,
 | 
					 | 
				
			||||||
					.greater_equals_comparison => .eql,
 | 
					 | 
				
			||||||
					.greater_than_comparison => .cgt,
 | 
					 | 
				
			||||||
					.equals_comparison => .cge,
 | 
					 | 
				
			||||||
					.less_than_comparison => .clt,
 | 
					 | 
				
			||||||
					.less_equals_comparison => .cle,
 | 
					 | 
				
			||||||
				});
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			.unary_operation => |operation| {
 | 
					 | 
				
			||||||
				try self.compile_expression(operation.expression.*);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				try self.chunk.append_opcode(switch (operation.operator) {
 | 
					 | 
				
			||||||
					.boolean_negation => .not,
 | 
					 | 
				
			||||||
					.numeric_negation => .neg,
 | 
					 | 
				
			||||||
				});
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			.call => |call| {
 | 
					 | 
				
			||||||
				if (call.argument_expressions.values.len > coral.math.max_int(@typeInfo(u8).Int)) {
 | 
					 | 
				
			||||||
					return self.chunk.env.raise(error.BadSyntax, "lambdas may contain a maximum of 255 arguments");
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				for (call.argument_expressions.values) |argument_expression| {
 | 
					 | 
				
			||||||
					try self.compile_expression(argument_expression);
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				try self.compile_expression(call.object_expression.*);
 | 
					 | 
				
			||||||
				try self.chunk.append_opcode(.{.call = @intCast(call.argument_expressions.values.len)});
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			.grouped_expression => |grouped_expression| {
 | 
					 | 
				
			||||||
				try self.compile_expression(grouped_expression.*);
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			.get_system => |get_system| {
 | 
					 | 
				
			||||||
				try self.chunk.append_opcode(.{.push_system = try self.chunk.declare_constant_string(get_system)});
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			.get_local => |get_local| {
 | 
					 | 
				
			||||||
				try self.chunk.append_opcode(.{
 | 
					 | 
				
			||||||
					.push_local = self.resolve_local(get_local) orelse {
 | 
					 | 
				
			||||||
						return self.chunk.env.raise(error.OutOfMemory, "undefined local");
 | 
					 | 
				
			||||||
					},
 | 
					 | 
				
			||||||
				});
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			.set_local => |set_local| {
 | 
					 | 
				
			||||||
				if (self.resolve_local(set_local)) |index| {
 | 
					 | 
				
			||||||
					try self.chunk.append_opcode(.{.set_local = index});
 | 
					 | 
				
			||||||
				} else {
 | 
					 | 
				
			||||||
					try self.declare_local(set_local);
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			.get_field => |get_field| {
 | 
					 | 
				
			||||||
				try self.compile_expression(get_field.object_expression.*);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				try self.chunk.append_opcode(.{
 | 
					 | 
				
			||||||
					.push_const = try self.chunk.declare_constant_symbol(get_field.identifier),
 | 
					 | 
				
			||||||
				});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				try self.chunk.append_opcode(.get_dynamic);
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			.set_field => |set_field| {
 | 
					 | 
				
			||||||
				try self.compile_expression(set_field.object_expression.*);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				try self.chunk.append_opcode(.{
 | 
					 | 
				
			||||||
					.push_const = try self.chunk.declare_constant_symbol(set_field.identifier),
 | 
					 | 
				
			||||||
				});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				try self.compile_expression(set_field.value_expression.*);
 | 
					 | 
				
			||||||
				try self.chunk.append_opcode(.set_dynamic);
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	fn compile_statement(self: *AstCompiler, statement: Ast.Statement) kym.RuntimeError!void {
 | 
					 | 
				
			||||||
		switch (statement) {
 | 
					 | 
				
			||||||
			.@"return" => |@"return"| {
 | 
					 | 
				
			||||||
				if (@"return".expression) |expression| {
 | 
					 | 
				
			||||||
					try self.compile_expression(expression);
 | 
					 | 
				
			||||||
				} else {
 | 
					 | 
				
			||||||
					try self.chunk.append_opcode(.push_nil);
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			.expression => |expression| try self.compile_expression(expression),
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	fn declare_local(self: *AstCompiler, identifier: []const u8) kym.RuntimeError!void {
 | 
					 | 
				
			||||||
		if (self.local_identifiers_count == self.local_identifiers_buffer.len) {
 | 
					 | 
				
			||||||
			return self.chunk.env.raise(error.OutOfMemory, "lambdas may contain a maximum of 255 locals");
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		self.local_identifiers_buffer[self.local_identifiers_count] = identifier;
 | 
					 | 
				
			||||||
		self.local_identifiers_count += 1;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	fn resolve_local(self: *AstCompiler, local_identifier: []const coral.io.Byte) ?u8 {
 | 
					 | 
				
			||||||
		if (self.local_identifiers_count == 0) {
 | 
					 | 
				
			||||||
			return null;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		var index = @as(u8, self.local_identifiers_count - 1);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		while (true) : (index -= 1) {
 | 
					 | 
				
			||||||
			if (coral.io.equals(local_identifier, self.local_identifiers_buffer[index])) {
 | 
					 | 
				
			||||||
				return index;
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			if (index == 0) {
 | 
					 | 
				
			||||||
				return null;
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const RefList = coral.list.Stack(*kym.RuntimeRef);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const LocalsList = coral.list.Stack([]const u8);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub const Opcode = union (enum) {
 | 
					 | 
				
			||||||
	pop,
 | 
					 | 
				
			||||||
	push_nil,
 | 
					 | 
				
			||||||
	push_true,
 | 
					 | 
				
			||||||
	push_false,
 | 
					 | 
				
			||||||
	push_const: u16,
 | 
					 | 
				
			||||||
	push_local: u8,
 | 
					 | 
				
			||||||
	push_table: u32,
 | 
					 | 
				
			||||||
	push_system: u16,
 | 
					 | 
				
			||||||
	set_local: u8,
 | 
					 | 
				
			||||||
	get_dynamic,
 | 
					 | 
				
			||||||
	set_dynamic,
 | 
					 | 
				
			||||||
	call: u8,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	not,
 | 
					 | 
				
			||||||
	neg,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	add,
 | 
					 | 
				
			||||||
	sub,
 | 
					 | 
				
			||||||
	mul,
 | 
					 | 
				
			||||||
	div,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	eql,
 | 
					 | 
				
			||||||
	cgt,
 | 
					 | 
				
			||||||
	clt,
 | 
					 | 
				
			||||||
	cge,
 | 
					 | 
				
			||||||
	cle,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const OpcodeList = coral.list.Stack(Opcode);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const Self = @This();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn append_opcode(self: *Self, opcode: Opcode) kym.RuntimeError!void {
 | 
					 | 
				
			||||||
	return self.opcodes.push_one(opcode);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn as_caller(self: *Self) kym.Caller {
 | 
					 | 
				
			||||||
	return kym.Caller.bind(Self, self, execute);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn compile_ast(self: *Self, ast: Ast) kym.RuntimeError!void {
 | 
					 | 
				
			||||||
	try self.constant_refs.grow(coral.math.max_int(@typeInfo(u16).Int));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var compiler = AstCompiler{.chunk = self};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	for (ast.list_statements()) |statement| {
 | 
					 | 
				
			||||||
		try compiler.compile_statement(statement);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	try self.constant_refs.pack();
 | 
					 | 
				
			||||||
	try self.opcodes.pack();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn declare_constant_fixed(self: *Self, constant: kym.Fixed) kym.RuntimeError!u16 {
 | 
					 | 
				
			||||||
	const tail = self.constant_refs.values.len;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if (tail == coral.math.max_int(@typeInfo(u16).Int)) {
 | 
					 | 
				
			||||||
		return self.env.raise(error.BadSyntax, "functions may contain a maximum of 65,535 constants");
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const constant_ref = try self.env.new_fixed(constant);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	errdefer self.env.discard(constant_ref);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	try self.constant_refs.push_one(constant_ref);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return @intCast(tail);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn declare_constant_float(self: *Self, constant: kym.Float) kym.RuntimeError!u16 {
 | 
					 | 
				
			||||||
	const tail = self.constant_refs.values.len;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if (tail == coral.math.max_int(@typeInfo(u16).Int)) {
 | 
					 | 
				
			||||||
		return self.env.raise(error.BadSyntax, "functions may contain a maximum of 65,535 constants");
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const constant_ref = try self.env.new_float(constant);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	errdefer self.env.discard(constant_ref);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	try self.constant_refs.push_one(constant_ref);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return @intCast(tail);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn declare_constant_string(self: *Self, constant: []const coral.io.Byte) kym.RuntimeError!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_string(constant);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	errdefer self.env.discard(constant_ref);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	try self.constant_refs.push_one(constant_ref);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	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) {
 | 
					 | 
				
			||||||
			.pop => env.discard(try env.pop_local()),
 | 
					 | 
				
			||||||
			.push_nil => try env.push_ref(null),
 | 
					 | 
				
			||||||
			.push_true => try env.push_boolean(true),
 | 
					 | 
				
			||||||
			.push_false => try env.push_boolean(false),
 | 
					 | 
				
			||||||
			.push_const => |constant| try env.push_ref(self.constant_refs.values[constant]),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			.push_table => |field_count| {
 | 
					 | 
				
			||||||
				const table_ref = try kym.new_table(env);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				defer env.discard(table_ref);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				{
 | 
					 | 
				
			||||||
					const dynamic = try kym.unbox_dynamic(env, table_ref);
 | 
					 | 
				
			||||||
					var popped = @as(usize, 0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					while (popped < field_count) : (popped += 1) {
 | 
					 | 
				
			||||||
						const index_ref = try env.pop_local() orelse {
 | 
					 | 
				
			||||||
							return env.raise(error.TypeMismatch, "nil is not a valid index");
 | 
					 | 
				
			||||||
						};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
						defer env.discard(index_ref);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
						const value_ref = try env.pop_local();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
						defer env.discard(value_ref);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
						try dynamic.typeinfo.set(.{
 | 
					 | 
				
			||||||
							.userdata = dynamic.userdata,
 | 
					 | 
				
			||||||
							.env = env,
 | 
					 | 
				
			||||||
						}, index_ref, value_ref);
 | 
					 | 
				
			||||||
					}
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				try env.push_ref(table_ref);
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			.push_system => |push_system| {
 | 
					 | 
				
			||||||
				const system_ref = self.env.get_syscallable(try kym.unbox_string(
 | 
					 | 
				
			||||||
					self.env,
 | 
					 | 
				
			||||||
					self.constant_refs.values[push_system],
 | 
					 | 
				
			||||||
				));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				defer self.env.discard(system_ref);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				try self.env.push_ref(system_ref);
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			.push_local => |local| {
 | 
					 | 
				
			||||||
				const ref = try env.get_local(local);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				defer env.discard(ref);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				try env.push_ref(ref);
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			.set_local => |local| {
 | 
					 | 
				
			||||||
				const ref = try env.pop_local();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				defer env.discard(ref);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				try env.set_local(local, ref);
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			.get_dynamic => {
 | 
					 | 
				
			||||||
				const index_ref = try env.pop_local() orelse {
 | 
					 | 
				
			||||||
					return env.raise(error.TypeMismatch, "nil is not a valid index");
 | 
					 | 
				
			||||||
				};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				defer env.discard(index_ref);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				const indexable_ref = try env.pop_local() orelse {
 | 
					 | 
				
			||||||
					return env.raise(error.TypeMismatch, "nil is not a valid indexable");
 | 
					 | 
				
			||||||
				};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				defer env.discard(indexable_ref);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				const value_ref = try env.get_dynamic(try kym.unbox_dynamic(env, indexable_ref), index_ref);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				defer env.discard(value_ref);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				try env.push_ref(value_ref);
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			.set_dynamic => {
 | 
					 | 
				
			||||||
				const value_ref = try env.pop_local();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				defer env.discard(value_ref);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				const index_ref = try env.pop_local() orelse {
 | 
					 | 
				
			||||||
					return env.raise(error.TypeMismatch, "nil is not a valid index");
 | 
					 | 
				
			||||||
				};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				defer env.discard(index_ref);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				const indexable_ref = try env.pop_local() orelse {
 | 
					 | 
				
			||||||
					return env.raise(error.TypeMismatch, "nil is not a valid indexable");
 | 
					 | 
				
			||||||
				};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				defer env.discard(indexable_ref);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				try env.set_dynamic(try kym.unbox_dynamic(env, indexable_ref), index_ref, value_ref);
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			.call => |arg_count| {
 | 
					 | 
				
			||||||
				const result_ref = call: {
 | 
					 | 
				
			||||||
					const callable_ref = try env.pop_local() orelse {
 | 
					 | 
				
			||||||
						return env.raise(error.TypeMismatch, "nil is not callable");
 | 
					 | 
				
			||||||
					};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					defer env.discard(callable_ref);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					break: call try env.call(callable_ref, arg_count, "");
 | 
					 | 
				
			||||||
				};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				defer env.discard(result_ref);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				try env.push_ref(result_ref);
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			.neg => {
 | 
					 | 
				
			||||||
				const ref = (try env.pop_local()) orelse {
 | 
					 | 
				
			||||||
					return env.raise(error.TypeMismatch, "null is not scalar negatable");
 | 
					 | 
				
			||||||
				};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				defer env.discard(ref);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				switch (env.unbox(ref)) {
 | 
					 | 
				
			||||||
					.fixed => |fixed| try env.push_fixed(-fixed),
 | 
					 | 
				
			||||||
					.float => |float| try env.push_float(-float),
 | 
					 | 
				
			||||||
					else => return env.raise(error.TypeMismatch, "object is not scalar negatable"),
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			.not => {
 | 
					 | 
				
			||||||
				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()) orelse {
 | 
					 | 
				
			||||||
					return env.raise(error.TypeMismatch, "nil is not addable");
 | 
					 | 
				
			||||||
				};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				defer env.discard(rhs_ref);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				const lhs_ref = (try env.pop_local()) orelse {
 | 
					 | 
				
			||||||
					return env.raise(error.TypeMismatch, "nil is not addable");
 | 
					 | 
				
			||||||
				};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				defer env.discard(lhs_ref);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				try switch (env.unbox(lhs_ref)) {
 | 
					 | 
				
			||||||
					.float => |lhs_float| switch (env.unbox(rhs_ref)) {
 | 
					 | 
				
			||||||
						.float => |rhs_float| env.push_float(lhs_float + rhs_float),
 | 
					 | 
				
			||||||
						else => env.raise(error.TypeMismatch, "right-hand object is not addable"),
 | 
					 | 
				
			||||||
					},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					else => env.raise(error.TypeMismatch, "left-hand object is not addable"),
 | 
					 | 
				
			||||||
				};
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			.sub => {
 | 
					 | 
				
			||||||
				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()) orelse {
 | 
					 | 
				
			||||||
					return env.raise(error.TypeMismatch, "nil is not subtractable");
 | 
					 | 
				
			||||||
				};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				defer env.discard(lhs_ref);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				try switch (env.unbox(lhs_ref)) {
 | 
					 | 
				
			||||||
					.float => |lhs_float| switch (env.unbox(rhs_ref)) {
 | 
					 | 
				
			||||||
						.float => |rhs_float| env.push_float(lhs_float - rhs_float),
 | 
					 | 
				
			||||||
						else => env.raise(error.TypeMismatch, "right-hand object is not subtractable"),
 | 
					 | 
				
			||||||
					},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					else => env.raise(error.TypeMismatch, "left-hand object is not subtractable"),
 | 
					 | 
				
			||||||
				};
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			.mul => {
 | 
					 | 
				
			||||||
				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()) orelse {
 | 
					 | 
				
			||||||
					return env.raise(error.TypeMismatch, "nil is not multiplicable");
 | 
					 | 
				
			||||||
				};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				defer env.discard(lhs_ref);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				try switch (env.unbox(lhs_ref)) {
 | 
					 | 
				
			||||||
					.float => |lhs_float| switch (env.unbox(rhs_ref)) {
 | 
					 | 
				
			||||||
						.float => |rhs_float| env.push_float(lhs_float * rhs_float),
 | 
					 | 
				
			||||||
						else => env.raise(error.TypeMismatch, "right-hand object is not multipliable"),
 | 
					 | 
				
			||||||
					},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					else => env.raise(error.TypeMismatch, "left-hand object is not multipliable"),
 | 
					 | 
				
			||||||
				};
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			.div => {
 | 
					 | 
				
			||||||
				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()) orelse {
 | 
					 | 
				
			||||||
					return env.raise(error.TypeMismatch, "nil is not divisible");
 | 
					 | 
				
			||||||
				};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				defer env.discard(lhs_ref);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				try switch (env.unbox(lhs_ref)) {
 | 
					 | 
				
			||||||
					.float => |lhs_float| switch (env.unbox(rhs_ref)) {
 | 
					 | 
				
			||||||
						.float => |rhs_float| env.push_float(lhs_float / rhs_float),
 | 
					 | 
				
			||||||
						else => env.raise(error.TypeMismatch, "right-hand object is not divisible"),
 | 
					 | 
				
			||||||
					},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					else => env.raise(error.TypeMismatch, "left-hand object is not divisible"),
 | 
					 | 
				
			||||||
				};
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			.eql => {
 | 
					 | 
				
			||||||
				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() orelse {
 | 
					 | 
				
			||||||
					return env.raise(error.TypeMismatch, "nil is not equatable");
 | 
					 | 
				
			||||||
				};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				defer env.discard(lhs_ref);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				try env.push_boolean(kym.test_equality(env, lhs_ref, rhs_ref));
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			.cgt => {
 | 
					 | 
				
			||||||
				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() orelse {
 | 
					 | 
				
			||||||
					return env.raise(error.TypeMismatch, "nil is not comparable");
 | 
					 | 
				
			||||||
				};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				defer env.discard(lhs_ref);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				try env.push_boolean(try kym.test_difference(env, lhs_ref, rhs_ref) > 0);
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			.clt => {
 | 
					 | 
				
			||||||
				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() orelse {
 | 
					 | 
				
			||||||
					return env.raise(error.TypeMismatch, "nil is not comparable");
 | 
					 | 
				
			||||||
				};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				defer env.discard(lhs_ref);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				try env.push_boolean(try kym.test_difference(env, lhs_ref, rhs_ref) < 0);
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			.cge => {
 | 
					 | 
				
			||||||
				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() orelse {
 | 
					 | 
				
			||||||
					return env.raise(error.TypeMismatch, "nil is not comparable");
 | 
					 | 
				
			||||||
				};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				defer env.discard(lhs_ref);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				try env.push_boolean(try kym.test_difference(env, lhs_ref, rhs_ref) >= 0);
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			.cle => {
 | 
					 | 
				
			||||||
				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() orelse {
 | 
					 | 
				
			||||||
					return env.raise(error.TypeMismatch, "nil is not comparable");
 | 
					 | 
				
			||||||
				};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				defer env.discard(lhs_ref);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				try env.push_boolean(try kym.test_difference(env, lhs_ref, rhs_ref) <= 0);
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return env.pop_local();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn free(self: *Self) void {
 | 
					 | 
				
			||||||
	for (self.constant_refs.values) |constant| {
 | 
					 | 
				
			||||||
		self.env.discard(constant);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	self.opcodes.free();
 | 
					 | 
				
			||||||
	self.constant_refs.free();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn make(env: *kym.RuntimeEnv) Self {
 | 
					 | 
				
			||||||
	return Self{
 | 
					 | 
				
			||||||
		.opcodes = OpcodeList.make(env.allocator),
 | 
					 | 
				
			||||||
		.constant_refs = RefList.make(env.allocator),
 | 
					 | 
				
			||||||
		.env = env,
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -6,14 +6,12 @@ associative: AssociativeTable,
 | 
				
			|||||||
contiguous: ContiguousList,
 | 
					contiguous: ContiguousList,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const AssociativeTable = coral.map.Table(*kym.RuntimeRef, *kym.RuntimeRef, struct {
 | 
					const AssociativeTable = coral.map.Table(*kym.RuntimeRef, *kym.RuntimeRef, struct {
 | 
				
			||||||
	env: *kym.RuntimeEnv,
 | 
						pub fn hash(key: *kym.RuntimeRef) usize {
 | 
				
			||||||
 | 
							return key.hash();
 | 
				
			||||||
	pub fn hash(self: @This(), key_ref: *kym.RuntimeRef) usize {
 | 
					 | 
				
			||||||
		return kym.hash(self.env, key_ref);
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	pub fn equals(self: @This(), key_ref_a: *kym.RuntimeRef, key_ref_b: *kym.RuntimeRef) bool {
 | 
						pub fn equals(key_a: *kym.RuntimeRef, key_b: *kym.RuntimeRef) bool {
 | 
				
			||||||
		return kym.test_equality(self.env, key_ref_a, key_ref_b);
 | 
							return key_a.equals(key_b);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -23,10 +21,7 @@ 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{
 | 
				
			||||||
		.associative = AssociativeTable.make(env.allocator, .{
 | 
							.associative = AssociativeTable.make(env.allocator, .{}),
 | 
				
			||||||
			.env = env,
 | 
					 | 
				
			||||||
		}),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		.contiguous = ContiguousList.make(env.allocator),
 | 
							.contiguous = ContiguousList.make(env.allocator),
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -35,17 +30,20 @@ pub fn new(env: *kym.RuntimeEnv) kym.RuntimeError!*kym.RuntimeRef {
 | 
				
			|||||||
		self.contiguous.free();
 | 
							self.contiguous.free();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return try env.new_dynamic(coral.io.bytes_of(&self), &typeinfo);
 | 
						const table = try env.new_dynamic(&.{
 | 
				
			||||||
 | 
							.name = "table",
 | 
				
			||||||
 | 
							.size = @sizeOf(Self),
 | 
				
			||||||
 | 
							.destruct = typeinfo_destruct,
 | 
				
			||||||
 | 
							.get = typeinfo_get,
 | 
				
			||||||
 | 
							.set = typeinfo_set,
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						coral.io.copy(env.unbox_dynamic(table) catch unreachable, coral.io.bytes_of(&self));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return table;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const typeinfo = kym.Typeinfo{
 | 
					fn typeinfo_destruct(method: kym.Typeinfo.Method) void {
 | 
				
			||||||
	.name = "table",
 | 
					 | 
				
			||||||
	.clean = typeinfo_clean,
 | 
					 | 
				
			||||||
	.get = typeinfo_get,
 | 
					 | 
				
			||||||
	.set = typeinfo_set,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fn typeinfo_clean(method: kym.Method) void {
 | 
					 | 
				
			||||||
	const table = @as(*Self, @ptrCast(@alignCast(method.userdata.ptr)));
 | 
						const table = @as(*Self, @ptrCast(@alignCast(method.userdata.ptr)));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
@ -59,125 +57,77 @@ fn typeinfo_clean(method: kym.Method) void {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	table.associative.free();
 | 
						table.associative.free();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	while (table.contiguous.pop()) |ref| {
 | 
						while (table.contiguous.pop()) |value| {
 | 
				
			||||||
		method.env.discard(ref);
 | 
							if (value) |ref| {
 | 
				
			||||||
 | 
								method.env.discard(ref);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	table.contiguous.free();
 | 
						table.contiguous.free();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn typeinfo_get(method: kym.Method, index_ref: *const kym.RuntimeRef) kym.RuntimeError!?*kym.RuntimeRef {
 | 
					fn typeinfo_get(method: kym.Typeinfo.Method, index: *const kym.RuntimeRef) kym.RuntimeError!?*kym.RuntimeRef {
 | 
				
			||||||
	const table = @as(*Self, @ptrCast(@alignCast(method.userdata.ptr)));
 | 
						const table = @as(*Self, @ptrCast(@alignCast(method.userdata.ptr)));
 | 
				
			||||||
 | 
						const acquired_index = try method.env.acquire(index);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	switch (method.env.unbox(index_ref)) {
 | 
						defer method.env.discard(acquired_index);
 | 
				
			||||||
		.symbol, .string, .float => {
 | 
					 | 
				
			||||||
			const mutable_index_ref = method.env.acquire(index_ref);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
			defer method.env.discard(mutable_index_ref);
 | 
						if (acquired_index.is_fixed()) |fixed| {
 | 
				
			||||||
 | 
							if (fixed < 0) {
 | 
				
			||||||
 | 
								// TODO: Negative indexing.
 | 
				
			||||||
 | 
								unreachable;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if (table.associative.lookup(mutable_index_ref)) |value_ref| {
 | 
							if (fixed < table.contiguous.values.len) {
 | 
				
			||||||
				return method.env.acquire(value_ref);
 | 
								return method.env.acquire(table.contiguous.values[@intCast(fixed)] orelse return null);
 | 
				
			||||||
			}
 | 
							}
 | 
				
			||||||
		},
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		.fixed => |fixed| {
 | 
						if (table.associative.lookup(acquired_index)) |value_ref| {
 | 
				
			||||||
			if (fixed < 0) {
 | 
							return method.env.acquire(value_ref);
 | 
				
			||||||
				// TODO: Negative indexing.
 | 
					 | 
				
			||||||
				unreachable;
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			if (fixed < table.contiguous.values.len) {
 | 
					 | 
				
			||||||
				return method.env.acquire(table.contiguous.values[@intCast(fixed)] orelse return null);
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			const mutable_index_ref = method.env.acquire(index_ref);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			defer method.env.discard(mutable_index_ref);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			if (table.associative.lookup(mutable_index_ref)) |value_ref| {
 | 
					 | 
				
			||||||
				return method.env.acquire(value_ref);
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		else => return method.env.raise(error.TypeMismatch, "expected symbol, string, or number index"),
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return null;
 | 
						return null;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn typeinfo_set(method: kym.Method, index_ref: *const kym.RuntimeRef, value_ref: ?*const kym.RuntimeRef) kym.RuntimeError!void {
 | 
					fn typeinfo_set(method: kym.Typeinfo.Method, index: *const kym.RuntimeRef, value: ?*const kym.RuntimeRef) kym.RuntimeError!void {
 | 
				
			||||||
	const table = @as(*Self, @ptrCast(@alignCast(method.userdata.ptr)));
 | 
						const table = @as(*Self, @ptrCast(@alignCast(method.userdata.ptr)));
 | 
				
			||||||
 | 
						const acquired_index = try method.env.acquire(index);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	switch (method.env.unbox(index_ref)) {
 | 
						errdefer method.env.discard(acquired_index);
 | 
				
			||||||
		.symbol, .string, .float => {
 | 
					 | 
				
			||||||
			const acquired_index_ref = method.env.acquire(index_ref);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
			errdefer method.env.discard(acquired_index_ref);
 | 
						if (acquired_index.is_fixed()) |fixed| {
 | 
				
			||||||
 | 
							if (fixed < 0) {
 | 
				
			||||||
 | 
								// TODO: Negative indexing.
 | 
				
			||||||
 | 
								unreachable;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if (value_ref) |ref| {
 | 
							if (fixed < table.contiguous.values.len) {
 | 
				
			||||||
				const acquired_ref = method.env.acquire(ref);
 | 
								const maybe_replacing = &table.contiguous.values[@intCast(fixed)];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				errdefer method.env.discard(acquired_ref);
 | 
								if (maybe_replacing.*) |replacing| {
 | 
				
			||||||
 | 
									method.env.discard(replacing);
 | 
				
			||||||
				if (try table.associative.replace(acquired_index_ref, acquired_ref)) |replaced| {
 | 
					 | 
				
			||||||
					method.env.discard(replaced.key);
 | 
					 | 
				
			||||||
					method.env.discard(replaced.value);
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			} else if (table.associative.remove(acquired_index_ref)) |removed| {
 | 
					 | 
				
			||||||
				method.env.discard(removed.key);
 | 
					 | 
				
			||||||
				method.env.discard(removed.value);
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		.fixed => |fixed| {
 | 
					 | 
				
			||||||
			if (fixed < 0) {
 | 
					 | 
				
			||||||
				// TODO: Negative indexing.
 | 
					 | 
				
			||||||
				unreachable;
 | 
					 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if (fixed < table.contiguous.values.len) {
 | 
								maybe_replacing.* = if (value) |ref| try method.env.acquire(ref) else null;
 | 
				
			||||||
				const contiguous_value = &table.contiguous.values[@intCast(fixed)];
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
				method.env.discard(contiguous_value.*);
 | 
								return;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				contiguous_value.* = if (value_ref) |ref| method.env.acquire(ref) else null;
 | 
						const acquired_value = try method.env.acquire(value orelse {
 | 
				
			||||||
 | 
							if (table.associative.remove(acquired_index)) |removed| {
 | 
				
			||||||
 | 
								method.env.discard(removed.key);
 | 
				
			||||||
 | 
								method.env.discard(removed.value);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				return;
 | 
							return;
 | 
				
			||||||
			}
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if (fixed == table.contiguous.values.len) {
 | 
						errdefer method.env.discard(acquired_value);
 | 
				
			||||||
				if (value_ref) |ref| {
 | 
					 | 
				
			||||||
					const acquired_ref = method.env.acquire(ref);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
					errdefer method.env.discard(acquired_ref);
 | 
						if (try table.associative.replace(acquired_index, acquired_value)) |replaced| {
 | 
				
			||||||
 | 
							method.env.discard(replaced.key);
 | 
				
			||||||
					try table.contiguous.push_one(acquired_ref);
 | 
							method.env.discard(replaced.value);
 | 
				
			||||||
				} else {
 | 
					 | 
				
			||||||
					try table.contiguous.push_one(null);
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				return;
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			const acquired_index_ref = method.env.acquire(index_ref);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			errdefer method.env.discard(acquired_index_ref);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			if (value_ref) |ref| {
 | 
					 | 
				
			||||||
				const acquired_ref = method.env.acquire(ref);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				errdefer method.env.discard(acquired_ref);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				if (try table.associative.replace(acquired_index_ref, acquired_ref)) |replaced| {
 | 
					 | 
				
			||||||
					method.env.discard(replaced.key);
 | 
					 | 
				
			||||||
					method.env.discard(replaced.value);
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			} else if (table.associative.remove(acquired_index_ref)) |removed| {
 | 
					 | 
				
			||||||
				method.env.discard(removed.key);
 | 
					 | 
				
			||||||
				method.env.discard(removed.value);
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		else => return method.env.raise(error.TypeMismatch, "expected symbol, string, or number index"),
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -27,25 +27,31 @@ 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 {
 | 
				
			||||||
	if (try env.view_arg(0)) |arg| {
 | 
						const argument = try env.expect(try env.arg(0));
 | 
				
			||||||
		app.log_info(try kym.unbox_string(env, arg));
 | 
					
 | 
				
			||||||
	}
 | 
						defer env.discard(argument);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						app.log_info(try env.unbox_string(argument));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	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 {
 | 
				
			||||||
	if (try env.view_arg(0)) |arg| {
 | 
						const argument = try env.expect(try env.arg(0));
 | 
				
			||||||
		app.log_warn(try kym.unbox_string(env, arg));
 | 
					
 | 
				
			||||||
	}
 | 
						defer env.discard(argument);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						app.log_warn(try env.unbox_string(argument));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	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 {
 | 
				
			||||||
	if (try env.view_arg(0)) |arg| {
 | 
						const argument = try env.expect(try env.arg(0));
 | 
				
			||||||
		app.log_fail(try kym.unbox_string(env, arg));
 | 
					
 | 
				
			||||||
	}
 | 
						defer env.discard(argument);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						app.log_fail(try env.unbox_string(argument));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return null;
 | 
						return null;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user