Work in progress changes
This commit is contained in:
		
							parent
							
								
									bf0ff03b49
								
							
						
					
					
						commit
						7604594630
					
				@ -7,10 +7,10 @@ const list = @import("./list.zig");
 | 
			
		||||
const math = @import("./math.zig");
 | 
			
		||||
 | 
			
		||||
pub const Stacking = struct {
 | 
			
		||||
	base_allocator: io.Allocator,
 | 
			
		||||
	page_allocator: io.Allocator,
 | 
			
		||||
	min_page_size: usize,
 | 
			
		||||
	allocations: list.Stack(usize) = .{},
 | 
			
		||||
	pages: list.Stack(Page) = .{},
 | 
			
		||||
	allocations: list.Stack(usize),
 | 
			
		||||
	pages: list.Stack(Page),
 | 
			
		||||
 | 
			
		||||
	const Page = struct {
 | 
			
		||||
		buffer: []u8,
 | 
			
		||||
@ -49,11 +49,11 @@ pub const Stacking = struct {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fn allocate_page(self: *Stacking, page_size: usize) io.AllocationError!*Page {
 | 
			
		||||
		var buffer = try io.allocate_many(self.base_allocator, page_size, u8);
 | 
			
		||||
		var buffer = try io.allocate_many(self.page_allocator, page_size, u8);
 | 
			
		||||
 | 
			
		||||
		errdefer io.deallocate(self.base_allocator, buffer);
 | 
			
		||||
		errdefer io.deallocate(self.page_allocator, buffer);
 | 
			
		||||
 | 
			
		||||
		try self.pages.push_one(self.base_allocator, .{
 | 
			
		||||
		try self.pages.push_one(.{
 | 
			
		||||
			.buffer = buffer,
 | 
			
		||||
			.used = 0,
 | 
			
		||||
		});
 | 
			
		||||
@ -83,15 +83,6 @@ pub const Stacking = struct {
 | 
			
		||||
		}.reallocate);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pub fn clear_allocations(self: *Stacking) void {
 | 
			
		||||
		for (self.pages.values) |page| {
 | 
			
		||||
			io.deallocate(self.base_allocator, page.buffer);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		self.pages.deinit(self.base_allocator);
 | 
			
		||||
		self.allocations.deinit(self.base_allocator);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fn current_page(self: Stacking) ?*Page {
 | 
			
		||||
		if (self.pages.values.len == 0) {
 | 
			
		||||
			return null;
 | 
			
		||||
@ -99,4 +90,22 @@ pub const Stacking = struct {
 | 
			
		||||
 | 
			
		||||
		return &self.pages.values[self.pages.values.len - 1];
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pub fn deinit(self: *Stacking) void {
 | 
			
		||||
		for (self.pages.values) |page| {
 | 
			
		||||
			io.deallocate(self.page_allocator, page.buffer);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		self.pages.deinit();
 | 
			
		||||
		self.allocations.deinit();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pub fn init(allocator: io.Allocator, min_page_size: usize) io.AllocationError!Stacking {
 | 
			
		||||
		return Stacking{
 | 
			
		||||
			.page_allocator = allocator,
 | 
			
		||||
			.allocations = .{.allocator = allocator},
 | 
			
		||||
			.pages = .{.allocator = allocator},
 | 
			
		||||
			.min_page_size = min_page_size,
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -14,6 +14,11 @@ pub const AllocationOptions = struct {
 | 
			
		||||
 | 
			
		||||
pub const Allocator = Generator(?[]u8, AllocationOptions);
 | 
			
		||||
 | 
			
		||||
///
 | 
			
		||||
///
 | 
			
		||||
///
 | 
			
		||||
pub const Byte = u8;
 | 
			
		||||
 | 
			
		||||
///
 | 
			
		||||
/// Function pointer coupled with an immutable state context for providing dynamic dispatch over a given `Input` and
 | 
			
		||||
/// `Output`.
 | 
			
		||||
@ -134,7 +139,7 @@ pub const FixedBuffer = struct {
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub const Writer = Generator(?usize, []const u8);
 | 
			
		||||
pub const Writer = Generator(?usize, []const Byte);
 | 
			
		||||
 | 
			
		||||
pub fn allocate_many(allocator: Allocator, amount: usize, comptime Type: type) AllocationError![]Type {
 | 
			
		||||
	if (@sizeOf(Type) == 0) {
 | 
			
		||||
 | 
			
		||||
@ -9,6 +9,7 @@ const math = @import("./math.zig");
 | 
			
		||||
///
 | 
			
		||||
pub fn Stack(comptime Value: type) type {
 | 
			
		||||
	return struct {
 | 
			
		||||
		allocator: io.Allocator,
 | 
			
		||||
		capacity: usize = 0,
 | 
			
		||||
		values: []Value = &.{},
 | 
			
		||||
 | 
			
		||||
@ -34,12 +35,12 @@ pub fn Stack(comptime Value: type) type {
 | 
			
		||||
		/// *Note* if the `capacity` field of `self` is a non-zero value, `allocator` must reference the same allocation
 | 
			
		||||
		/// strategy as the one originally used to allocate the current internal buffer.
 | 
			
		||||
		///
 | 
			
		||||
		pub fn deinit(self: *Self, allocator: io.Allocator) void {
 | 
			
		||||
		pub fn deinit(self: *Self) void {
 | 
			
		||||
			if (self.capacity == 0) {
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			io.deallocate(allocator, self.values.ptr[0 .. self.capacity]);
 | 
			
		||||
			io.deallocate(self.allocator, self.values.ptr[0 .. self.capacity]);
 | 
			
		||||
 | 
			
		||||
			self.values = &.{};
 | 
			
		||||
			self.capacity = 0;
 | 
			
		||||
@ -69,21 +70,18 @@ pub fn Stack(comptime Value: type) type {
 | 
			
		||||
		/// Growing ahead of multiple push operations is useful when the upper bound of pushes is well-understood, as it
 | 
			
		||||
		/// can reduce the number of allocations required per push.
 | 
			
		||||
		///
 | 
			
		||||
		/// *Note* if the `capacity` field of `self` is a non-zero value, `allocator` must reference the same allocation
 | 
			
		||||
		/// strategy as the one originally used to allocate the current internal buffer.
 | 
			
		||||
		///
 | 
			
		||||
		pub fn grow(self: *Self, allocator: io.Allocator, growth_amount: usize) io.AllocationError!void {
 | 
			
		||||
		pub fn grow(self: *Self, growth_amount: usize) io.AllocationError!void {
 | 
			
		||||
			const grown_capacity = self.capacity + growth_amount;
 | 
			
		||||
			const values = (try io.allocate_many(allocator, grown_capacity, Value))[0 .. self.values.len];
 | 
			
		||||
			const values = (try io.allocate_many(self.allocator, grown_capacity, Value))[0 .. self.values.len];
 | 
			
		||||
 | 
			
		||||
			errdefer io.deallocate(allocator, values);
 | 
			
		||||
			errdefer io.deallocate(self.allocator, values);
 | 
			
		||||
 | 
			
		||||
			if (self.capacity != 0) {
 | 
			
		||||
				for (0 .. self.values.len) |index| {
 | 
			
		||||
					values[index] = self.values[index];
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				io.deallocate(allocator, self.values.ptr[0 .. self.capacity]);
 | 
			
		||||
				io.deallocate(self.allocator, self.values.ptr[0 .. self.capacity]);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			self.values = values;
 | 
			
		||||
@ -113,14 +111,11 @@ pub fn Stack(comptime Value: type) type {
 | 
			
		||||
		/// The function returns [io.AllocationError] if `allocator` could not commit the memory required to grow the
 | 
			
		||||
		/// internal buffer of `self` when necessary.
 | 
			
		||||
		///
 | 
			
		||||
		/// *Note* if the `capacity` field of `self` is a non-zero value, `allocator` must reference the same allocation
 | 
			
		||||
		/// strategy as the one originally used to allocate the current internal buffer.
 | 
			
		||||
		///
 | 
			
		||||
		pub fn push_all(self: *Self, allocator: io.Allocator, values: []const Value) io.AllocationError!void {
 | 
			
		||||
		pub fn push_all(self: *Self, values: []const Value) io.AllocationError!void {
 | 
			
		||||
			const new_length = self.values.len + values.len;
 | 
			
		||||
 | 
			
		||||
			if (new_length > self.capacity) {
 | 
			
		||||
				try self.grow(allocator, values.len + values.len);
 | 
			
		||||
				try self.grow(values.len + values.len);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			const offset_index = self.values.len;
 | 
			
		||||
@ -139,14 +134,11 @@ pub fn Stack(comptime Value: type) type {
 | 
			
		||||
		/// The function returns [io.AllocationError] if `allocator` could not commit the memory required to grow the
 | 
			
		||||
		/// internal buffer of `self` when necessary.
 | 
			
		||||
		///
 | 
			
		||||
		/// *Note* if the `capacity` field of `self` is a non-zero value, `allocator` must reference the same allocation
 | 
			
		||||
		/// strategy as the one originally used to allocate the current internal buffer.
 | 
			
		||||
		///
 | 
			
		||||
		pub fn push_many(self: *Self, allocator: io.Allocator, value: Value, amount: usize) io.AllocationError!void {
 | 
			
		||||
		pub fn push_many(self: *Self, value: Value, amount: usize) io.AllocationError!void {
 | 
			
		||||
			const new_length = self.values.len + amount;
 | 
			
		||||
 | 
			
		||||
			if (new_length >= self.capacity) {
 | 
			
		||||
				try self.grow(allocator, amount + amount);
 | 
			
		||||
				try self.grow(amount + amount);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			const offset_index = self.values.len;
 | 
			
		||||
@ -165,12 +157,9 @@ pub fn Stack(comptime Value: type) type {
 | 
			
		||||
		/// The function returns [io.AllocationError] if `allocator` could not commit the memory required to grow the
 | 
			
		||||
		/// internal buffer of `self` when necessary.
 | 
			
		||||
		///
 | 
			
		||||
		/// *Note* if the `capacity` field of `self` is a non-zero value, `allocator` must reference the same allocation
 | 
			
		||||
		/// strategy as the one originally used to allocate the current internal buffer.
 | 
			
		||||
		///
 | 
			
		||||
		pub fn push_one(self: *Self, allocator: io.Allocator, value: Value) io.AllocationError!void {
 | 
			
		||||
		pub fn push_one(self: *Self, value: Value) io.AllocationError!void {
 | 
			
		||||
			if (self.values.len == self.capacity) {
 | 
			
		||||
				try self.grow(allocator, math.max(1, self.capacity));
 | 
			
		||||
				try self.grow(math.max(1, self.capacity));
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			const offset_index = self.values.len;
 | 
			
		||||
@ -183,47 +172,19 @@ pub fn Stack(comptime Value: type) type {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
///
 | 
			
		||||
/// Bridge context between a list type implement as part of the list module and an allocator, allowing the list resource
 | 
			
		||||
/// referenced by the [Writable] instance to be written to directly or virtually via the [io.Writer] interface.
 | 
			
		||||
///
 | 
			
		||||
/// *Note* if the given list contains an existing allocation, the provided [io.Allocator] instance must reference the
 | 
			
		||||
/// same allocation strategy as the one originally used to allocate the list type memory.
 | 
			
		||||
///
 | 
			
		||||
pub const Writable = struct {
 | 
			
		||||
	allocator: io.Allocator,
 | 
			
		||||
pub const ByteStack = Stack(io.Byte);
 | 
			
		||||
 | 
			
		||||
	list: union (enum) {
 | 
			
		||||
		stack: *ByteStack,
 | 
			
		||||
	},
 | 
			
		||||
///
 | 
			
		||||
/// Returns a [io.Writer] instance that binds a reference of `self` to the [write] operation.
 | 
			
		||||
///
 | 
			
		||||
pub fn stack_as_writer(self: *ByteStack) io.Writer {
 | 
			
		||||
	return io.Writer.bind(ByteStack, self, struct {
 | 
			
		||||
		fn write(stack: *ByteStack, bytes: []const io.Byte) ?usize {
 | 
			
		||||
			stack.push_all(bytes) catch return null;
 | 
			
		||||
 | 
			
		||||
	///
 | 
			
		||||
	/// Stack of bytes.
 | 
			
		||||
	///
 | 
			
		||||
	const ByteStack = Stack(u8);
 | 
			
		||||
 | 
			
		||||
	///
 | 
			
		||||
	/// Returns a [io.Writer] instance that binds a reference of `self` to the [write] operation.
 | 
			
		||||
	///
 | 
			
		||||
	pub fn as_writer(self: *Writable) io.Writer {
 | 
			
		||||
		return io.Writer.bind(Writable, self, struct {
 | 
			
		||||
			fn write(writable: *Writable, bytes: []const u8) ?usize {
 | 
			
		||||
				writable.write(bytes) catch return null;
 | 
			
		||||
 | 
			
		||||
				return bytes.len;
 | 
			
		||||
			}
 | 
			
		||||
		}.write);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	///
 | 
			
		||||
	/// Attempts to call the appropriate multi-element writing function for the current list referenced by `self`,
 | 
			
		||||
	/// passing `bytes` along.
 | 
			
		||||
	///
 | 
			
		||||
	/// The function returns [io.AllocationError] if `allocator` could not commit the memory by the list implementation
 | 
			
		||||
	/// referenced by `self`. See the specific implementation details of the respective list type for more information.
 | 
			
		||||
	///
 | 
			
		||||
	pub fn write(self: *Writable, bytes: []const u8) io.AllocationError!void {
 | 
			
		||||
		return switch (self.list) {
 | 
			
		||||
			.stack => |stack| stack.push_all(self.allocator, bytes),
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
			return bytes.len;
 | 
			
		||||
		}
 | 
			
		||||
	}.write);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -19,6 +19,7 @@ const std = @import("std");
 | 
			
		||||
///
 | 
			
		||||
pub fn Map(comptime index_int: std.builtin.Type.Int, comptime Value: type) type {
 | 
			
		||||
	return struct {
 | 
			
		||||
		allocator: io.Allocator,
 | 
			
		||||
		free_index: Index = 0,
 | 
			
		||||
		count: Index = 0,
 | 
			
		||||
		table: []Entry = &.{},
 | 
			
		||||
@ -73,12 +74,12 @@ pub fn Map(comptime index_int: std.builtin.Type.Int, comptime Value: type) type
 | 
			
		||||
		///
 | 
			
		||||
		/// Fetches the value referenced by `index` in `self`, returning it.
 | 
			
		||||
		///
 | 
			
		||||
		pub fn fetch(self: *Self, index: Index) Value {
 | 
			
		||||
		pub fn fetch(self: *Self, index: Index) *Value {
 | 
			
		||||
			const entry = &self.table[index];
 | 
			
		||||
 | 
			
		||||
			debug.assert(entry.* == .value);
 | 
			
		||||
 | 
			
		||||
			return entry.value;
 | 
			
		||||
			return &entry.value;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		///
 | 
			
		||||
@ -125,9 +126,9 @@ pub fn Map(comptime index_int: std.builtin.Type.Int, comptime Value: type) type
 | 
			
		||||
		/// *Note* if the `table` field of `self` is an allocated slice, `allocator` must reference the same allocation
 | 
			
		||||
		/// strategy as the one originally used to allocate the current table.
 | 
			
		||||
		///
 | 
			
		||||
		pub fn insert(self: *Self, allocator: io.Allocator, value: Value) io.AllocationError!Index {
 | 
			
		||||
		pub fn insert(self: *Self, value: Value) io.AllocationError!Index {
 | 
			
		||||
			if (self.count == self.table.len) {
 | 
			
		||||
				try self.grow(allocator, math.max(1, self.count));
 | 
			
		||||
				try self.grow(self.allocator, math.max(1, self.count));
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (self.free_index == self.count) {
 | 
			
		||||
 | 
			
		||||
@ -22,6 +22,7 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime keyer: Keyer(Ke
 | 
			
		||||
	const growth_factor = 0.6;
 | 
			
		||||
 | 
			
		||||
	return struct {
 | 
			
		||||
		allocator: io.Allocator,
 | 
			
		||||
		count: usize = 0,
 | 
			
		||||
		table: []?Entry = &.{},
 | 
			
		||||
 | 
			
		||||
@ -101,10 +102,7 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime keyer: Keyer(Ke
 | 
			
		||||
		/// The function returns [AllocationError] instead if `allocator` cannot commit the memory required to grow the
 | 
			
		||||
		/// entry table of `self` when necessary.
 | 
			
		||||
		///
 | 
			
		||||
		/// *Note* `allocator` must reference the same allocation strategy as the one originally used to initialize
 | 
			
		||||
		/// `self`.
 | 
			
		||||
		///
 | 
			
		||||
		pub fn assign(self: *Self, allocator: io.Allocator, key: Key, value: Value) io.AllocationError!?Entry {
 | 
			
		||||
		pub fn assign(self: *Self, key: Key, value: Value) io.AllocationError!?Entry {
 | 
			
		||||
			if (self.calculate_load_factor() >= load_max) {
 | 
			
		||||
				const growth_size = @intToFloat(f64, math.max(1, self.table.len)) * growth_factor;
 | 
			
		||||
 | 
			
		||||
@ -112,7 +110,7 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime keyer: Keyer(Ke
 | 
			
		||||
					return error.OutOfMemory;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				try self.rehash(allocator, @floatToInt(usize, growth_size));
 | 
			
		||||
				try self.rehash(@floatToInt(usize, growth_size));
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			debug.assert(self.table.len > self.count);
 | 
			
		||||
@ -174,15 +172,12 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime keyer: Keyer(Ke
 | 
			
		||||
		///
 | 
			
		||||
		/// To clear all items from the table while preserving the current capacity, see [clear] instead.
 | 
			
		||||
		///
 | 
			
		||||
		/// *Note* `allocator` must reference the same allocation strategy as the one originally used to initialize
 | 
			
		||||
		/// `self`.
 | 
			
		||||
		///
 | 
			
		||||
		pub fn deinit(self: *Self, allocator: io.Allocator) void {
 | 
			
		||||
		pub fn deinit(self: *Self) void {
 | 
			
		||||
			if (self.table.len == 0) {
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			io.deallocate(allocator, self.table);
 | 
			
		||||
			io.deallocate(self.allocator, self.table);
 | 
			
		||||
 | 
			
		||||
			self.table = &.{};
 | 
			
		||||
			self.count = 0;
 | 
			
		||||
@ -195,15 +190,12 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime keyer: Keyer(Ke
 | 
			
		||||
		/// The function returns [AllocationError] instead if `allocator` cannot commit the memory required to grow the
 | 
			
		||||
		/// entry table of `self` when necessary.
 | 
			
		||||
		///
 | 
			
		||||
		/// *Note* `allocator` must reference the same allocation strategy as the one originally used to initialize
 | 
			
		||||
		/// `self`.
 | 
			
		||||
		///
 | 
			
		||||
		pub fn insert(self: *Self, allocator: io.Allocator, key: Key, value: Value) io.AllocationError!bool {
 | 
			
		||||
		pub fn insert(self: *Self, key: Key, value: Value) io.AllocationError!bool {
 | 
			
		||||
			if (self.calculate_load_factor() >= load_max) {
 | 
			
		||||
				const growth_amount = @intToFloat(f64, self.table.len) * growth_factor;
 | 
			
		||||
				const min_size = 1;
 | 
			
		||||
 | 
			
		||||
				try self.rehash(allocator, self.table.len + math.max(min_size, @floatToInt(usize, growth_amount)));
 | 
			
		||||
				try self.rehash(self.table.len + math.max(min_size, @floatToInt(usize, growth_amount)));
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			debug.assert(self.table.len > self.count);
 | 
			
		||||
@ -246,16 +238,13 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime keyer: Keyer(Ke
 | 
			
		||||
		/// greater than `requested_range`, returning [io.AllocationError] if `allocator` cannot commit the memory
 | 
			
		||||
		/// required for the table capacity size.
 | 
			
		||||
		///
 | 
			
		||||
		/// *Note* `allocator` must reference the same allocation strategy as the one originally used to initialize
 | 
			
		||||
		/// `self`.
 | 
			
		||||
		///
 | 
			
		||||
		pub fn rehash(self: *Self, allocator: io.Allocator, requested_range: usize) io.AllocationError!void {
 | 
			
		||||
		pub fn rehash(self: *Self, requested_range: usize) io.AllocationError!void {
 | 
			
		||||
			const old_table = self.table;
 | 
			
		||||
 | 
			
		||||
			self.table = try io.allocate_many(allocator, math.max(requested_range, self.count), ?Entry);
 | 
			
		||||
			self.table = try io.allocate_many(self.allocator, math.max(requested_range, self.count), ?Entry);
 | 
			
		||||
 | 
			
		||||
			errdefer {
 | 
			
		||||
				io.deallocate(allocator, self.table);
 | 
			
		||||
				io.deallocate(self.allocator, self.table);
 | 
			
		||||
 | 
			
		||||
				self.table = old_table;
 | 
			
		||||
			}
 | 
			
		||||
@ -272,7 +261,7 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime keyer: Keyer(Ke
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				io.deallocate(allocator, old_table);
 | 
			
		||||
				io.deallocate(self.allocator, old_table);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,541 @@
 | 
			
		||||
pub const Environment = @import("./kym/Environment.zig");
 | 
			
		||||
const Ast = @import("./kym/Ast.zig");
 | 
			
		||||
 | 
			
		||||
const coral = @import("coral");
 | 
			
		||||
 | 
			
		||||
const types = @import("./kym/types.zig");
 | 
			
		||||
const file = @import("./file.zig");
 | 
			
		||||
 | 
			
		||||
const tokens = @import("./kym/tokens.zig");
 | 
			
		||||
 | 
			
		||||
///
 | 
			
		||||
///
 | 
			
		||||
///
 | 
			
		||||
pub const CallContext = struct {
 | 
			
		||||
	env: *RuntimeEnv,
 | 
			
		||||
	obj: Value,
 | 
			
		||||
	args: []const Value = &.{},
 | 
			
		||||
 | 
			
		||||
	///
 | 
			
		||||
	///
 | 
			
		||||
	///
 | 
			
		||||
	pub fn arg_at(self: CallContext, index: Int) RuntimeError!Value {
 | 
			
		||||
		if (!coral.math.is_clamped(index, 0, self.args.len - 1)) {
 | 
			
		||||
			return self.env.check_fail("argument out of bounds");
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return self.args[@intCast(usize, index)];
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const Chunk = struct {
 | 
			
		||||
	env: *RuntimeEnv,
 | 
			
		||||
	opcodes: coral.list.Stack(Opcode),
 | 
			
		||||
 | 
			
		||||
	///
 | 
			
		||||
	///
 | 
			
		||||
	///
 | 
			
		||||
	const Opcode = union (enum) {
 | 
			
		||||
		push_nil,
 | 
			
		||||
		push_true,
 | 
			
		||||
		push_false,
 | 
			
		||||
		push_zero,
 | 
			
		||||
		push_number: Float,
 | 
			
		||||
		push_array: i32,
 | 
			
		||||
		push_table: i32,
 | 
			
		||||
 | 
			
		||||
		not,
 | 
			
		||||
		neg,
 | 
			
		||||
 | 
			
		||||
		add,
 | 
			
		||||
		sub,
 | 
			
		||||
		mul,
 | 
			
		||||
		div,
 | 
			
		||||
 | 
			
		||||
		eql,
 | 
			
		||||
		cgt,
 | 
			
		||||
		clt,
 | 
			
		||||
		cge,
 | 
			
		||||
		cle,
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	pub fn compile_ast(self: *Chunk, ast: Ast) void {
 | 
			
		||||
		for (ast.list_statements()) |statement| {
 | 
			
		||||
			switch (statement) {
 | 
			
		||||
				.return_expression => |return_expression| {
 | 
			
		||||
					try self.compile_expression(return_expression);
 | 
			
		||||
				},
 | 
			
		||||
 | 
			
		||||
				.return_nothing => {
 | 
			
		||||
					try self.emit(.push_nil);
 | 
			
		||||
				},
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pub fn compile_expression(self: *Chunk, expression: Ast.Expression) void {
 | 
			
		||||
		switch (expression) {
 | 
			
		||||
			.nil_literal => try self.emit(.push_nil),
 | 
			
		||||
			.true_literal => try self.emit(.push_true),
 | 
			
		||||
			.false_literal => try self.emit(.push_false),
 | 
			
		||||
			.integer_literal => |literal| try self.emit(if (literal == 0) .push_zero else .{.push_integer = literal}),
 | 
			
		||||
			.float_literal => |literal| try self.emit(if (literal == 0) .push_zero else .{.push_float = literal}),
 | 
			
		||||
			.string_literal => |literal| try self.emit(.{.push_object = try self.intern(literal)}),
 | 
			
		||||
 | 
			
		||||
			.array_literal => |elements| {
 | 
			
		||||
				if (elements.values.len > coral.math.max_int(@typeInfo(Int).Int)) {
 | 
			
		||||
					return error.OutOfMemory;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				for (elements.values) |element_expression| {
 | 
			
		||||
					try self.compile_expression(element_expression);
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				try self.emit(.{.push_array = @intCast(Int, elements.values.len)});
 | 
			
		||||
			},
 | 
			
		||||
 | 
			
		||||
			.table_literal => |fields| {
 | 
			
		||||
				if (fields.values.len > coral.math.max_int(@typeInfo(Int).Int)) {
 | 
			
		||||
					return error.OutOfMemory;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				for (fields.values) |field| {
 | 
			
		||||
					try self.compile_expression(field.expression);
 | 
			
		||||
					try self.emit(.{.push_object = try self.intern(field.identifier)});
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				try self.emit(.{.push_table = @intCast(Int, fields.values.len)});
 | 
			
		||||
			},
 | 
			
		||||
 | 
			
		||||
			.binary_operation => |operation| {
 | 
			
		||||
				try self.compile_expression(operation.lhs_expression.*);
 | 
			
		||||
				try self.compile_expression(operation.rhs_expression.*);
 | 
			
		||||
 | 
			
		||||
				try self.emit(switch (operation.operator) {
 | 
			
		||||
					.addition => .add,
 | 
			
		||||
					.subtraction => .sub,
 | 
			
		||||
					.multiplication => .mul,
 | 
			
		||||
					.divsion => .div,
 | 
			
		||||
					.greater_equals_comparison => .compare_eq,
 | 
			
		||||
					.greater_than_comparison => .compare_gt,
 | 
			
		||||
					.equals_comparison => .compare_ge,
 | 
			
		||||
					.less_than_comparison => .compare_lt,
 | 
			
		||||
					.less_equals_comparison => .compare_le,
 | 
			
		||||
				});
 | 
			
		||||
			},
 | 
			
		||||
 | 
			
		||||
			.unary_operation => |operation| {
 | 
			
		||||
				try self.compile_expression(operation.expression.*);
 | 
			
		||||
 | 
			
		||||
				try self.emit(switch (operation.operator) {
 | 
			
		||||
					.boolean_negation => .not,
 | 
			
		||||
					.numeric_negation => .neg,
 | 
			
		||||
				});
 | 
			
		||||
			},
 | 
			
		||||
 | 
			
		||||
			.grouped_expression => |grouped_expression| {
 | 
			
		||||
				try self.compile_expression(grouped_expression.*);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pub fn deinit(self: *Chunk) void {
 | 
			
		||||
		self.opcodes.deinit(self.env.allocator);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pub fn execute(self: *Chunk) RuntimeError!Value {
 | 
			
		||||
		_ = self;
 | 
			
		||||
 | 
			
		||||
		return Value.nil;
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub const Float = f64;
 | 
			
		||||
 | 
			
		||||
pub const Int = i32;
 | 
			
		||||
 | 
			
		||||
pub const Objectid = u32;
 | 
			
		||||
 | 
			
		||||
pub const RuntimeEnv = struct {
 | 
			
		||||
	output: coral.io.Writer,
 | 
			
		||||
	stack: coral.list.Stack(u64),
 | 
			
		||||
	interned: coral.table.Hashed([]const u8, Objectid, coral.table.string_keyer),
 | 
			
		||||
	objects: coral.slab.Map(@typeInfo(u32).Int, Object),
 | 
			
		||||
	user_allocator: coral.io.Allocator,
 | 
			
		||||
 | 
			
		||||
	pub const DataSource = struct {
 | 
			
		||||
		name: []const u8,
 | 
			
		||||
		data: []const u8,
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	pub const Limits = struct {
 | 
			
		||||
		stack_max: u32,
 | 
			
		||||
		calls_max: u32,
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const Object = struct {
 | 
			
		||||
		ref_count: usize,
 | 
			
		||||
 | 
			
		||||
		state: struct {
 | 
			
		||||
			info: ObjectInfo,
 | 
			
		||||
			userdata: []u8,
 | 
			
		||||
 | 
			
		||||
			fields: coral.table.Hashed(*Object, *Value, .{
 | 
			
		||||
				.hasher = struct {
 | 
			
		||||
					fn hash(object: *Object) coral.table.Hash {
 | 
			
		||||
						coral.debug.assert(object.state.info.identity == null);
 | 
			
		||||
 | 
			
		||||
						return coral.table.hash_string(object.state.userdata);
 | 
			
		||||
					}
 | 
			
		||||
				}.hash,
 | 
			
		||||
 | 
			
		||||
				.comparer = struct {
 | 
			
		||||
					fn compare(object_a: *Object, object_b: *Object) isize {
 | 
			
		||||
						coral.debug.assert(object_a.state.info.identity == null);
 | 
			
		||||
						coral.debug.assert(object_b.state.info.identity == null);
 | 
			
		||||
 | 
			
		||||
						return coral.io.compare(object_a.state.userdata, object_b.state.userdata);
 | 
			
		||||
					}
 | 
			
		||||
				}.compare,
 | 
			
		||||
			}),
 | 
			
		||||
		},
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	pub const ObjectInfo = struct {
 | 
			
		||||
		caller: *const fn (caller: Value, context: CallContext) RuntimeError!Value = default_call,
 | 
			
		||||
		cleaner: *const fn (userdata: []u8) void = default_clean,
 | 
			
		||||
		getter: *const fn (context: CallContext) RuntimeError!Value = default_get,
 | 
			
		||||
		identity: ?*const anyopaque = null,
 | 
			
		||||
		setter: *const fn (context: CallContext) RuntimeError!void = default_set,
 | 
			
		||||
 | 
			
		||||
		fn default_call(_: Value, context: CallContext) RuntimeError!Value {
 | 
			
		||||
			return context.env.fail(error.BadOperation, "attempt to call non-callable");
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		fn default_clean(_: []u8) void {
 | 
			
		||||
			// Nothing to clean up by default.
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		fn default_get(context: CallContext) RuntimeError!Value {
 | 
			
		||||
			return context.env.fail(error.BadOperation, "attempt to get non-indexable");
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		fn default_set(context: CallContext) RuntimeError!void {
 | 
			
		||||
			return context.env.fail(error.BadOperation, "attempt to set non-indexable");
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	pub fn call(self: *RuntimeEnv, caller: Value, maybe_index: ?Value, args: []const Value) RuntimeError!RuntimeVar {
 | 
			
		||||
		if (maybe_index) |index| {
 | 
			
		||||
			const callable = try self.get(caller, index);
 | 
			
		||||
 | 
			
		||||
			defer callable.deinit();
 | 
			
		||||
 | 
			
		||||
			return switch (callable.value.unpack()) {
 | 
			
		||||
				.objectid => |callable_id| .{
 | 
			
		||||
					.env = self,
 | 
			
		||||
 | 
			
		||||
					.value = try self.objects.fetch(callable_id).state.info.caller(.{
 | 
			
		||||
						.env = self,
 | 
			
		||||
						.callable = callable.value,
 | 
			
		||||
						.obj = caller,
 | 
			
		||||
						.args = args,
 | 
			
		||||
					}),
 | 
			
		||||
				},
 | 
			
		||||
 | 
			
		||||
				else => self.fail(error.BadOperation, "attempt to call non-object type"),
 | 
			
		||||
			};
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return self.bind(try self.objects.fetch(try self.to_objectid(caller)).state.info.caller(.{
 | 
			
		||||
			.env = self,
 | 
			
		||||
			.obj = caller,
 | 
			
		||||
			// .caller = .{.object = self.global_object},
 | 
			
		||||
			.args = args,
 | 
			
		||||
		}));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pub fn check(
 | 
			
		||||
		self: *RuntimeEnv,
 | 
			
		||||
		condition: bool,
 | 
			
		||||
		runtime_error: RuntimeError,
 | 
			
		||||
		failure_message: []const u8) RuntimeError!void {
 | 
			
		||||
 | 
			
		||||
		if (condition) {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return self.fail(runtime_error, failure_message);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pub fn deinit(self: *RuntimeEnv) void {
 | 
			
		||||
		self.stack.deinit();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pub fn execute_data(self: *RuntimeEnv, allocator: coral.io.Allocator, source: DataSource) RuntimeError!RuntimeVar {
 | 
			
		||||
		var ast = try Ast.init(allocator);
 | 
			
		||||
 | 
			
		||||
		defer ast.deinit();
 | 
			
		||||
 | 
			
		||||
		{
 | 
			
		||||
			var tokenizer = tokens.Tokenizer{.source = source.data};
 | 
			
		||||
 | 
			
		||||
			try ast.parse(&tokenizer);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var chunk = Chunk{
 | 
			
		||||
			.env = self,
 | 
			
		||||
			.opcodes = .{.allocator = allocator},
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		const typeid = "<chunk>";
 | 
			
		||||
 | 
			
		||||
		const script = try self.new_object(allocator, coral.io.bytes_of(&chunk), .{
 | 
			
		||||
			.identity = typeid,
 | 
			
		||||
 | 
			
		||||
			.cleaner = struct {
 | 
			
		||||
				fn clean(userdata: []const u8) void {
 | 
			
		||||
					@ptrCast(*Chunk, @alignCast(@alignOf(Chunk), userdata)).deinit();
 | 
			
		||||
				}
 | 
			
		||||
			}.clean,
 | 
			
		||||
 | 
			
		||||
			.caller = struct {
 | 
			
		||||
				fn call(caller: Value, context: CallContext) RuntimeError!Value {
 | 
			
		||||
					_ = caller;
 | 
			
		||||
 | 
			
		||||
					return (context.env.native_cast(context.obj, typeid, Chunk) catch unreachable).execute();
 | 
			
		||||
				}
 | 
			
		||||
			}.call,
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		defer script.deinit();
 | 
			
		||||
 | 
			
		||||
		return try self.call(script.value, null, &.{});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pub fn execute_file(
 | 
			
		||||
		self: *RuntimeEnv,
 | 
			
		||||
		allocator: coral.io.Allocator,
 | 
			
		||||
		file_system: file.System,
 | 
			
		||||
		file_path: file.Path) RuntimeError!RuntimeVar {
 | 
			
		||||
 | 
			
		||||
		const readable_file = file_system.open_readable(file_path) catch return error.SystemFailure;
 | 
			
		||||
 | 
			
		||||
		defer readable_file.close();
 | 
			
		||||
 | 
			
		||||
		var file_data = coral.list.ByteStack{.allocator = allocator};
 | 
			
		||||
		const file_size = (file_system.query_info(file_path) catch return error.SystemFailure).size;
 | 
			
		||||
 | 
			
		||||
		try file_data.grow(file_size);
 | 
			
		||||
 | 
			
		||||
		defer file_data.deinit();
 | 
			
		||||
 | 
			
		||||
		{
 | 
			
		||||
			var stream_buffer = [_]u8{0} ** 4096;
 | 
			
		||||
 | 
			
		||||
			if ((coral.io.stream(coral.list.stack_as_writer(&file_data), readable_file.as_reader(), &stream_buffer) catch {
 | 
			
		||||
				return error.SystemFailure;
 | 
			
		||||
			}) != file_size) {
 | 
			
		||||
				return error.SystemFailure;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return try self.execute_data(allocator, .{
 | 
			
		||||
			.name = file_path.to_string() catch return error.SystemFailure,
 | 
			
		||||
			.data = file_data.values,
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pub fn fail(self: *RuntimeEnv, runtime_error: RuntimeError, failure_message: []const u8) RuntimeError {
 | 
			
		||||
		// TODO: Call stack and line numbers.
 | 
			
		||||
		coral.utf8.print_formatted(self.output, "{name}@({line}): {message}\n", .{
 | 
			
		||||
			.name = ".ona",
 | 
			
		||||
			.line = @as(u64, 0),
 | 
			
		||||
			.message = failure_message,
 | 
			
		||||
		}) catch return error.SystemFailure;
 | 
			
		||||
 | 
			
		||||
		return runtime_error;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pub fn get(self: *RuntimeEnv, indexable: Value, index: Value) RuntimeError!RuntimeVar {
 | 
			
		||||
		const indexable_object = self.objects.fetch(switch (indexable.unpack()) {
 | 
			
		||||
			.objectid => |indexable_id| indexable_id,
 | 
			
		||||
			else => return self.fail(error.BadOperation, "attempt to index non-indexable type"),
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		return .{
 | 
			
		||||
			.env = self,
 | 
			
		||||
 | 
			
		||||
			.value = try indexable_object.state.info.getter(.{
 | 
			
		||||
				.env = self,
 | 
			
		||||
				.obj = indexable,
 | 
			
		||||
				.args = &.{index},
 | 
			
		||||
			}),
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pub fn init(allocator: coral.io.Allocator, output: coral.io.Writer, limits: Limits) RuntimeError!RuntimeEnv {
 | 
			
		||||
		var env = RuntimeEnv{
 | 
			
		||||
			.output = output,
 | 
			
		||||
			.stack = .{.allocator = allocator},
 | 
			
		||||
			.objects = .{.allocator = allocator},
 | 
			
		||||
			.interned = .{.allocator = allocator},
 | 
			
		||||
			.user_allocator = allocator,
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		try env.stack.grow(limits.stack_max * limits.calls_max);
 | 
			
		||||
 | 
			
		||||
		errdefer env.stack.deinit();
 | 
			
		||||
 | 
			
		||||
		return env;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pub fn intern(self: *RuntimeEnv, string: []const u8) RuntimeError!Value {
 | 
			
		||||
		return Value.pack_objectid(self.interned.lookup(string) orelse {
 | 
			
		||||
			const interned_value = (try self.new_string(string)).value;
 | 
			
		||||
 | 
			
		||||
			switch (interned_value.unpack()) {
 | 
			
		||||
				.objectid => |id| coral.debug.assert(try self.interned.insert(string, id)),
 | 
			
		||||
				else => unreachable,
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return interned_value;
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pub fn native_cast(self: *RuntimeEnv, castable: Value, id: *const anyopaque, comptime Type: type) RuntimeError!*Type {
 | 
			
		||||
		const object = self.objects.fetch(castable.to_objectid() orelse {
 | 
			
		||||
			return self.fail(error.BadOperation, "attempt to cast non-castable type");
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		const is_expected_type = (object.state.info.identity == id) and (object.state.userdata.len == @sizeOf(Type));
 | 
			
		||||
 | 
			
		||||
		try self.check(is_expected_type, "invalid object cast: native type");
 | 
			
		||||
 | 
			
		||||
		return @ptrCast(*Type, @alignCast(@alignOf(Type), object.state.userdata));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pub fn new_object(self: *RuntimeEnv, allocator: coral.io.Allocator, userdata: []const u8, info: ObjectInfo) RuntimeError!RuntimeVar {
 | 
			
		||||
		const allocation = try coral.io.allocate_many(allocator, userdata.len, u8);
 | 
			
		||||
 | 
			
		||||
		errdefer coral.io.deallocate(allocator, allocation);
 | 
			
		||||
 | 
			
		||||
		coral.io.copy(allocation, userdata);
 | 
			
		||||
 | 
			
		||||
		const objectid = try self.objects.insert(.{
 | 
			
		||||
			.ref_count = 1,
 | 
			
		||||
 | 
			
		||||
			.state = .{
 | 
			
		||||
				.info = info,
 | 
			
		||||
				.userdata = allocation,
 | 
			
		||||
				.fields = .{.allocator = allocator},
 | 
			
		||||
			},
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		return .{
 | 
			
		||||
			.env = self,
 | 
			
		||||
			.value = .{.data = @as(usize, 0x7FF8000000000002) | (@as(usize, objectid) << 32)},
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pub fn new_string(self: *RuntimeEnv, data: []const u8) RuntimeError!RuntimeVar {
 | 
			
		||||
		return try self.new_object(data, .{
 | 
			
		||||
			.getter = struct {
 | 
			
		||||
				fn get_byte(context: CallContext) RuntimeError!*Value {
 | 
			
		||||
					const string = context.env.string_cast(context.obj) catch unreachable;
 | 
			
		||||
					const index = try context.env.to_int(try context.arg_at(0));
 | 
			
		||||
 | 
			
		||||
					try context.env.check(coral.math.is_clamped(index, 0, string.len), "index out of string bounds");
 | 
			
		||||
 | 
			
		||||
					return context.env.new_int(string[@intCast(usize, index)]);
 | 
			
		||||
				}
 | 
			
		||||
			}.get_byte,
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub const RuntimeError = coral.io.AllocationError || Ast.ParseError || error {
 | 
			
		||||
	BadOperation,
 | 
			
		||||
	BadArgument,
 | 
			
		||||
	SystemFailure,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub const RuntimeVar = struct {
 | 
			
		||||
	value: Value,
 | 
			
		||||
	env: *RuntimeEnv,
 | 
			
		||||
 | 
			
		||||
	pub fn deinit(self: RuntimeVar) void {
 | 
			
		||||
		switch (self.value.unpack()) {
 | 
			
		||||
			.objectid => |id| {
 | 
			
		||||
				const object = self.env.objects.fetch(id);
 | 
			
		||||
 | 
			
		||||
				coral.debug.assert(object.ref_count != 0);
 | 
			
		||||
 | 
			
		||||
				object.ref_count -= 1;
 | 
			
		||||
 | 
			
		||||
				if (object.ref_count == 0) {
 | 
			
		||||
					object.state.info.cleaner(object.state.userdata);
 | 
			
		||||
					// TODO: Free individual key-value pairs of fields
 | 
			
		||||
					object.state.fields.deinit();
 | 
			
		||||
					coral.io.deallocate(self.env.user_allocator, object.state.userdata);
 | 
			
		||||
					self.env.objects.remove(id);
 | 
			
		||||
				}
 | 
			
		||||
			},
 | 
			
		||||
 | 
			
		||||
			else => {},
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub const Value = struct {
 | 
			
		||||
	data: u64,
 | 
			
		||||
 | 
			
		||||
	pub const Unpacked = union (enum) {
 | 
			
		||||
		nil,
 | 
			
		||||
		false,
 | 
			
		||||
		true,
 | 
			
		||||
		number: Float,
 | 
			
		||||
		objectid: Objectid,
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	fn pack_number(float: Float) Value {
 | 
			
		||||
		return @bitCast(Value, float);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fn pack_objectid(id: Objectid) Value {
 | 
			
		||||
		return signature.objectid | id;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pub const @"false" = @as(Value, nan | 0x0001000000000000);
 | 
			
		||||
 | 
			
		||||
	const mask = .{
 | 
			
		||||
		.sign = @as(u64, 0x8000000000000000),
 | 
			
		||||
		.exponent = @as(u64, 0x7ff0000000000000),
 | 
			
		||||
		.quiet = @as(u64, 0x0008000000000000),
 | 
			
		||||
		.type = @as(u64, 0x0007000000000000),
 | 
			
		||||
		.signature = @as(u64, 0xffff000000000000),
 | 
			
		||||
		.object = @as(u64, 0x00000000ffffffff),
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	pub const nan = @as(Value, mask.exponent | mask.quiet);
 | 
			
		||||
 | 
			
		||||
	pub const nil = @as(Value, nan | 0x0003000000000000);
 | 
			
		||||
 | 
			
		||||
	const signature = .{
 | 
			
		||||
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	pub const @"true" = @as(Value, nan | 0x0002000000000000);
 | 
			
		||||
 | 
			
		||||
	pub fn unpack(self: Value) Unpacked {
 | 
			
		||||
		if ((~self.data & mask.exponent) != 0) {
 | 
			
		||||
			return .{.number = @bitCast(Float, self.data)};
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return switch ((self.data & mask.signature) != 0) {
 | 
			
		||||
			.signature_nan => .{.number = @bitCast(Float, self.data)},
 | 
			
		||||
			.signature_false => .false,
 | 
			
		||||
			.signature_true => .true,
 | 
			
		||||
			.signature_object => @intCast(Objectid, self.data & mask.object),
 | 
			
		||||
			else => return .nil,
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -2,8 +2,6 @@ const coral = @import("coral");
 | 
			
		||||
 | 
			
		||||
const tokens = @import("./tokens.zig");
 | 
			
		||||
 | 
			
		||||
const types = @import("./types.zig");
 | 
			
		||||
 | 
			
		||||
allocator: coral.io.Allocator,
 | 
			
		||||
arena: coral.arena.Stacking,
 | 
			
		||||
statements: StatementList,
 | 
			
		||||
@ -39,8 +37,8 @@ pub const Expression = union (enum) {
 | 
			
		||||
	nil_literal,
 | 
			
		||||
	true_literal,
 | 
			
		||||
	false_literal,
 | 
			
		||||
	integer_literal: types.Integer,
 | 
			
		||||
	float_literal: types.Float,
 | 
			
		||||
	integer_literal: []const u8,
 | 
			
		||||
	float_literal: []const u8,
 | 
			
		||||
	string_literal: []const u8,
 | 
			
		||||
	array_literal: coral.list.Stack(Expression),
 | 
			
		||||
 | 
			
		||||
@ -63,7 +61,15 @@ pub const Expression = union (enum) {
 | 
			
		||||
	},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const ExpressionParser = fn (self: *Self, tokenizer: *tokens.Tokenizer) types.ParseError!Expression;
 | 
			
		||||
const ExpressionParser = fn (self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression;
 | 
			
		||||
 | 
			
		||||
///
 | 
			
		||||
///
 | 
			
		||||
///
 | 
			
		||||
pub const ParseError = error {
 | 
			
		||||
	OutOfMemory,
 | 
			
		||||
	BadSyntax,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const Self = @This();
 | 
			
		||||
 | 
			
		||||
@ -81,7 +87,7 @@ const UnaryOperator = enum {
 | 
			
		||||
 | 
			
		||||
fn binary_operation_parser(comptime parse_next: ExpressionParser, comptime operators: []const BinaryOperator) ExpressionParser {
 | 
			
		||||
	return struct {
 | 
			
		||||
		fn parse(self: *Self, tokenizer: *tokens.Tokenizer) types.ParseError!Expression {
 | 
			
		||||
		fn parse(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression {
 | 
			
		||||
			var expression = try parse_next(self, tokenizer);
 | 
			
		||||
 | 
			
		||||
			{
 | 
			
		||||
@ -111,7 +117,7 @@ fn binary_operation_parser(comptime parse_next: ExpressionParser, comptime opera
 | 
			
		||||
	}.parse;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn check_syntax(self: *Self, condition: bool, error_message: []const u8) types.ParseError!void {
 | 
			
		||||
fn check_syntax(self: *Self, condition: bool, error_message: []const u8) ParseError!void {
 | 
			
		||||
	if (condition) {
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
@ -120,11 +126,11 @@ fn check_syntax(self: *Self, condition: bool, error_message: []const u8) types.P
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn deinit(self: *Self) void {
 | 
			
		||||
	self.arena.clear_allocations();
 | 
			
		||||
	self.statements.deinit(self.allocator);
 | 
			
		||||
	self.arena.deinit();
 | 
			
		||||
	self.statements.deinit();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn fail_syntax(self: *Self, error_message: []const u8) types.ParseError {
 | 
			
		||||
fn fail_syntax(self: *Self, error_message: []const u8) ParseError {
 | 
			
		||||
	self.error_message = error_message;
 | 
			
		||||
 | 
			
		||||
	return error.BadSyntax;
 | 
			
		||||
@ -132,13 +138,9 @@ fn fail_syntax(self: *Self, error_message: []const u8) types.ParseError {
 | 
			
		||||
 | 
			
		||||
pub fn init(allocator: coral.io.Allocator) coral.io.AllocationError!Self {
 | 
			
		||||
	return Self{
 | 
			
		||||
		.arena = .{
 | 
			
		||||
			.base_allocator = allocator,
 | 
			
		||||
			.min_page_size = 4096,
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		.arena = try coral.arena.Stacking.init(allocator, 4096),
 | 
			
		||||
		.allocator = allocator,
 | 
			
		||||
		.statements = .{},
 | 
			
		||||
		.statements = .{.allocator = allocator},
 | 
			
		||||
		.error_message = "",
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
@ -147,7 +149,7 @@ pub fn list_statements(self: Self) []const Statement {
 | 
			
		||||
	return self.statements.values;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn parse(self: *Self, tokenizer: *tokens.Tokenizer) types.ParseError!void {
 | 
			
		||||
pub fn parse(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!void {
 | 
			
		||||
	self.reset();
 | 
			
		||||
 | 
			
		||||
	errdefer self.reset();
 | 
			
		||||
@ -159,7 +161,7 @@ pub fn parse(self: *Self, tokenizer: *tokens.Tokenizer) types.ParseError!void {
 | 
			
		||||
			.keyword_return => {
 | 
			
		||||
				try self.check_syntax(has_not_returned_yet, "cannot return more than once per function scope");
 | 
			
		||||
 | 
			
		||||
				try self.statements.push_one(self.allocator, get_statement: {
 | 
			
		||||
				try self.statements.push_one(get_statement: {
 | 
			
		||||
					if (tokenizer.step(.{.include_newlines = true})) {
 | 
			
		||||
						if (tokenizer.current_token != .newline) {
 | 
			
		||||
							break: get_statement .{.return_expression = try self.parse_expression(tokenizer)};
 | 
			
		||||
@ -199,7 +201,7 @@ const parse_expression = binary_operation_parser(parse_equality, &.{
 | 
			
		||||
	.subtraction,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) types.ParseError!Expression {
 | 
			
		||||
fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression {
 | 
			
		||||
	switch (tokenizer.current_token) {
 | 
			
		||||
		.symbol_paren_left => {
 | 
			
		||||
			try self.check_syntax(tokenizer.step(.{.include_newlines = false}), "expected an expression after `(`");
 | 
			
		||||
@ -216,26 +218,13 @@ fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) types.ParseError!Expr
 | 
			
		||||
		.integer => |value| {
 | 
			
		||||
			_ = tokenizer.step(.{.include_newlines = false});
 | 
			
		||||
 | 
			
		||||
			return Expression{
 | 
			
		||||
				.integer_literal = coral.utf8.parse_decimal(types.Integer, value, .{}) catch |parse_error| {
 | 
			
		||||
					return self.fail_syntax(switch (parse_error) {
 | 
			
		||||
						error.BadSyntax => "invalid integer literal",
 | 
			
		||||
						error.IntOverflow => "integer literal is too big",
 | 
			
		||||
					});
 | 
			
		||||
				},
 | 
			
		||||
			};
 | 
			
		||||
			return Expression{.integer_literal = value};
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		.real => |value| {
 | 
			
		||||
			_ = tokenizer.step(.{.include_newlines = false});
 | 
			
		||||
 | 
			
		||||
			return Expression{
 | 
			
		||||
				.float_literal = coral.utf8.parse_decimal(types.Float, value, .{}) catch |parse_error| {
 | 
			
		||||
					return self.fail_syntax(switch (parse_error) {
 | 
			
		||||
						error.BadSyntax => "invalid float literal",
 | 
			
		||||
					});
 | 
			
		||||
				},
 | 
			
		||||
			};
 | 
			
		||||
			return Expression{.float_literal = value};
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		.string => |value| {
 | 
			
		||||
@ -247,14 +236,17 @@ fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) types.ParseError!Expr
 | 
			
		||||
		.symbol_bracket_left => {
 | 
			
		||||
			try self.check_syntax(tokenizer.step(.{.include_newlines = false}), "unexpected end of array literal");
 | 
			
		||||
 | 
			
		||||
			var expression = Expression{.array_literal = .{}};
 | 
			
		||||
			var expression = Expression{
 | 
			
		||||
				.array_literal = .{
 | 
			
		||||
					.allocator = self.arena.as_allocator(),
 | 
			
		||||
				},
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			coral.debug.assert(expression == .array_literal);
 | 
			
		||||
 | 
			
		||||
			const allocator = self.arena.as_allocator();
 | 
			
		||||
			const array_average_maximum = 32;
 | 
			
		||||
 | 
			
		||||
			try expression.array_literal.grow(allocator, array_average_maximum);
 | 
			
		||||
			try expression.array_literal.grow(array_average_maximum);
 | 
			
		||||
 | 
			
		||||
			while (true) {
 | 
			
		||||
				switch (tokenizer.current_token) {
 | 
			
		||||
@ -269,7 +261,7 @@ fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) types.ParseError!Expr
 | 
			
		||||
							tokenizer.step(.{.include_newlines = false}),
 | 
			
		||||
							"expected `]` or expression after `[`");
 | 
			
		||||
 | 
			
		||||
						try expression.array_literal.push_one(allocator, try self.parse_expression(tokenizer));
 | 
			
		||||
						try expression.array_literal.push_one(try self.parse_expression(tokenizer));
 | 
			
		||||
					},
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
@ -278,12 +270,14 @@ fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) types.ParseError!Expr
 | 
			
		||||
		.symbol_brace_left => {
 | 
			
		||||
			try self.check_syntax(tokenizer.step(.{.include_newlines = false}), "unexpected end of table literal");
 | 
			
		||||
 | 
			
		||||
			var expression = Expression{.table_literal = .{}};
 | 
			
		||||
			var expression = Expression{
 | 
			
		||||
				.table_literal = .{
 | 
			
		||||
					.allocator = self.arena.as_allocator(),
 | 
			
		||||
				},
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			coral.debug.assert(expression == .table_literal);
 | 
			
		||||
 | 
			
		||||
			const allocator = self.arena.as_allocator();
 | 
			
		||||
 | 
			
		||||
			while (true) {
 | 
			
		||||
				switch (tokenizer.current_token) {
 | 
			
		||||
					.symbol_brace_right => {
 | 
			
		||||
@ -299,7 +293,7 @@ fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) types.ParseError!Expr
 | 
			
		||||
 | 
			
		||||
						try self.check_syntax(tokenizer.step(.{.include_newlines = false}), "unexpected end after `=`");
 | 
			
		||||
 | 
			
		||||
						try expression.table_literal.push_one(allocator, .{
 | 
			
		||||
						try expression.table_literal.push_one(.{
 | 
			
		||||
							.identifier = identifier,
 | 
			
		||||
							.expression = try self.parse_expression(tokenizer),
 | 
			
		||||
						});
 | 
			
		||||
@ -365,5 +359,5 @@ const parse_term = binary_operation_parser(parse_factor, &.{
 | 
			
		||||
 | 
			
		||||
pub fn reset(self: *Self) void {
 | 
			
		||||
	self.statements.clear();
 | 
			
		||||
	self.arena.clear_allocations();
 | 
			
		||||
	self.arena.deinit();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,247 +0,0 @@
 | 
			
		||||
const Ast = @import("./Ast.zig");
 | 
			
		||||
 | 
			
		||||
const Environment = @import("./Environment.zig");
 | 
			
		||||
 | 
			
		||||
const coral = @import("coral");
 | 
			
		||||
 | 
			
		||||
const types = @import("./types.zig");
 | 
			
		||||
 | 
			
		||||
const tokens = @import("./tokens.zig");
 | 
			
		||||
 | 
			
		||||
env: *Environment,
 | 
			
		||||
message_name_len: usize,
 | 
			
		||||
message_data: Buffer,
 | 
			
		||||
bytecode_buffer: Buffer,
 | 
			
		||||
 | 
			
		||||
const Buffer = coral.list.Stack(u8);
 | 
			
		||||
 | 
			
		||||
const Opcode = enum (u8) {
 | 
			
		||||
	ret,
 | 
			
		||||
 | 
			
		||||
	push_nil,
 | 
			
		||||
	push_true,
 | 
			
		||||
	push_false,
 | 
			
		||||
	push_zero,
 | 
			
		||||
	push_integer,
 | 
			
		||||
	push_float,
 | 
			
		||||
	push_object,
 | 
			
		||||
	push_array,
 | 
			
		||||
	push_table,
 | 
			
		||||
 | 
			
		||||
	not,
 | 
			
		||||
	neg,
 | 
			
		||||
 | 
			
		||||
	add,
 | 
			
		||||
	sub,
 | 
			
		||||
	mul,
 | 
			
		||||
	div,
 | 
			
		||||
 | 
			
		||||
	compare_eq,
 | 
			
		||||
	compare_gt,
 | 
			
		||||
	compare_lt,
 | 
			
		||||
	compare_ge,
 | 
			
		||||
	compare_le,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const Self = @This();
 | 
			
		||||
 | 
			
		||||
fn clear_error_details(self: *Self) void {
 | 
			
		||||
	coral.debug.assert(self.message_data.values.len >= self.message_name_len);
 | 
			
		||||
	coral.debug.assert(self.message_data.drop(self.message_data.values.len - self.message_name_len));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn compile(self: *Self, data: []const u8) types.RuntimeError!void {
 | 
			
		||||
	var ast = try Ast.init(self.env.allocator);
 | 
			
		||||
 | 
			
		||||
	defer ast.deinit();
 | 
			
		||||
 | 
			
		||||
	{
 | 
			
		||||
		var tokenizer = tokens.Tokenizer{.source = data};
 | 
			
		||||
 | 
			
		||||
		ast.parse(&tokenizer) catch |init_error| {
 | 
			
		||||
			if (init_error == error.BadSyntax) {
 | 
			
		||||
				self.clear_error_details();
 | 
			
		||||
 | 
			
		||||
				var writable_data = coral.list.Writable{
 | 
			
		||||
					.allocator = self.env.allocator,
 | 
			
		||||
					.list = .{.stack = &self.message_data},
 | 
			
		||||
				};
 | 
			
		||||
 | 
			
		||||
				coral.utf8.print_formatted(writable_data.as_writer(), "@({line}): {name}", .{
 | 
			
		||||
					.line = tokenizer.lines_stepped,
 | 
			
		||||
					.name = ast.error_message,
 | 
			
		||||
				}) catch return error.OutOfMemory;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return init_error;
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for (ast.list_statements()) |statement| {
 | 
			
		||||
		switch (statement) {
 | 
			
		||||
			.return_expression => |return_expression| {
 | 
			
		||||
				try self.compile_expression(return_expression);
 | 
			
		||||
				try self.emit_opcode(.ret);
 | 
			
		||||
			},
 | 
			
		||||
 | 
			
		||||
			.return_nothing => {
 | 
			
		||||
				try self.emit_opcode(.push_nil);
 | 
			
		||||
				try self.emit_opcode(.ret);
 | 
			
		||||
			},
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn compile_expression(self: *Self, expression: Ast.Expression) types.RuntimeError!void {
 | 
			
		||||
	switch (expression) {
 | 
			
		||||
		.nil_literal => try self.emit_opcode(.push_nil),
 | 
			
		||||
		.true_literal => try self.emit_opcode(.push_true),
 | 
			
		||||
		.false_literal => try self.emit_opcode(.push_false),
 | 
			
		||||
 | 
			
		||||
		.integer_literal => |literal| {
 | 
			
		||||
			if (literal == 0) {
 | 
			
		||||
				try self.emit_opcode(.push_zero);
 | 
			
		||||
			} else {
 | 
			
		||||
				try self.emit_opcode(.push_integer);
 | 
			
		||||
				try self.emit_float(0);
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		.float_literal => |literal| {
 | 
			
		||||
			if (literal == 0) {
 | 
			
		||||
				try self.emit_opcode(.push_zero);
 | 
			
		||||
			} else {
 | 
			
		||||
				try self.emit_opcode(.push_float);
 | 
			
		||||
				try self.emit_float(literal);
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		.string_literal => |literal| {
 | 
			
		||||
			try self.emit_opcode(.push_object);
 | 
			
		||||
			try self.emit_object(try self.intern(literal));
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		.array_literal => |elements| {
 | 
			
		||||
			if (elements.values.len > coral.math.max_int(@typeInfo(types.Integer).Int)) {
 | 
			
		||||
				return error.OutOfMemory;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			for (elements.values) |element_expression| {
 | 
			
		||||
				try self.compile_expression(element_expression);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			try self.emit_opcode(.push_array);
 | 
			
		||||
			try self.emit_integer(@intCast(types.Integer, elements.values.len));
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		.table_literal => |fields| {
 | 
			
		||||
			if (fields.values.len > coral.math.max_int(@typeInfo(types.Integer).Int)) {
 | 
			
		||||
				return error.OutOfMemory;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			for (fields.values) |field| {
 | 
			
		||||
				try self.compile_expression(field.expression);
 | 
			
		||||
				try self.emit_opcode(.push_object);
 | 
			
		||||
				try self.emit_object(try self.intern(field.identifier));
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			try self.emit_opcode(.push_table);
 | 
			
		||||
			try self.emit_integer(@intCast(types.Integer, fields.values.len));
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		.binary_operation => |operation| {
 | 
			
		||||
			try self.compile_expression(operation.lhs_expression.*);
 | 
			
		||||
			try self.compile_expression(operation.rhs_expression.*);
 | 
			
		||||
 | 
			
		||||
			try self.emit_opcode(switch (operation.operator) {
 | 
			
		||||
				.addition => .add,
 | 
			
		||||
				.subtraction => .sub,
 | 
			
		||||
				.multiplication => .mul,
 | 
			
		||||
				.divsion => .div,
 | 
			
		||||
				.greater_equals_comparison => .compare_eq,
 | 
			
		||||
				.greater_than_comparison => .compare_gt,
 | 
			
		||||
				.equals_comparison => .compare_ge,
 | 
			
		||||
				.less_than_comparison => .compare_lt,
 | 
			
		||||
				.less_equals_comparison => .compare_le,
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		.unary_operation => |operation| {
 | 
			
		||||
			try self.compile_expression(operation.expression.*);
 | 
			
		||||
 | 
			
		||||
			try self.emit_opcode(switch (operation.operator) {
 | 
			
		||||
				.boolean_negation => .not,
 | 
			
		||||
				.numeric_negation => .neg,
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		.grouped_expression => |grouped_expression| {
 | 
			
		||||
			try self.compile_expression(grouped_expression.*);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn deinit(self: *Self) void {
 | 
			
		||||
	self.bytecode_buffer.deinit(self.env.allocator);
 | 
			
		||||
	self.message_data.deinit(self.env.allocator);
 | 
			
		||||
 | 
			
		||||
	self.message_name_len = 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn emit_float(self: *Self, float: types.Float) coral.io.AllocationError!void {
 | 
			
		||||
	try self.bytecode_buffer.push_all(self.env.allocator, coral.io.bytes_of(&float));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn emit_integer(self: *Self, integer: types.Integer) coral.io.AllocationError!void {
 | 
			
		||||
	try self.bytecode_buffer.push_all(self.env.allocator, coral.io.bytes_of(&integer));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn emit_object(self: *Self, object: types.Object) coral.io.AllocationError!void {
 | 
			
		||||
	try self.bytecode_buffer.push_all(self.env.allocator, coral.io.bytes_of(&object));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn emit_opcode(self: *Self, opcode: Opcode) coral.io.AllocationError!void {
 | 
			
		||||
	try self.bytecode_buffer.push_one(self.env.allocator, @enumToInt(opcode));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn error_details(self: Self) []const u8 {
 | 
			
		||||
	coral.debug.assert(self.message_data.values.len >= self.message_name_len);
 | 
			
		||||
 | 
			
		||||
	return self.message_data.values;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn execute(self: *Self) types.RuntimeError!types.Val {
 | 
			
		||||
	_ = self;
 | 
			
		||||
	// TODO: Implement.
 | 
			
		||||
 | 
			
		||||
	return .nil;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn init(env: *Environment, chunk_name: []const u8) coral.io.AllocationError!Self {
 | 
			
		||||
	var message_data = Buffer{};
 | 
			
		||||
 | 
			
		||||
	try message_data.push_all(env.allocator, chunk_name);
 | 
			
		||||
 | 
			
		||||
	errdefer message_data.deinit(env.allocator);
 | 
			
		||||
 | 
			
		||||
	return Self{
 | 
			
		||||
		.env = env,
 | 
			
		||||
		.message_data = message_data,
 | 
			
		||||
		.bytecode_buffer = .{},
 | 
			
		||||
		.message_name_len = chunk_name.len,
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn intern(self: *Self, string: []const u8) coral.io.AllocationError!types.Object {
 | 
			
		||||
	const interned_string = try self.env.intern(string);
 | 
			
		||||
 | 
			
		||||
	coral.debug.assert(interned_string == .object);
 | 
			
		||||
 | 
			
		||||
	return interned_string.object;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn name(self: Self) []const u8 {
 | 
			
		||||
	coral.debug.assert(self.message_data.values.len >= self.message_name_len);
 | 
			
		||||
 | 
			
		||||
	return self.message_data.values[0 .. self.message_name_len];
 | 
			
		||||
}
 | 
			
		||||
@ -1,466 +0,0 @@
 | 
			
		||||
const Chunk = @import("./Chunk.zig");
 | 
			
		||||
 | 
			
		||||
const coral = @import("coral");
 | 
			
		||||
 | 
			
		||||
const file = @import("../file.zig");
 | 
			
		||||
 | 
			
		||||
const types = @import("./types.zig");
 | 
			
		||||
 | 
			
		||||
const tokens = @import("./tokens.zig");
 | 
			
		||||
 | 
			
		||||
allocator: coral.io.Allocator,
 | 
			
		||||
heap: ObjectSlab,
 | 
			
		||||
global_object: types.Object,
 | 
			
		||||
interned: InternTable,
 | 
			
		||||
reporter: Reporter,
 | 
			
		||||
values: ValueStack,
 | 
			
		||||
calls: CallStack,
 | 
			
		||||
 | 
			
		||||
const CallStack = coral.list.Stack(struct {
 | 
			
		||||
	ip: usize,
 | 
			
		||||
	slots: []types.Val,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
pub const DataSource = struct {
 | 
			
		||||
	name: []const u8,
 | 
			
		||||
	data: []const u8,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub const ExecuteFileError = file.System.OpenError || coral.io.StreamError || file.ReadError || types.RuntimeError;
 | 
			
		||||
 | 
			
		||||
pub const InitOptions = struct {
 | 
			
		||||
	values_max: u32,
 | 
			
		||||
	calls_max: u32,
 | 
			
		||||
	reporter: Reporter,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const InternTable = coral.table.Hashed([]const u8, types.Object, coral.table.string_keyer);
 | 
			
		||||
 | 
			
		||||
const Object = struct {
 | 
			
		||||
	ref_count: usize,
 | 
			
		||||
 | 
			
		||||
	state: struct {
 | 
			
		||||
		info: ObjectInfo,
 | 
			
		||||
		userdata: []u8,
 | 
			
		||||
		fields: Fields,
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	const Fields = coral.table.Hashed(*Object, types.Val, .{
 | 
			
		||||
		.hasher = struct {
 | 
			
		||||
			fn hash(object: *Object) coral.table.Hash {
 | 
			
		||||
				coral.debug.assert(object.state.info.identity == null);
 | 
			
		||||
 | 
			
		||||
				return coral.table.hash_string(object.state.userdata);
 | 
			
		||||
			}
 | 
			
		||||
		}.hash,
 | 
			
		||||
 | 
			
		||||
		.comparer = struct {
 | 
			
		||||
			fn compare(object_a: *Object, object_b: *Object) isize {
 | 
			
		||||
				coral.debug.assert(object_a.state.info.identity == null);
 | 
			
		||||
				coral.debug.assert(object_b.state.info.identity == null);
 | 
			
		||||
 | 
			
		||||
				return coral.io.compare(object_a.state.userdata, object_b.state.userdata);
 | 
			
		||||
			}
 | 
			
		||||
		}.compare,
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	pub fn acquire(self: *Object) void {
 | 
			
		||||
		coral.debug.assert(self.ref_count != 0);
 | 
			
		||||
 | 
			
		||||
		self.ref_count += 1;
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub const ObjectInfo = struct {
 | 
			
		||||
	caller: *const Caller = default_call,
 | 
			
		||||
	deinitializer: *const Deinitializer = default_deinitialize,
 | 
			
		||||
	getter: *const Getter = default_get,
 | 
			
		||||
	identity: ?*const anyopaque = null,
 | 
			
		||||
	setter: *const Setter = default_set,
 | 
			
		||||
 | 
			
		||||
	pub const CallContext = struct {
 | 
			
		||||
		env: *Self,
 | 
			
		||||
		caller: types.Ref,
 | 
			
		||||
		callable: types.Ref,
 | 
			
		||||
		args: []const types.Ref,
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	pub const Caller = fn (context: CallContext) types.RuntimeError!types.Val;
 | 
			
		||||
 | 
			
		||||
	pub const DeinitializeContext = struct {
 | 
			
		||||
		env: *Self,
 | 
			
		||||
		obj: types.Ref,
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	pub const Deinitializer = fn (context: DeinitializeContext) void;
 | 
			
		||||
 | 
			
		||||
	pub const GetContext = struct {
 | 
			
		||||
		env: *Self,
 | 
			
		||||
		indexable: types.Ref,
 | 
			
		||||
		index: types.Ref,
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	pub const Getter = fn (context: GetContext) types.RuntimeError!types.Val;
 | 
			
		||||
 | 
			
		||||
	pub const SetContext = struct {
 | 
			
		||||
		env: *Self,
 | 
			
		||||
		indexable: types.Ref,
 | 
			
		||||
		index: types.Ref,
 | 
			
		||||
		value: types.Ref,
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	pub const Setter = fn (context: SetContext) types.RuntimeError!void;
 | 
			
		||||
 | 
			
		||||
	fn default_call(context: CallContext) types.RuntimeError!types.Val {
 | 
			
		||||
		return context.env.fail("attempt to call non-callable");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fn default_deinitialize(_: DeinitializeContext) void {
 | 
			
		||||
		// Nothing to deinitialize by default.
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fn default_get(context: GetContext) types.RuntimeError!types.Val {
 | 
			
		||||
		return context.env.get_field(context.indexable, context.index);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fn default_set(context: SetContext) types.RuntimeError!void {
 | 
			
		||||
		return context.env.fail("attempt to set non-indexable");
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const ObjectSlab = coral.slab.Map(@typeInfo(u32).Int, Object);
 | 
			
		||||
 | 
			
		||||
pub const Reporter = coral.io.Functor(void, []const u8);
 | 
			
		||||
 | 
			
		||||
const Self = @This();
 | 
			
		||||
 | 
			
		||||
const ValueStack = coral.list.Stack(types.Ref);
 | 
			
		||||
 | 
			
		||||
pub fn call(self: *Self, caller: types.Ref, maybe_index: ?types.Ref, args: []const types.Ref) types.RuntimeError!types.Val {
 | 
			
		||||
	if (maybe_index) |index| {
 | 
			
		||||
		try self.check(caller == .object, "invalid type conversion: object");
 | 
			
		||||
 | 
			
		||||
		const callable = try self.get_object(caller, index);
 | 
			
		||||
 | 
			
		||||
		defer self.discard(callable);
 | 
			
		||||
		try self.check(callable == .object, "invalid type conversion: object");
 | 
			
		||||
 | 
			
		||||
		return self.heap.fetch(callable.object).state.info.caller(.{
 | 
			
		||||
			.env = self,
 | 
			
		||||
			.callable = callable.as_ref(),
 | 
			
		||||
			.caller = caller,
 | 
			
		||||
			.args = args,
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return self.heap.fetch(caller.object).state.info.caller(.{
 | 
			
		||||
		.env = self,
 | 
			
		||||
		.callable = caller,
 | 
			
		||||
		.caller = .{.object = self.global_object},
 | 
			
		||||
		.args = args,
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn check(self: *Self, condition: bool, failure_message: []const u8) !void {
 | 
			
		||||
	if (condition) {
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return self.fail(failure_message);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn deinit(self: *Self) void {
 | 
			
		||||
	self.object_release(self.global_object);
 | 
			
		||||
 | 
			
		||||
	{
 | 
			
		||||
		var interned_iterable = InternTable.Iterable{.hashed_map = &self.interned};
 | 
			
		||||
 | 
			
		||||
		while (interned_iterable.next()) |entry| {
 | 
			
		||||
			self.object_release(entry.value);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	self.interned.deinit(self.allocator);
 | 
			
		||||
	self.values.deinit(self.allocator);
 | 
			
		||||
	self.calls.deinit(self.allocator);
 | 
			
		||||
	coral.debug.assert(self.heap.is_empty());
 | 
			
		||||
	self.heap.deinit(self.allocator);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn discard(self: *Self, val: types.Val) void {
 | 
			
		||||
	switch (val) {
 | 
			
		||||
		.object => |object| self.object_release(object),
 | 
			
		||||
		else => {},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn execute_data(self: *Self, source: DataSource) types.RuntimeError!types.Val {
 | 
			
		||||
	const typeid = "<chunk>";
 | 
			
		||||
 | 
			
		||||
	const Behaviors = struct {
 | 
			
		||||
		fn call(context: ObjectInfo.CallContext) types.RuntimeError!types.Val {
 | 
			
		||||
			return (context.env.native_cast(context.callable, typeid, Chunk) catch unreachable).execute();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		fn deinitialize(context: ObjectInfo.DeinitializeContext) void {
 | 
			
		||||
			(context.env.native_cast(context.obj, typeid, Chunk) catch unreachable).deinit();
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	var compiled_chunk = init_compiled_chunk: {
 | 
			
		||||
		var chunk = try Chunk.init(self, source.name);
 | 
			
		||||
 | 
			
		||||
		errdefer chunk.deinit();
 | 
			
		||||
 | 
			
		||||
		chunk.compile(source.data) catch |compile_error| {
 | 
			
		||||
			self.reporter.invoke(chunk.error_details());
 | 
			
		||||
 | 
			
		||||
			return compile_error;
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		break: init_compiled_chunk chunk;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const script = try self.new_object(coral.io.bytes_of(&compiled_chunk), .{
 | 
			
		||||
		.identity = typeid,
 | 
			
		||||
		.deinitializer = Behaviors.deinitialize,
 | 
			
		||||
		.caller = Behaviors.call,
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	defer self.discard(script);
 | 
			
		||||
 | 
			
		||||
	return try self.call(script.as_ref(), null, &.{});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn execute_file(self: *Self, fs: file.System, file_path: file.Path) ExecuteFileError!types.Val {
 | 
			
		||||
	const readable_file = try fs.open_readable(file_path);
 | 
			
		||||
 | 
			
		||||
	defer readable_file.close();
 | 
			
		||||
 | 
			
		||||
	var file_data = coral.list.Stack(u8){};
 | 
			
		||||
	const file_size = (try fs.query_info(file_path)).size;
 | 
			
		||||
 | 
			
		||||
	try file_data.grow(self.allocator, file_size);
 | 
			
		||||
 | 
			
		||||
	defer file_data.deinit(self.allocator);
 | 
			
		||||
 | 
			
		||||
	{
 | 
			
		||||
		var writable_data = coral.list.Writable{
 | 
			
		||||
			.allocator = self.allocator,
 | 
			
		||||
			.list = .{.stack = &file_data},
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		var stream_buffer = [_]u8{0} ** 4096;
 | 
			
		||||
 | 
			
		||||
		if ((try coral.io.stream(writable_data.as_writer(), readable_file.as_reader(), &stream_buffer)) != file_size) {
 | 
			
		||||
			return error.ReadFailure;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return try self.execute_data(.{
 | 
			
		||||
		.name = try file_path.to_string(),
 | 
			
		||||
		.data = file_data.values,
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn fail(self: *Self, failure_message: []const u8) types.CheckError {
 | 
			
		||||
	self.reporter.invoke(failure_message);
 | 
			
		||||
 | 
			
		||||
	return error.CheckFailed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn get_field(self: *Self, indexable: types.Ref, field: types.Ref) !types.Val {
 | 
			
		||||
	try self.check(indexable == .object, "invalid type conversion: object");
 | 
			
		||||
	try self.check(field == .object, "invalid type conversion: object");
 | 
			
		||||
 | 
			
		||||
	const value = get_value: {
 | 
			
		||||
		var field_data = self.heap.fetch(field.object);
 | 
			
		||||
 | 
			
		||||
		break: get_value self.heap.fetch(indexable.object).state.fields.lookup(&field_data) orelse {
 | 
			
		||||
			return .nil;
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	if (value == .object) {
 | 
			
		||||
		var value_data = self.heap.fetch(value.object);
 | 
			
		||||
 | 
			
		||||
		value_data.acquire();
 | 
			
		||||
		self.heap.assign(value.object, value_data);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return value;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn get_object(self: *Self, indexable: types.Ref, index: types.Ref) types.RuntimeError!types.Val {
 | 
			
		||||
	try self.check(indexable == .object, "invalid type conversion: object");
 | 
			
		||||
 | 
			
		||||
	return self.heap.fetch(indexable.object).state.info.getter(.{
 | 
			
		||||
		.env = self,
 | 
			
		||||
		.indexable = indexable,
 | 
			
		||||
		.index = index,
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn init(allocator: coral.io.Allocator, options: InitOptions) !Self {
 | 
			
		||||
	var env = Self{
 | 
			
		||||
		.global_object = 0,
 | 
			
		||||
		.allocator = allocator,
 | 
			
		||||
		.reporter = options.reporter,
 | 
			
		||||
		.interned = .{},
 | 
			
		||||
		.values = .{},
 | 
			
		||||
		.calls = .{},
 | 
			
		||||
		.heap = .{},
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	errdefer {
 | 
			
		||||
		env.values.deinit(allocator);
 | 
			
		||||
		env.calls.deinit(allocator);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	try env.values.grow(allocator, options.values_max * options.calls_max);
 | 
			
		||||
	try env.calls.grow(allocator, options.calls_max);
 | 
			
		||||
 | 
			
		||||
	{
 | 
			
		||||
		const globals = try env.new_object(&.{}, .{
 | 
			
		||||
			.identity = "KYM GLOBAL OBJECT OC DO NOT STEAL",
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		coral.debug.assert(globals == .object);
 | 
			
		||||
 | 
			
		||||
		env.global_object = globals.object;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return env;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn intern(self: *Self, string: []const u8) coral.io.AllocationError!types.Ref {
 | 
			
		||||
	return .{.object = self.interned.lookup(string) orelse {
 | 
			
		||||
		const reference = try self.new_string(string);
 | 
			
		||||
 | 
			
		||||
		coral.debug.assert(reference == .object);
 | 
			
		||||
		coral.debug.assert(try self.interned.insert(self.allocator, string, reference.object));
 | 
			
		||||
 | 
			
		||||
		return .{.object = reference.object};
 | 
			
		||||
	}};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn native_cast(self: *Self, castable: types.Ref, id: *const anyopaque, comptime Type: type) types.RuntimeError!*Type {
 | 
			
		||||
	try self.check(castable == .object, "invalid type conversion: object");
 | 
			
		||||
 | 
			
		||||
	const object = self.heap.fetch(castable.object);
 | 
			
		||||
	const is_expected_type = (object.state.info.identity == id) and (object.state.userdata.len == @sizeOf(Type));
 | 
			
		||||
 | 
			
		||||
	try self.check(is_expected_type, "invalid object cast: native type");
 | 
			
		||||
 | 
			
		||||
	return @ptrCast(*Type, @alignCast(@alignOf(Type), object.state.userdata));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn new_array(self: *Self) coral.io.AllocationError!types.Val {
 | 
			
		||||
	return try self.new_object(.{
 | 
			
		||||
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn new_object(self: *Self, userdata: []const u8, info: ObjectInfo) coral.io.AllocationError!types.Val {
 | 
			
		||||
	const allocation = try coral.io.allocate_many(self.allocator, userdata.len, u8);
 | 
			
		||||
 | 
			
		||||
	errdefer coral.io.deallocate(self.allocator, allocation);
 | 
			
		||||
 | 
			
		||||
	coral.io.copy(allocation, userdata);
 | 
			
		||||
 | 
			
		||||
	return .{.object = try self.heap.insert(self.allocator, .{
 | 
			
		||||
		.ref_count = 1,
 | 
			
		||||
 | 
			
		||||
		.state = .{
 | 
			
		||||
			.info = info,
 | 
			
		||||
			.userdata = allocation,
 | 
			
		||||
			.fields = .{},
 | 
			
		||||
		},
 | 
			
		||||
	})};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn new_string(self: *Self, data: []const u8) coral.io.AllocationError!types.Val {
 | 
			
		||||
	const Behavior = struct {
 | 
			
		||||
		fn get_byte(context: ObjectInfo.GetContext) types.RuntimeError!types.Val {
 | 
			
		||||
			switch (context.index) {
 | 
			
		||||
				.integer => |integer| {
 | 
			
		||||
					const string = context.env.string_cast(context.indexable) catch unreachable;
 | 
			
		||||
 | 
			
		||||
					try context.env.check(coral.math.is_clamped(integer, 0, string.len), "index out of string bounds");
 | 
			
		||||
 | 
			
		||||
					return types.Val{.integer = string[@intCast(usize, integer)]};
 | 
			
		||||
				},
 | 
			
		||||
 | 
			
		||||
				else => return context.env.fail("attempt to index string with non-integer value"),
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	return try self.new_object(data, .{
 | 
			
		||||
		.getter = Behavior.get_byte,
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn object_release(self: *Self, object: types.Object) void {
 | 
			
		||||
	var data = self.heap.fetch(object);
 | 
			
		||||
 | 
			
		||||
	coral.debug.assert(data.ref_count != 0);
 | 
			
		||||
 | 
			
		||||
	data.ref_count -= 1;
 | 
			
		||||
 | 
			
		||||
	if (data.ref_count == 0) {
 | 
			
		||||
		data.state.info.deinitializer(.{
 | 
			
		||||
			.env = self,
 | 
			
		||||
			.obj = .{.object = object},
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		// TODO: Free individual key-value pairs of fields
 | 
			
		||||
		data.state.fields.deinit(self.allocator);
 | 
			
		||||
		coral.io.deallocate(self.allocator, data.state.userdata);
 | 
			
		||||
		self.heap.remove(object);
 | 
			
		||||
	} else {
 | 
			
		||||
		self.heap.assign(object, data);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn set_global(self: *Self, global_name: []const u8, value: types.Ref) coral.io.AllocationError!void {
 | 
			
		||||
	try self.globals.assign(self.allocator, global_name, value);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn set_object(self: *Self, obj: *Object, index: types.Ref, value: types.Ref) types.RuntimeError!void {
 | 
			
		||||
	return obj.behavior.setter(.{
 | 
			
		||||
		.env = self,
 | 
			
		||||
		.obj = obj,
 | 
			
		||||
		.index = index,
 | 
			
		||||
		.value = value,
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn string_cast(self: *Self, value: types.Ref) ![]const u8 {
 | 
			
		||||
	try self.check(value == .object, "invalid type conversion: object");
 | 
			
		||||
 | 
			
		||||
	const object = self.heap.fetch(value.object);
 | 
			
		||||
 | 
			
		||||
	try self.check(object.state.info.identity == null, "invalid object cast: string");
 | 
			
		||||
 | 
			
		||||
	return object.state.userdata;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn to_integer(self: *Self, value: types.Ref) !types.Integer {
 | 
			
		||||
	const fail_message = "invalid type conversion: integer";
 | 
			
		||||
 | 
			
		||||
	switch (value) {
 | 
			
		||||
		.float => |float| {
 | 
			
		||||
			const int = @typeInfo(types.Integer).Int;
 | 
			
		||||
 | 
			
		||||
			if (coral.math.is_clamped(float, coral.math.min_int(int), coral.math.max_int(int))) {
 | 
			
		||||
				return @floatToInt(types.Integer, float);
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		.integer => |integer| return integer,
 | 
			
		||||
		else => {},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return self.fail(fail_message);
 | 
			
		||||
}
 | 
			
		||||
@ -1,56 +0,0 @@
 | 
			
		||||
const coral = @import("coral");
 | 
			
		||||
 | 
			
		||||
pub const CheckError = error {
 | 
			
		||||
	CheckFailed
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub const Float = f32;
 | 
			
		||||
 | 
			
		||||
pub const Integer = i32;
 | 
			
		||||
 | 
			
		||||
pub const Object = u32;
 | 
			
		||||
 | 
			
		||||
pub const Primitive = enum {
 | 
			
		||||
	nil,
 | 
			
		||||
	false,
 | 
			
		||||
	true,
 | 
			
		||||
	float,
 | 
			
		||||
	integer,
 | 
			
		||||
	object,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub const Ref = union (Primitive) {
 | 
			
		||||
	nil,
 | 
			
		||||
	false,
 | 
			
		||||
	true,
 | 
			
		||||
	float: Float,
 | 
			
		||||
	integer: Integer,
 | 
			
		||||
	object: Object,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub const ParseError = error {
 | 
			
		||||
	OutOfMemory,
 | 
			
		||||
	BadSyntax,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub const RuntimeError = CheckError || ParseError;
 | 
			
		||||
 | 
			
		||||
pub const Val = union (Primitive) {
 | 
			
		||||
	nil,
 | 
			
		||||
	false,
 | 
			
		||||
	true,
 | 
			
		||||
	float: Float,
 | 
			
		||||
	integer: Integer,
 | 
			
		||||
	object: Object,
 | 
			
		||||
 | 
			
		||||
	pub fn as_ref(self: *const Val) Ref {
 | 
			
		||||
		return switch (self.*) {
 | 
			
		||||
			.nil => .nil,
 | 
			
		||||
			.false => .false,
 | 
			
		||||
			.true => .true,
 | 
			
		||||
			.float => .{.float = self.float},
 | 
			
		||||
			.integer => .{.integer = self.integer},
 | 
			
		||||
			.object => .{.object = self.object},
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
@ -17,69 +17,99 @@ const AppManifest = struct {
 | 
			
		||||
	width: u16 = 640,
 | 
			
		||||
	height: u16 = 480,
 | 
			
		||||
 | 
			
		||||
	pub fn load_script(self: *AppManifest, env: *kym.Environment, fs: file.System, file_path: []const u8) !void {
 | 
			
		||||
		const manifest = try env.execute_file(fs, file.Path.from(&.{file_path}));
 | 
			
		||||
	pub fn load_script(self: *AppManifest, env: *kym.RuntimeEnv, fs: file.System, file_path: []const u8) !void {
 | 
			
		||||
		var manifest = try env.execute_file(heap.allocator, fs, file.Path.from(&.{file_path}));
 | 
			
		||||
 | 
			
		||||
		defer env.discard(manifest);
 | 
			
		||||
 | 
			
		||||
		const manifest_ref = manifest.as_ref();
 | 
			
		||||
		defer manifest.deinit();
 | 
			
		||||
 | 
			
		||||
		{
 | 
			
		||||
			const title = try env.get_field(manifest_ref, try env.intern("title"));
 | 
			
		||||
			var title = try env.get(manifest.value, try env.intern("title"));
 | 
			
		||||
 | 
			
		||||
			defer env.discard(title);
 | 
			
		||||
			defer title.deinit();
 | 
			
		||||
 | 
			
		||||
			const title_string = try env.string_cast(title.as_ref());
 | 
			
		||||
			const title_string = try env.string_cast(title.value);
 | 
			
		||||
 | 
			
		||||
			try env.check(title_string.len <= self.title.len, "`title` cannot exceed 255 bytes in length");
 | 
			
		||||
			coral.io.copy(&self.title, title_string);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const u16_int = @typeInfo(u16).Int;
 | 
			
		||||
		const u16_max = coral.math.max_int(@typeInfo(u16).Int);
 | 
			
		||||
 | 
			
		||||
		{
 | 
			
		||||
			const width = try env.get_field(manifest_ref, try env.intern("width"));
 | 
			
		||||
			const width = try env.get(manifest.value, try env.intern("width"));
 | 
			
		||||
 | 
			
		||||
			errdefer env.discard(width);
 | 
			
		||||
			errdefer width.deinit();
 | 
			
		||||
 | 
			
		||||
			self.width = try coral.math.checked_cast(u16_int, try env.to_integer(width.as_ref()));
 | 
			
		||||
			if (width.value.as_number()) |value| {
 | 
			
		||||
				if (value < u16_max) {
 | 
			
		||||
					self.width = @floatToInt(u16, value);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		{
 | 
			
		||||
			const height = try env.get_field(manifest_ref, try env.intern("height"));
 | 
			
		||||
			const height = try env.get(manifest.value, try env.intern("height"));
 | 
			
		||||
 | 
			
		||||
			errdefer env.discard(height);
 | 
			
		||||
			errdefer height.deinit();
 | 
			
		||||
 | 
			
		||||
			self.width = try coral.math.checked_cast(u16_int, try env.to_integer(height.as_ref()));
 | 
			
		||||
			if (height.value.as_number()) |value| {
 | 
			
		||||
				if (value < u16_max) {
 | 
			
		||||
					self.height = @floatToInt(u16, value);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
fn stack_as_log_writer(self: *coral.list.ByteStack) coral.io.Writer {
 | 
			
		||||
	return coral.io.Writer.bind(coral.list.ByteStack, self, struct {
 | 
			
		||||
		fn write(stack: *coral.list.ByteStack, bytes: []const coral.io.Byte) ?usize {
 | 
			
		||||
			var line_written = @as(usize, 0);
 | 
			
		||||
 | 
			
		||||
			for (bytes) |byte| {
 | 
			
		||||
				if (byte == '\n') {
 | 
			
		||||
					ext.SDL_LogError(ext.SDL_LOG_CATEGORY_APPLICATION, "%.*s", stack.values.len, stack.values.ptr);
 | 
			
		||||
					stack.clear();
 | 
			
		||||
 | 
			
		||||
					line_written = 0;
 | 
			
		||||
 | 
			
		||||
					continue;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				stack.push_one(byte) catch {
 | 
			
		||||
					coral.debug.assert(stack.drop(line_written));
 | 
			
		||||
 | 
			
		||||
					return null;
 | 
			
		||||
				};
 | 
			
		||||
 | 
			
		||||
				line_written += 1;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return bytes.len;
 | 
			
		||||
		}
 | 
			
		||||
	}.write);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn run_app(base_file_system: file.System) void {
 | 
			
		||||
	defer heap.trace_leaks();
 | 
			
		||||
 | 
			
		||||
	const Logger = struct {
 | 
			
		||||
		const Self = @This();
 | 
			
		||||
	var log_buffer = coral.list.ByteStack{.allocator = heap.allocator};
 | 
			
		||||
 | 
			
		||||
		fn log(_: *const Self, message: []const u8) void {
 | 
			
		||||
			ext.SDL_LogError(ext.SDL_LOG_CATEGORY_APPLICATION, "%.*s", message.len, message.ptr);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	defer log_buffer.deinit();
 | 
			
		||||
 | 
			
		||||
	var script_environment = kym.Environment.init(heap.allocator, .{
 | 
			
		||||
		.values_max = 512,
 | 
			
		||||
	var script_env = kym.RuntimeEnv.init(heap.allocator, stack_as_log_writer(&log_buffer), .{
 | 
			
		||||
		.stack_max = 512,
 | 
			
		||||
		.calls_max = 512,
 | 
			
		||||
		.reporter = kym.Environment.Reporter.bind(Logger, &.{}, Logger.log),
 | 
			
		||||
	}) catch {
 | 
			
		||||
		return ext.SDL_LogError(ext.SDL_LOG_CATEGORY_APPLICATION, "failed to initialize Kym vm\n");
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	defer script_environment.deinit();
 | 
			
		||||
	defer script_env.deinit();
 | 
			
		||||
 | 
			
		||||
	const app_file_name = "app.ona";
 | 
			
		||||
	var app_manifest = AppManifest{};
 | 
			
		||||
 | 
			
		||||
	app_manifest.load_script(&script_environment, base_file_system, app_file_name) catch {
 | 
			
		||||
	app_manifest.load_script(&script_env, base_file_system, app_file_name) catch {
 | 
			
		||||
		return ext.SDL_LogError(ext.SDL_LOG_CATEGORY_APPLICATION, "failed to load %s\n", app_file_name);
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user