Array and Table Literal Expressions for Kym #11
							
								
								
									
										102
									
								
								source/coral/arena.zig
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										102
									
								
								source/coral/arena.zig
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,102 @@ | |||||||
|  | const debug = @import("./debug.zig"); | ||||||
|  | 
 | ||||||
|  | const io = @import("./io.zig"); | ||||||
|  | 
 | ||||||
|  | const list = @import("./list.zig"); | ||||||
|  | 
 | ||||||
|  | const math = @import("./math.zig"); | ||||||
|  | 
 | ||||||
|  | pub const Stacking = struct { | ||||||
|  | 	base_allocator: io.Allocator, | ||||||
|  | 	min_page_size: usize, | ||||||
|  | 	allocations: list.Stack(usize) = .{}, | ||||||
|  | 	pages: list.Stack(Page) = .{}, | ||||||
|  | 
 | ||||||
|  | 	const Page = struct { | ||||||
|  | 		buffer: []u8, | ||||||
|  | 		used: usize, | ||||||
|  | 
 | ||||||
|  | 		const Self = @This(); | ||||||
|  | 
 | ||||||
|  | 		fn available(self: Self) usize { | ||||||
|  | 			return self.buffer.len - self.used; | ||||||
|  | 		} | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	pub fn allocate(self: *Stacking, allocation_size: usize) io.AllocationError![]u8 { | ||||||
|  | 		const alignment = @as(usize, 4); | ||||||
|  | 		const aligned_allocation_size = (allocation_size + alignment - 1) & ~(alignment - 1); | ||||||
|  | 
 | ||||||
|  | 		if (self.pages.values.len == 0) { | ||||||
|  | 			const page = try self.allocate_page(math.max(self.min_page_size, aligned_allocation_size)); | ||||||
|  | 
 | ||||||
|  | 			page.used = allocation_size; | ||||||
|  | 
 | ||||||
|  | 			return page.buffer[0 .. allocation_size]; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		var page = self.current_page() orelse unreachable; | ||||||
|  | 
 | ||||||
|  | 		if (page.available() <= aligned_allocation_size) { | ||||||
|  | 			page = try self.allocate_page(math.max(self.min_page_size, aligned_allocation_size)); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		debug.assert(page.available() >= allocation_size); | ||||||
|  | 
 | ||||||
|  | 		defer page.used += aligned_allocation_size; | ||||||
|  | 
 | ||||||
|  | 		return page.buffer[page.used .. (page.used + allocation_size)]; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	fn allocate_page(self: *Stacking, page_size: usize) io.AllocationError!*Page { | ||||||
|  | 		var buffer = try io.allocate_many(u8, page_size, self.base_allocator); | ||||||
|  | 
 | ||||||
|  | 		errdefer io.deallocate(self.base_allocator, buffer); | ||||||
|  | 
 | ||||||
|  | 		try self.pages.push_one(self.base_allocator, .{ | ||||||
|  | 			.buffer = buffer, | ||||||
|  | 			.used = 0, | ||||||
|  | 		}); | ||||||
|  | 
 | ||||||
|  | 		return (self.current_page() orelse unreachable); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	pub fn as_allocator(self: *Stacking) io.Allocator { | ||||||
|  | 		return io.Allocator.bind(Stacking, self, struct { | ||||||
|  | 			fn reallocate(stacking: *Stacking, options: io.AllocationOptions) ?[]u8 { | ||||||
|  | 				const allocation = options.allocation orelse { | ||||||
|  | 					return stacking.allocate(options.size) catch null; | ||||||
|  | 				}; | ||||||
|  | 
 | ||||||
|  | 				if (allocation.len == 0) { | ||||||
|  | 					return null; | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				const reallocation = stacking.allocate(allocation.len) catch { | ||||||
|  | 					return null; | ||||||
|  | 				}; | ||||||
|  | 
 | ||||||
|  | 				io.copy(reallocation, allocation); | ||||||
|  | 
 | ||||||
|  | 				return reallocation; | ||||||
|  | 			} | ||||||
|  | 		}.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; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return &self.pages.values[self.pages.values.len - 1]; | ||||||
|  | 	} | ||||||
|  | }; | ||||||
| @ -1,13 +1,13 @@ | |||||||
|  | /// | ||||||
|  | /// Arena-based memory allocation strategies. | ||||||
|  | /// | ||||||
|  | pub const arena = @import("./arena.zig"); | ||||||
|  | 
 | ||||||
| /// | /// | ||||||
| /// Debug build-only utilities and sanity-checkers. | /// Debug build-only utilities and sanity-checkers. | ||||||
| /// | /// | ||||||
| pub const debug = @import("./debug.zig"); | pub const debug = @import("./debug.zig"); | ||||||
| 
 | 
 | ||||||
| /// |  | ||||||
| /// Heap memory allocation strategies. |  | ||||||
| /// |  | ||||||
| pub const heap = @import("./heap.zig"); |  | ||||||
| 
 |  | ||||||
| /// | /// | ||||||
| /// Platform-agnostic data input and output operations. | /// Platform-agnostic data input and output operations. | ||||||
| /// | /// | ||||||
|  | |||||||
| @ -1,96 +0,0 @@ | |||||||
| const debug = @import("./debug.zig"); |  | ||||||
| 
 |  | ||||||
| const io = @import("./io.zig"); |  | ||||||
| 
 |  | ||||||
| const math = @import("./math.zig"); |  | ||||||
| 
 |  | ||||||
| const table = @import("./table.zig"); |  | ||||||
| 
 |  | ||||||
| pub const Bucketed = struct { |  | ||||||
| 	base_allocator: io.Allocator, |  | ||||||
| 	slab_table: SlabTable, |  | ||||||
| 
 |  | ||||||
| 	const Slab = struct { |  | ||||||
| 		count: usize = 0, |  | ||||||
| 		buffer: []u8 = &.{}, |  | ||||||
| 		erased: []usize = &.{}, |  | ||||||
| 
 |  | ||||||
| 		fn create(self: *Slab, allocator: io.Allocator) ?[]u8 { |  | ||||||
| 			if (self.count == self.erased.len) { |  | ||||||
| 				const buffer = io.allocate_many(allocator, u8, math.max(1, self.buffer.len * 2)) orelse return null; |  | ||||||
| 
 |  | ||||||
| 				errdefer io.deallocate(allocator, buffer); |  | ||||||
| 
 |  | ||||||
| 				const erased = io.allocate_many(allocator, usize, math.max(1, self.erased.len * 2)) orelse return null; |  | ||||||
| 
 |  | ||||||
| 				errdefer io.deallocate(allocator, erased); |  | ||||||
| 
 |  | ||||||
| 				self.buffer = buffer; |  | ||||||
| 				self.erased = erased; |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			return null; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		fn destroy(self: *Slab) void { |  | ||||||
| 			_ = self; |  | ||||||
| 		} |  | ||||||
| 	}; |  | ||||||
| 
 |  | ||||||
| 	const SlabTable = table.Hashed(table.unsigned_key(@bitSizeOf(usize)), *Slab); |  | ||||||
| 
 |  | ||||||
| 	fn acquire_slab(self: *Bucketed, slab_element_size: usize) ?*Slab { |  | ||||||
| 		if (slab_element_size == 0) return null; |  | ||||||
| 
 |  | ||||||
| 		return self.slab_table.lookup(slab_element_size) orelse create_slab: { |  | ||||||
| 			const allocated_slab = io.allocate_one(self.base_allocator, Slab); |  | ||||||
| 
 |  | ||||||
| 			errdefer io.deallocate(self.base_allocator, allocated_slab); |  | ||||||
| 
 |  | ||||||
| 			allocated_slab.* = .{.size = slab_element_size}; |  | ||||||
| 
 |  | ||||||
| 			debug.assert(self.size_buckets.insert(slab_element_size, allocated_slab) catch return null); |  | ||||||
| 
 |  | ||||||
| 			break: create_slab allocated_slab; |  | ||||||
| 		}; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	pub fn as_allocator(self: *Bucketed) io.Allocator { |  | ||||||
| 		return io.Allocator.bind(self, reallocate); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	pub fn deinit(self: *Bucketed) void { |  | ||||||
| 		var slab_iterator = SlabTable.Iterator{.table = self.slab_table}; |  | ||||||
| 
 |  | ||||||
| 		while (slab_iterator.next()) |slab| { |  | ||||||
| 			slab.free(self.base_allocator); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		self.size_buckets.free(self.base_allocator); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	pub fn init(base_allocator: io.Allocator) io.AllocationError!Bucketed { |  | ||||||
| 		return Bucketed{ |  | ||||||
| 			.base_allocator = base_allocator, |  | ||||||
| 			.size_buckets = &.{}, |  | ||||||
| 		}; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	pub fn owns(self: Bucketed, memory: []const u8) bool { |  | ||||||
| 		return io.overlaps(memory.ptr, (self.slab_table.lookup(memory.len) orelse return false).buffer); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	pub fn reallocate(self: *Bucketed, options: io.AllocationOptions) ?[]u8 { |  | ||||||
| 		const origin_slab = self.acquire_slab(options.size) orelse return null; |  | ||||||
| 		const existing_allocation = options.allocation orelse return origin_slab.create(self.base_allocator); |  | ||||||
| 
 |  | ||||||
| 		defer origin_slab.destroy(existing_allocation); |  | ||||||
| 
 |  | ||||||
| 		const target_slab = self.acquire_slab(existing_allocation.len) orelse return null; |  | ||||||
| 		const updated_allocation = target_slab.create(existing_allocation.len); |  | ||||||
| 
 |  | ||||||
| 		io.copy(updated_allocation, existing_allocation); |  | ||||||
| 
 |  | ||||||
| 		return updated_allocation; |  | ||||||
| 	} |  | ||||||
| }; |  | ||||||
| @ -11,31 +11,33 @@ pub const AllocationOptions = struct { | |||||||
| 	size: usize, | 	size: usize, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| pub const Allocator = Functor(?[]u8, AllocationOptions); | pub const Allocator = Generator(?[]u8, AllocationOptions); | ||||||
| 
 | 
 | ||||||
| /// | /// | ||||||
| /// Function pointer coupled with a state context for providing dynamic dispatch over a given `Input` and `Output`. | /// Function pointer coupled with an immutable state context for providing dynamic dispatch over a given `Input` and | ||||||
|  | /// `Output`. | ||||||
| /// | /// | ||||||
| pub fn Functor(comptime Output: type, comptime Input: type) type { | pub fn Functor(comptime Output: type, comptime Input: type) type { | ||||||
| 	return struct { | 	return struct { | ||||||
| 		context: *anyopaque, | 		context: *const anyopaque, | ||||||
| 		invoker: *const fn (capture: *anyopaque, input: Input) Output, | 		invoker: *const fn (capture: *const anyopaque, input: Input) Output, | ||||||
| 
 | 
 | ||||||
| 		const Self = @This(); | 		const Self = @This(); | ||||||
| 
 | 
 | ||||||
| 		pub fn bind(comptime State: type, state: *State, comptime invoker: fn (capture: *State, input: Input) Output) Self { | 		pub fn bind(comptime State: type, state: *const State, comptime invoker: fn (capture: *const State, input: Input) Output) Self { | ||||||
|  | 			const alignment = @alignOf(State); | ||||||
|  | 			const is_zero_aligned = alignment == 0; | ||||||
|  | 
 | ||||||
| 			return .{ | 			return .{ | ||||||
| 				.context = state, | 				.context = if (is_zero_aligned) state else @ptrCast(*const anyopaque, state), | ||||||
| 
 | 
 | ||||||
| 				.invoker = struct { | 				.invoker = struct { | ||||||
| 					fn invoke_opaque(context: *anyopaque, input: Input) Output { | 					fn invoke_opaque(context: *const anyopaque, input: Input) Output { | ||||||
| 						const state_alignment = @alignOf(State); | 						if (is_zero_aligned) { | ||||||
| 
 | 							return invoker(@ptrCast(*const State, context), input); | ||||||
| 						if (state_alignment == 0) { |  | ||||||
| 							return invoker(@ptrCast(*State, context), input); |  | ||||||
| 						} | 						} | ||||||
| 
 | 
 | ||||||
| 						return invoker(@ptrCast(*State, @alignCast(state_alignment, context)), input); | 						return invoker(@ptrCast(*const State, @alignCast(alignment, context)), input); | ||||||
| 					} | 					} | ||||||
| 				}.invoke_opaque, | 				}.invoke_opaque, | ||||||
| 			}; | 			}; | ||||||
| @ -47,7 +49,43 @@ pub fn Functor(comptime Output: type, comptime Input: type) type { | |||||||
| 	}; | 	}; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub const Reader = Functor(?usize, []u8); | /// | ||||||
|  | /// Function pointer coupled with a mutable state context for providing dynamic dispatch over a given `Input` and | ||||||
|  | /// `Output`. | ||||||
|  | /// | ||||||
|  | pub fn Generator(comptime Output: type, comptime Input: type) type { | ||||||
|  | 	return struct { | ||||||
|  | 		context: *anyopaque, | ||||||
|  | 		invoker: *const fn (capture: *anyopaque, input: Input) Output, | ||||||
|  | 
 | ||||||
|  | 		const Self = @This(); | ||||||
|  | 
 | ||||||
|  | 		pub fn bind(comptime State: type, state: *State, comptime invoker: fn (capture: *State, input: Input) Output) Self { | ||||||
|  | 			const alignment = @alignOf(State); | ||||||
|  | 			const is_zero_aligned = alignment == 0; | ||||||
|  | 
 | ||||||
|  | 			return .{ | ||||||
|  | 				.context = if (is_zero_aligned) state else @ptrCast(*anyopaque, state), | ||||||
|  | 
 | ||||||
|  | 				.invoker = struct { | ||||||
|  | 					fn invoke_opaque(context: *anyopaque, input: Input) Output { | ||||||
|  | 						if (is_zero_aligned) { | ||||||
|  | 							return invoker(@ptrCast(*State, context), input); | ||||||
|  | 						} | ||||||
|  | 
 | ||||||
|  | 						return invoker(@ptrCast(*State, @alignCast(alignment, context)), input); | ||||||
|  | 					} | ||||||
|  | 				}.invoke_opaque, | ||||||
|  | 			}; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		pub fn invoke(self: Self, input: Input) Output { | ||||||
|  | 			return self.invoker(self.context, input); | ||||||
|  | 		} | ||||||
|  | 	}; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub const Reader = Generator(?usize, []u8); | ||||||
| 
 | 
 | ||||||
| pub const StreamError = error { | pub const StreamError = error { | ||||||
| 	ReadFailure, | 	ReadFailure, | ||||||
| @ -66,7 +104,7 @@ pub const FixedBuffer = struct { | |||||||
| 	slice: []u8, | 	slice: []u8, | ||||||
| 
 | 
 | ||||||
| 	pub fn as_writer(self: *FixedBuffer) Writer { | 	pub fn as_writer(self: *FixedBuffer) Writer { | ||||||
| 		return Writer.bind(self, struct { | 		return Writer.bind(FixedBuffer, self, struct { | ||||||
| 			fn write(writable_memory: *FixedBuffer, data: []const u8) ?usize { | 			fn write(writable_memory: *FixedBuffer, data: []const u8) ?usize { | ||||||
| 				return writable_memory.write(data); | 				return writable_memory.write(data); | ||||||
| 			} | 			} | ||||||
| @ -104,7 +142,7 @@ pub const GrowingBuffer = struct { | |||||||
| 		bytes: []const u8, | 		bytes: []const u8, | ||||||
| 	}; | 	}; | ||||||
| 
 | 
 | ||||||
| 	const Appender = Functor(AllocationError!void, AppendOptions); | 	const Appender = Generator(AllocationError!void, AppendOptions); | ||||||
| 
 | 
 | ||||||
| 	pub fn as_writer(self: *GrowingBuffer) Writer { | 	pub fn as_writer(self: *GrowingBuffer) Writer { | ||||||
| 		return Writer.bind(GrowingBuffer, self, struct { | 		return Writer.bind(GrowingBuffer, self, struct { | ||||||
| @ -136,17 +174,13 @@ pub const GrowingBuffer = struct { | |||||||
| 	} | 	} | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| pub const Writer = Functor(?usize, []const u8); | pub const Writer = Generator(?usize, []const u8); | ||||||
| 
 | 
 | ||||||
| pub fn allocate_many(comptime Type: type, amount: usize, allocator: Allocator) AllocationError![]Type { | pub fn allocate_many(comptime Type: type, amount: usize, allocator: Allocator) AllocationError![]Type { | ||||||
| 	if (@sizeOf(Type) == 0) { | 	if (@sizeOf(Type) == 0) { | ||||||
| 		@compileError("Cannot allocate memory for 0-byte type " ++ @typeName(Type)); | 		@compileError("Cannot allocate memory for 0-byte type " ++ @typeName(Type)); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if (amount == 0) { |  | ||||||
| 		return &.{}; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return @ptrCast([*]Type, @alignCast(@alignOf(Type), allocator.invoke(.{.size = @sizeOf(Type) * amount}) orelse { | 	return @ptrCast([*]Type, @alignCast(@alignOf(Type), allocator.invoke(.{.size = @sizeOf(Type) * amount}) orelse { | ||||||
| 		return error.OutOfMemory; | 		return error.OutOfMemory; | ||||||
| 	}))[0 .. amount]; | 	}))[0 .. amount]; | ||||||
| @ -191,46 +225,32 @@ pub fn compare(this: []const u8, that: []const u8) isize { | |||||||
| 	return @intCast(isize, this.len) - @intCast(isize, that.len); | 	return @intCast(isize, this.len) - @intCast(isize, that.len); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub fn deallocate(allocator: Allocator, allocation: anytype) void { |  | ||||||
| 	const Element = @TypeOf(allocation); |  | ||||||
| 
 |  | ||||||
| 	switch (@typeInfo(Element).Pointer.size) { |  | ||||||
| 		.One => { |  | ||||||
| 			debug.assert(allocator.invoke(.{ |  | ||||||
| 				.allocation = @ptrCast([*]u8, allocation)[0 .. @sizeOf(Element)], |  | ||||||
| 				.size = 0 |  | ||||||
| 			}) == null); |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		.Slice => { |  | ||||||
| 			debug.assert(allocator.invoke(.{ |  | ||||||
| 				.allocation = @ptrCast([*]u8, allocation.ptr)[0 .. (@sizeOf(Element) * allocation.len)], |  | ||||||
| 				.size = 0 |  | ||||||
| 			}) == null); |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		.Many, .C => @compileError("length of allocation must be known to deallocate"), |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub fn bytes_to(comptime Type: type, source_bytes: []const u8) ?Type { |  | ||||||
| 	const type_size = @sizeOf(Type); |  | ||||||
| 
 |  | ||||||
| 	if (source_bytes.len != type_size) return null; |  | ||||||
| 
 |  | ||||||
| 	var target_bytes = @as([type_size]u8, undefined); |  | ||||||
| 
 |  | ||||||
| 	copy(&target_bytes, source_bytes); |  | ||||||
| 
 |  | ||||||
| 	return @bitCast(Type, target_bytes); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub fn copy(target: []u8, source: []const u8) void { | pub fn copy(target: []u8, source: []const u8) void { | ||||||
| 	var index: usize = 0; | 	var index: usize = 0; | ||||||
| 
 | 
 | ||||||
| 	while (index < source.len) : (index += 1) target[index] = source[index]; | 	while (index < source.len) : (index += 1) target[index] = source[index]; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | pub fn deallocate(allocator: Allocator, allocation: anytype) void { | ||||||
|  | 	const Allocation = @TypeOf(allocation); | ||||||
|  | 
 | ||||||
|  | 	switch (@typeInfo(Allocation)) { | ||||||
|  | 		.Pointer => |allocation_pointer| { | ||||||
|  | 			_ = allocator.invoke(.{ | ||||||
|  | 				.allocation = switch (allocation_pointer.size) { | ||||||
|  | 					.One => @ptrCast([*]u8, allocation)[0 .. @sizeOf(Allocation)], | ||||||
|  | 					.Slice => @ptrCast([*]u8, allocation.ptr)[0 .. (@sizeOf(Allocation) * allocation.len)], | ||||||
|  | 					.Many, .C => @compileError("length of allocation must be known to deallocate"), | ||||||
|  | 				}, | ||||||
|  | 
 | ||||||
|  | 				.size = 0, | ||||||
|  | 			}); | ||||||
|  | 		}, | ||||||
|  | 
 | ||||||
|  | 		else => @compileError("cannot deallocate " ++ allocation), | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
| pub fn ends_with(target: []const u8, match: []const u8) bool { | pub fn ends_with(target: []const u8, match: []const u8) bool { | ||||||
| 	if (target.len < match.len) return false; | 	if (target.len < match.len) return false; | ||||||
| 
 | 
 | ||||||
| @ -274,10 +294,6 @@ pub const null_writer = Writer.bind(&null_context, struct { | |||||||
| 	} | 	} | ||||||
| }.write); | }.write); | ||||||
| 
 | 
 | ||||||
| pub fn overlaps(pointer: [*]u8, memory_range: []u8) bool { |  | ||||||
| 	return (pointer >= memory_range.ptr) and (pointer < (memory_range.ptr + memory_range.len)); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub fn reallocate(allocator: Allocator, allocation: anytype, amount: usize) AllocationError![]@typeInfo(@TypeOf(allocation)).Pointer.child { | pub fn reallocate(allocator: Allocator, allocation: anytype, amount: usize) AllocationError![]@typeInfo(@TypeOf(allocation)).Pointer.child { | ||||||
| 	const pointer_info = @typeInfo(@TypeOf(allocation)).Pointer; | 	const pointer_info = @typeInfo(@TypeOf(allocation)).Pointer; | ||||||
| 	const Element = pointer_info.child; | 	const Element = pointer_info.child; | ||||||
| @ -319,6 +335,10 @@ pub fn swap(comptime Element: type, this: *Element, that: *Element) void { | |||||||
| 	that.* = temp; | 	that.* = temp; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | pub fn tag_of(comptime value: anytype) Tag(@TypeOf(value)) { | ||||||
|  | 	return @as(Tag(@TypeOf(value)), value); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| pub fn zero(target: []u8) void { | pub fn zero(target: []u8) void { | ||||||
| 	for (target) |*t| t.* = 0; | 	for (target) |*t| t.* = 0; | ||||||
| } | } | ||||||
|  | |||||||
| @ -9,8 +9,8 @@ const math = @import("./math.zig"); | |||||||
| /// | /// | ||||||
| pub fn Stack(comptime Value: type) type { | pub fn Stack(comptime Value: type) type { | ||||||
| 	return struct { | 	return struct { | ||||||
| 		capacity: usize, | 		capacity: usize = 0, | ||||||
| 		values: []Value, | 		values: []Value = &.{}, | ||||||
| 
 | 
 | ||||||
| 		/// | 		/// | ||||||
| 		/// Stack type. | 		/// Stack type. | ||||||
| @ -23,8 +23,8 @@ pub fn Stack(comptime Value: type) type { | |||||||
| 		/// The returned buffer may be used to write to the stack without needing to explicitly pass an allocator | 		/// The returned buffer may be used to write to the stack without needing to explicitly pass an allocator | ||||||
| 		/// context, as well decay further into a generic [io.Writer] type. | 		/// context, as well decay further into a generic [io.Writer] type. | ||||||
| 		/// | 		/// | ||||||
| 		/// *Note* `allocator` must reference the same allocation strategy as the one originally used to initialize | 		/// *Note* if `capacity` is a non-zero value, `allocator` must reference the same allocation strategy as the one | ||||||
| 		/// `self`. | 		/// originally used to allocate the current internal buffer. | ||||||
| 		/// | 		/// | ||||||
| 		pub fn as_buffer(self: *Self, allocator: io.Allocator) io.GrowingBuffer { | 		pub fn as_buffer(self: *Self, allocator: io.Allocator) io.GrowingBuffer { | ||||||
| 			return io.GrowingBuffer.bind(Self, allocator, self, push_all); | 			return io.GrowingBuffer.bind(Self, allocator, self, push_all); | ||||||
| @ -44,12 +44,17 @@ pub fn Stack(comptime Value: type) type { | |||||||
| 		/// | 		/// | ||||||
| 		/// To clear all items from the stack while preserving the current internal buffer, see [clear] instead. | 		/// To clear all items from the stack while preserving the current internal buffer, see [clear] instead. | ||||||
| 		/// | 		/// | ||||||
| 		/// *Note* `allocator` must reference the same allocation strategy as the one originally used to initialize | 		/// *Note* if the `capacity` field of `self` is a non-zero value, `allocator` must reference the same allocation | ||||||
| 		/// `self`. | 		/// 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, allocator: io.Allocator) void { | ||||||
|  | 			if (self.capacity == 0) { | ||||||
|  | 				return; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
| 			io.deallocate(allocator, self.values); | 			io.deallocate(allocator, self.values); | ||||||
| 
 | 
 | ||||||
|  | 			self.values = &.{}; | ||||||
| 			self.capacity = 0; | 			self.capacity = 0; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| @ -77,8 +82,8 @@ pub fn Stack(comptime Value: type) type { | |||||||
| 		/// Growing ahead of pushing operations is useful when the upper bound of pushes is well-understood, as it can | 		/// Growing ahead of pushing operations is useful when the upper bound of pushes is well-understood, as it can | ||||||
| 		/// reduce the number of allocations required per push. | 		/// reduce the number of allocations required per push. | ||||||
| 		/// | 		/// | ||||||
| 		/// *Note* `allocator` must reference the same allocation strategy as the one originally used to initialize | 		/// *Note* if the `capacity` field of `self` is a non-zero value, `allocator` must reference the same allocation | ||||||
| 		/// `self`. | 		/// 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, allocator: io.Allocator, growth_amount: usize) io.AllocationError!void { | ||||||
| 			const grown_capacity = self.capacity + growth_amount; | 			const grown_capacity = self.capacity + growth_amount; | ||||||
| @ -86,34 +91,18 @@ pub fn Stack(comptime Value: type) type { | |||||||
| 
 | 
 | ||||||
| 			errdefer io.deallocate(allocator, values); | 			errdefer io.deallocate(allocator, values); | ||||||
| 
 | 
 | ||||||
|  | 			if (self.capacity != 0) { | ||||||
| 				for (0 .. self.values.len) |index| { | 				for (0 .. self.values.len) |index| { | ||||||
| 					values[index] = self.values[index]; | 					values[index] = self.values[index]; | ||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
| 				io.deallocate(allocator, self.values); | 				io.deallocate(allocator, self.values); | ||||||
|  | 			} | ||||||
| 
 | 
 | ||||||
| 			self.values = values; | 			self.values = values; | ||||||
| 			self.capacity = grown_capacity; | 			self.capacity = grown_capacity; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		/// |  | ||||||
| 		/// Attempts to allocate and return an empty stack with an internal buffer of `initial_capacity` size using |  | ||||||
| 		/// `allocator` as the memory allocation strategy. |  | ||||||
| 		/// |  | ||||||
| 		/// The function returns [io.AllocationError] if `allocator` could not commit the memory required for an |  | ||||||
| 		/// internal buffer of `initial_capacity` size. |  | ||||||
| 		/// |  | ||||||
| 		pub fn init(allocator: io.Allocator, initial_capacity: usize) !Self { |  | ||||||
| 			const values = try io.allocate_many(Value, initial_capacity, allocator); |  | ||||||
| 
 |  | ||||||
| 			errdefer io.deallocate(values); |  | ||||||
| 
 |  | ||||||
| 			return Self{ |  | ||||||
| 				.capacity = initial_capacity, |  | ||||||
| 				.values = values[0 .. 0], |  | ||||||
| 			}; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		/// | 		/// | ||||||
| 		/// Attempts to remove the last element of `self` that was inserted, if one exists, returning it or `null` if | 		/// Attempts to remove the last element of `self` that was inserted, if one exists, returning it or `null` if | ||||||
| 		/// `self` is empty. | 		/// `self` is empty. | ||||||
| @ -137,14 +126,14 @@ pub fn Stack(comptime Value: type) type { | |||||||
| 		/// The function returns [io.AllocationError] if `allocator` could not commit the memory required to grow the | 		/// The function returns [io.AllocationError] if `allocator` could not commit the memory required to grow the | ||||||
| 		/// internal buffer of `self` when necessary. | 		/// internal buffer of `self` when necessary. | ||||||
| 		/// | 		/// | ||||||
| 		/// *Note* `allocator` must reference the same allocation strategy as the one originally used to initialize | 		/// *Note* if the `capacity` field of `self` is a non-zero value, `allocator` must reference the same allocation | ||||||
| 		/// `self`. | 		/// 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, allocator: io.Allocator, values: []const Value) io.AllocationError!void { | ||||||
| 			const new_length = self.values.len + values.len; | 			const new_length = self.values.len + values.len; | ||||||
| 
 | 
 | ||||||
| 			if (new_length >= self.capacity) { | 			if (new_length >= self.capacity) { | ||||||
| 				try self.grow(allocator, math.min(new_length, self.capacity)); | 				try self.grow(allocator, values.len + values.len); | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			const offset_index = self.values.len; | 			const offset_index = self.values.len; | ||||||
| @ -163,14 +152,14 @@ pub fn Stack(comptime Value: type) type { | |||||||
| 		/// The function returns [io.AllocationError] if `allocator` could not commit the memory required to grow the | 		/// The function returns [io.AllocationError] if `allocator` could not commit the memory required to grow the | ||||||
| 		/// internal buffer of `self` when necessary. | 		/// internal buffer of `self` when necessary. | ||||||
| 		/// | 		/// | ||||||
| 		/// *Note* `allocator` must reference the same allocation strategy as the one originally used to initialize | 		/// *Note* if the `capacity` field of `self` is a non-zero value, `allocator` must reference the same allocation | ||||||
| 		/// `self`. | 		/// 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, allocator: io.Allocator, value: Value, amount: usize) io.AllocationError!void { | ||||||
| 			const new_length = self.values.len + amount; | 			const new_length = self.values.len + amount; | ||||||
| 
 | 
 | ||||||
| 			if (new_length >= self.capacity) { | 			if (new_length >= self.capacity) { | ||||||
| 				try self.grow(allocator, math.max(usize, new_length, self.capacity)); | 				try self.grow(allocator, amount + amount); | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			const offset_index = self.values.len; | 			const offset_index = self.values.len; | ||||||
| @ -189,8 +178,8 @@ pub fn Stack(comptime Value: type) type { | |||||||
| 		/// The function returns [io.AllocationError] if `allocator` could not commit the memory required to grow the | 		/// The function returns [io.AllocationError] if `allocator` could not commit the memory required to grow the | ||||||
| 		/// internal buffer of `self` when necessary. | 		/// internal buffer of `self` when necessary. | ||||||
| 		/// | 		/// | ||||||
| 		/// *Note* `allocator` must reference the same allocation strategy as the one originally used to initialize | 		/// *Note* if the `capacity` field of `self` is a non-zero value, `allocator` must reference the same allocation | ||||||
| 		/// `self`. | 		/// 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, allocator: io.Allocator, value: Value) io.AllocationError!void { | ||||||
| 			if (self.values.len == self.capacity) { | 			if (self.values.len == self.capacity) { | ||||||
|  | |||||||
| @ -6,8 +6,8 @@ const io = @import("./io.zig"); | |||||||
| 
 | 
 | ||||||
| pub fn Map(comptime Index: type, comptime Element: type) type { | pub fn Map(comptime Index: type, comptime Element: type) type { | ||||||
| 	return struct { | 	return struct { | ||||||
| 		free_index: Index, | 		free_index: Index = 0, | ||||||
| 		entries: []Entry, | 		entries: []Entry = &.{}, | ||||||
| 
 | 
 | ||||||
| 		const Entry = union (enum) { | 		const Entry = union (enum) { | ||||||
| 			free_index: usize, | 			free_index: usize, | ||||||
| @ -28,17 +28,6 @@ pub fn Map(comptime Index: type, comptime Element: type) type { | |||||||
| 			io.deallocate(allocator, self.entries); | 			io.deallocate(allocator, self.entries); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		pub fn init(allocator: io.Allocator) io.AllocationError!Self { |  | ||||||
| 			const entries = try io.allocate_many(Entry, 4, allocator); |  | ||||||
| 
 |  | ||||||
| 			errdefer io.deallocate(allocator, entries); |  | ||||||
| 
 |  | ||||||
| 			return Self{ |  | ||||||
| 				.free_index = 0, |  | ||||||
| 				.entries = entries, |  | ||||||
| 			}; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		pub fn insert(self: *Self, allocator: io.Allocator, value: Element) io.AllocationError!Index { | 		pub fn insert(self: *Self, allocator: io.Allocator, value: Element) io.AllocationError!Index { | ||||||
| 			_ = self; | 			_ = self; | ||||||
| 			_ = allocator; | 			_ = allocator; | ||||||
|  | |||||||
| @ -22,8 +22,8 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime keyer: Keyer(Ke | |||||||
| 	const growth_factor = 0.6; | 	const growth_factor = 0.6; | ||||||
| 
 | 
 | ||||||
| 	return struct { | 	return struct { | ||||||
| 		count: usize, | 		count: usize = 0, | ||||||
| 		table: []?Entry, | 		table: []?Entry = &.{}, | ||||||
| 
 | 
 | ||||||
| 		/// | 		/// | ||||||
| 		/// Key-value pair bundling. | 		/// Key-value pair bundling. | ||||||
| @ -82,7 +82,7 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime keyer: Keyer(Ke | |||||||
| 		/// | 		/// | ||||||
| 		pub fn assign(self: *Self, allocator: io.Allocator, key: Key, value: Value) io.AllocationError!?Entry { | 		pub fn assign(self: *Self, allocator: io.Allocator, key: Key, value: Value) io.AllocationError!?Entry { | ||||||
| 			if (self.calculate_load_factor() >= load_max) { | 			if (self.calculate_load_factor() >= load_max) { | ||||||
| 				const growth_size = @intToFloat(f64, self.table.len) * growth_factor; | 				const growth_size = @intToFloat(f64, math.max(1, self.table.len)) * growth_factor; | ||||||
| 
 | 
 | ||||||
| 				if (growth_size > math.max_int(@typeInfo(usize).Int)) { | 				if (growth_size > math.max_int(@typeInfo(usize).Int)) { | ||||||
| 					return error.OutOfMemory; | 					return error.OutOfMemory; | ||||||
| @ -129,7 +129,7 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime keyer: Keyer(Ke | |||||||
| 		/// Returns the calculated load factor of `self` at the moment. | 		/// Returns the calculated load factor of `self` at the moment. | ||||||
| 		/// | 		/// | ||||||
| 		pub fn calculate_load_factor(self: Self) f32 { | 		pub fn calculate_load_factor(self: Self) f32 { | ||||||
| 			return @intToFloat(f32, self.count) / @intToFloat(f32, self.table.len); | 			return if (self.table.len == 0) 1 else @intToFloat(f32, self.count) / @intToFloat(f32, self.table.len); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		/// | 		/// | ||||||
| @ -160,28 +160,6 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime keyer: Keyer(Ke | |||||||
| 			self.count = 0; | 			self.count = 0; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		/// |  | ||||||
| 		/// Attempts to allocate and return an empty table with an implementation-defined initial capacity using |  | ||||||
| 		/// `allocator` as the memory allocation strategy. |  | ||||||
| 		/// |  | ||||||
| 		/// The function returns [AllocationError] instead if `allocator` cannot commit the memory required for the |  | ||||||
| 		/// table capcity size. |  | ||||||
| 		/// |  | ||||||
| 		pub fn init(allocator: io.Allocator) io.AllocationError!Self { |  | ||||||
| 			const table = try io.allocate_many(?Entry, 4, allocator); |  | ||||||
| 
 |  | ||||||
| 			errdefer io.deallocate(allocator, table); |  | ||||||
| 
 |  | ||||||
| 			for (table) |*entry| { |  | ||||||
| 				entry.* = null; |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			return Self{ |  | ||||||
| 				.table = table, |  | ||||||
| 				.count = 0, |  | ||||||
| 			}; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		/// | 		/// | ||||||
| 		/// Attempts to write the `key`-`value` pair into `self`, using `allocator` as the memory allocation strategy, | 		/// Attempts to write the `key`-`value` pair into `self`, using `allocator` as the memory allocation strategy, | ||||||
| 		/// if no value already exists with a matching `key`, returning `true` if it was inserted, otherwise `false`. | 		/// if no value already exists with a matching `key`, returning `true` if it was inserted, otherwise `false`. | ||||||
| @ -194,17 +172,16 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime keyer: Keyer(Ke | |||||||
| 		/// | 		/// | ||||||
| 		pub fn insert(self: *Self, allocator: io.Allocator, key: Key, value: Value) io.AllocationError!bool { | 		pub fn insert(self: *Self, allocator: io.Allocator, key: Key, value: Value) io.AllocationError!bool { | ||||||
| 			if (self.calculate_load_factor() >= load_max) { | 			if (self.calculate_load_factor() >= load_max) { | ||||||
| 				const growth_size = @intToFloat(f64, self.table.len) * growth_factor; | 				const growth_amount = @intToFloat(f64, self.table.len) * growth_factor; | ||||||
|  | 				const min_size = 1; | ||||||
| 
 | 
 | ||||||
| 				if (growth_size > math.max_int(@typeInfo(usize).Int)) { | 				try self.rehash(allocator, self.table.len + math.max(min_size, @floatToInt(usize, growth_amount))); | ||||||
| 					return error.OutOfMemory; |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				try self.rehash(allocator, @floatToInt(usize, growth_size)); |  | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			debug.assert(self.table.len > self.count); | 			debug.assert(self.table.len > self.count); | ||||||
| 
 | 
 | ||||||
|  | 			defer self.count += 1; | ||||||
|  | 
 | ||||||
| 			return (Entry{ | 			return (Entry{ | ||||||
| 				.key = key, | 				.key = key, | ||||||
| 				.value = value, | 				.value = value, | ||||||
| @ -222,8 +199,9 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime keyer: Keyer(Ke | |||||||
| 			{ | 			{ | ||||||
| 				const hash_max = math.min(math.max_int(hash_info), self.table.len); | 				const hash_max = math.min(math.max_int(hash_info), self.table.len); | ||||||
| 				var hashed_key = math.wrap(keyer.hasher(key), math.min_int(hash_info), hash_max); | 				var hashed_key = math.wrap(keyer.hasher(key), math.min_int(hash_info), hash_max); | ||||||
|  | 				var iterations = @as(usize, 0); | ||||||
| 
 | 
 | ||||||
| 				while (true) { | 				while (iterations < self.count) : (iterations += 1) { | ||||||
| 					const entry = &(self.table[hashed_key] orelse return null); | 					const entry = &(self.table[hashed_key] orelse return null); | ||||||
| 
 | 
 | ||||||
| 					if (keyer.comparer(entry.key, key) == 0) { | 					if (keyer.comparer(entry.key, key) == 0) { | ||||||
| @ -260,6 +238,8 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime keyer: Keyer(Ke | |||||||
| 				entry.* = null; | 				entry.* = null; | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
|  | 			if (old_table.len != 0) | ||||||
|  | 			{ | ||||||
| 				for (old_table) |maybe_entry| { | 				for (old_table) |maybe_entry| { | ||||||
| 					if (maybe_entry) |entry| { | 					if (maybe_entry) |entry| { | ||||||
| 						debug.assert(entry.write_into(self.table)); | 						debug.assert(entry.write_into(self.table)); | ||||||
| @ -268,6 +248,7 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime keyer: Keyer(Ke | |||||||
| 
 | 
 | ||||||
| 				io.deallocate(allocator, old_table); | 				io.deallocate(allocator, old_table); | ||||||
| 			} | 			} | ||||||
|  | 		} | ||||||
| 	}; | 	}; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,19 +1,26 @@ | |||||||
| const std = @import("std"); | const debug = @import("./debug.zig"); | ||||||
| 
 | 
 | ||||||
| const io = @import("./io.zig"); | const io = @import("./io.zig"); | ||||||
| 
 | 
 | ||||||
| const math = @import("./math.zig"); | const math = @import("./math.zig"); | ||||||
| 
 | 
 | ||||||
| /// | const std = @import("std"); | ||||||
| /// Errors that may occur during utf8-encoded int parsing. |  | ||||||
| /// |  | ||||||
| pub const IntParseError = math.CheckedArithmeticError || ParseError; |  | ||||||
| 
 | 
 | ||||||
| /// | /// | ||||||
| /// Optional rules for int parsing logic to consider during parsing. |  | ||||||
| /// | /// | ||||||
| pub const IntParseOptions = struct { | /// | ||||||
|  | pub const DecimalFormat = struct { | ||||||
| 	delimiter: []const u8 = "", | 	delimiter: []const u8 = "", | ||||||
|  | 	positive_prefix: enum {none, plus, space} = .none, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /// | ||||||
|  | /// | ||||||
|  | /// | ||||||
|  | pub const HexadecimalFormat = struct { | ||||||
|  | 	delimiter: []const u8 = "", | ||||||
|  | 	positive_prefix: enum {none, plus, space} = .none, | ||||||
|  | 	casing: enum {lower, upper} = .lower, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| /// | /// | ||||||
| @ -32,36 +39,84 @@ pub const PrintError = error { | |||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| /// | /// | ||||||
| /// Attempts to parse a float value of type described by `float` from `utf8`. |  | ||||||
| /// | /// | ||||||
| /// The function returns a [ParseError] if `utf8` does not conform to the syntax of a float. |  | ||||||
| /// | /// | ||||||
| pub fn parse_float(comptime float: std.builtin.Type.Float, utf8: []const u8) ParseError!math.Float(float) { | pub fn parse_decimal(comptime Decimal: type, utf8: []const u8, format: DecimalFormat) !Decimal { | ||||||
|  | 	if (utf8.len == 0) { | ||||||
|  | 		return error.BadSyntax; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	switch (@typeInfo(Decimal)) { | ||||||
|  | 		.Int => |int| { | ||||||
|  | 			var has_sign = switch (utf8[0]) { | ||||||
|  | 				'-', '+', ' ' => true, | ||||||
|  | 				else => false, | ||||||
|  | 			}; | ||||||
|  | 
 | ||||||
|  | 			var result = @as(Decimal, 0); | ||||||
|  | 
 | ||||||
|  | 			for (@boolToInt(has_sign) .. utf8.len) |index| { | ||||||
|  | 				const radix = 10; | ||||||
|  | 				const code = utf8[index]; | ||||||
|  | 
 | ||||||
|  | 				switch (code) { | ||||||
|  | 					'0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => { | ||||||
|  | 						result = try math.checked_add( | ||||||
|  | 							try math.checked_mul(result, radix), | ||||||
|  | 							try math.checked_sub(code, '0')); | ||||||
|  | 					}, | ||||||
|  | 
 | ||||||
|  | 					else => { | ||||||
|  | 						if (format.delimiter.len == 0 or !io.equals(format.delimiter, utf8[index ..])) { | ||||||
|  | 							return error.BadSyntax; | ||||||
|  | 						} | ||||||
|  | 					}, | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			switch (int.signedness) { | ||||||
|  | 				.signed => { | ||||||
|  | 					return result * @as(Decimal, if (has_sign and utf8[0] == '-') -1 else 1); | ||||||
|  | 				}, | ||||||
|  | 
 | ||||||
|  | 				.unsigned => { | ||||||
|  | 					if (has_sign and utf8[0] == '-') { | ||||||
|  | 						return error.OutOfMemory; | ||||||
|  | 					} | ||||||
|  | 
 | ||||||
|  | 					return result; | ||||||
|  | 				}, | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
|  | 
 | ||||||
|  | 		.Float => { | ||||||
| 			// "" | 			// "" | ||||||
| 			if (utf8.len == 0) { | 			if (utf8.len == 0) { | ||||||
| 				return error.BadSyntax; | 				return error.BadSyntax; | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 	const is_negative = utf8[0] == '-'; | 			var has_sign = switch (utf8[0]) { | ||||||
|  | 				'-', '+', ' ' => true, | ||||||
|  | 				else => false, | ||||||
|  | 			}; | ||||||
| 
 | 
 | ||||||
| 			// "-" | 			// "-" | ||||||
| 	if (is_negative and (utf8.len == 1)) { | 			if (has_sign and utf8.len == 1) { | ||||||
| 				return error.BadSyntax; | 				return error.BadSyntax; | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 	const negative_offset = @boolToInt(is_negative); | 			const sign_offset = @boolToInt(has_sign); | ||||||
| 	var has_decimal = utf8[negative_offset] == '.'; | 			var has_decimal = utf8[sign_offset] == '.'; | ||||||
| 
 | 
 | ||||||
| 			// "-." | 			// "-." | ||||||
| 			if (has_decimal and (utf8.len == 2)) { | 			if (has_decimal and (utf8.len == 2)) { | ||||||
| 				return error.BadSyntax; | 				return error.BadSyntax; | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 	const Float = math.Float(float); | 			var result = @as(Decimal, 0); | ||||||
| 	var result: Float = 0; | 			var factor = @as(Decimal, if (has_sign and utf8[0] == '-') -1 else 1); | ||||||
| 	var factor: Float = 1; |  | ||||||
| 
 | 
 | ||||||
| 	for (utf8[0 .. negative_offset + @boolToInt(has_decimal)]) |code| switch (code) { | 			for (utf8[0 .. (sign_offset + @boolToInt(has_decimal))]) |code| switch (code) { | ||||||
| 				'.' => { | 				'.' => { | ||||||
| 					if (has_decimal) return error.BadSyntax; | 					if (has_decimal) return error.BadSyntax; | ||||||
| 
 | 
 | ||||||
| @ -71,68 +126,17 @@ pub fn parse_float(comptime float: std.builtin.Type.Float, utf8: []const u8) Par | |||||||
| 				'0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => { | 				'0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => { | ||||||
| 					if (has_decimal) factor /= 10.0; | 					if (has_decimal) factor /= 10.0; | ||||||
| 
 | 
 | ||||||
| 			result = ((result * 10.0) + @intToFloat(Float, code - '0')); | 					result = ((result * 10.0) + @intToFloat(Decimal, code - '0')); | ||||||
| 				}, | 				}, | ||||||
| 
 | 
 | ||||||
| 				else => return error.BadSyntax, | 				else => return error.BadSyntax, | ||||||
| 			}; | 			}; | ||||||
| 
 | 
 | ||||||
| 			return result * factor; | 			return result * factor; | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /// |  | ||||||
| /// Attempts to parse an int value of type described by `int` from `utf8`, with `options` as additional rules for the |  | ||||||
| /// parsing logic to consider. |  | ||||||
| /// |  | ||||||
| /// The function returns a [IntParseError] if `utf8` does not conform to the syntax of a float, does not match the rules |  | ||||||
| /// specified in `option`, or exceeds the maximum size of the int described by `int`. |  | ||||||
| /// |  | ||||||
| pub fn parse_int( |  | ||||||
| 	comptime int: std.builtin.Type.Int, |  | ||||||
| 	utf8: []const u8, |  | ||||||
| 	options: IntParseOptions) IntParseError!math.Int(int) { |  | ||||||
| 
 |  | ||||||
| 	if (utf8.len == 0) { |  | ||||||
| 		return error.BadSyntax; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	{ |  | ||||||
| 		const is_negative = utf8[0] == '-'; |  | ||||||
| 
 |  | ||||||
| 		switch (int.signedness) { |  | ||||||
| 			.signed => { |  | ||||||
| 				if (is_negative and utf8.len == 1) { |  | ||||||
| 					return error.BadSyntax; |  | ||||||
| 				} |  | ||||||
| 		}, | 		}, | ||||||
| 
 | 
 | ||||||
| 			.unsigned => { | 		else => @compileError("`" ++ @typeName(Decimal) ++ "` cannot be formatted as a decimal string"), | ||||||
| 				if (is_negative) { |  | ||||||
| 					return error.BadSyntax; |  | ||||||
| 	} | 	} | ||||||
| 			}, |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	var result = @as(math.Int(int), 0); |  | ||||||
| 
 |  | ||||||
| 	for (0 .. utf8.len) |index| { |  | ||||||
| 		const code = utf8[index]; |  | ||||||
| 
 |  | ||||||
| 		switch (code) { |  | ||||||
| 			'0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => { |  | ||||||
| 				result = try math.checked_add(try math.checked_mul(result, 10), try math.checked_sub(code, '0')); |  | ||||||
| 			}, |  | ||||||
| 
 |  | ||||||
| 			else => { |  | ||||||
| 				if (options.delimiter.len == 0 or !io.equals(options.delimiter, utf8[index ..])) { |  | ||||||
| 					return error.BadSyntax; |  | ||||||
| 				} |  | ||||||
| 			}, |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return result; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// | /// | ||||||
| @ -147,40 +151,148 @@ pub fn print(writer: io.Writer, utf8: []const u8) PrintError!void { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// | /// | ||||||
| /// Attempts to print the int `value` described by `int` to `writer`. |  | ||||||
| /// | /// | ||||||
| /// The function returns [PrintError] if the write failed to complete partially or entirely. |  | ||||||
| /// | /// | ||||||
| pub fn print_int(comptime int: std.builtin.Type.Int, writer: io.Writer, value: math.Int(int)) PrintError!void { | pub fn print_formatted(writer: io.Writer, comptime format: []const u8, arguments: anytype) PrintError!void { | ||||||
|  | 	switch (@typeInfo(@TypeOf(arguments))) { | ||||||
|  | 		.Struct => |arguments_struct| { | ||||||
|  | 			comptime var arg_index = 0; | ||||||
|  | 			comptime var head = 0; | ||||||
|  | 			comptime var tail = 0; | ||||||
|  | 
 | ||||||
|  | 			inline while (tail < format.len) : (tail += 1) { | ||||||
|  | 				if (format[tail] == '{') { | ||||||
|  | 					if (tail > format.len) { | ||||||
|  | 						@compileError("expected an idenifier after opening `{`"); | ||||||
|  | 					} | ||||||
|  | 
 | ||||||
|  | 					tail += 1; | ||||||
|  | 
 | ||||||
|  | 					switch (format[tail]) { | ||||||
|  | 						'{' => { | ||||||
|  | 							try print(writer, format[head .. (tail - 1)]); | ||||||
|  | 
 | ||||||
|  | 							tail += 1; | ||||||
|  | 							head = tail; | ||||||
|  | 						}, | ||||||
|  | 
 | ||||||
|  | 						'}' => { | ||||||
|  | 							if (!arguments_struct.is_tuple) { | ||||||
|  | 								@compileError("all format specifiers must be named when using a named struct"); | ||||||
|  | 							} | ||||||
|  | 
 | ||||||
|  | 							try print(writer, arguments[arg_index]); | ||||||
|  | 
 | ||||||
|  | 							arg_index += 1; | ||||||
|  | 							tail += 1; | ||||||
|  | 							head = tail; | ||||||
|  | 						}, | ||||||
|  | 
 | ||||||
|  | 						else => { | ||||||
|  | 							if (arguments_struct.is_tuple) { | ||||||
|  | 								@compileError("format specifiers cannot be named when using a tuple struct"); | ||||||
|  | 							} | ||||||
|  | 
 | ||||||
|  | 							try print(writer, format[head .. (tail - 1)]); | ||||||
|  | 
 | ||||||
|  | 							head = tail; | ||||||
|  | 							tail += 1; | ||||||
|  | 
 | ||||||
|  | 							if (tail >= format.len) { | ||||||
|  | 								@compileError("expected closing `}` or another `{` after opening `{`"); | ||||||
|  | 							} | ||||||
|  | 
 | ||||||
|  | 							debug.assert(tail < format.len); | ||||||
|  | 
 | ||||||
|  | 							inline while (format[tail] != '}') { | ||||||
|  | 								tail += 1; | ||||||
|  | 
 | ||||||
|  | 								debug.assert(tail < format.len); | ||||||
|  | 							} | ||||||
|  | 
 | ||||||
|  | 							try print_value(writer, @field(arguments, format[head .. tail])); | ||||||
|  | 
 | ||||||
|  | 							tail += 1; | ||||||
|  | 							head = tail; | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
|  | 
 | ||||||
|  | 		else => @compileError("`arguments` must be a struct type"), | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// | ||||||
|  | /// | ||||||
|  | /// | ||||||
|  | pub fn print_decimal(writer: io.Writer, value: anytype, format: DecimalFormat) PrintError!void { | ||||||
| 	if (value == 0) { | 	if (value == 0) { | ||||||
| 		return try print(writer, "0"); | 		return print(writer, switch (format.positive_prefix) { | ||||||
|  | 			.none => "0", | ||||||
|  | 			.plus => "+0", | ||||||
|  | 			.space => " 0", | ||||||
|  | 		}); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// TODO: Don't make this buffer arbitrarily size cause big int types WILL overflow. | 	switch (@typeInfo(@TypeOf(value))) { | ||||||
| 	var buffer = [_]u8{0} ** 40; | 		.Int => |int| { | ||||||
| 	var buffer_count: usize = 0; |  | ||||||
| 	var split_value = value; |  | ||||||
| 
 |  | ||||||
| 	if ((int.signedness == .unsigned) and (value < 0)) { |  | ||||||
| 		buffer[0] = '-'; |  | ||||||
| 		buffer_count += 1; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	while (split_value != 0) : (buffer_count += 1) { |  | ||||||
| 			const radix = 10; | 			const radix = 10; | ||||||
| 
 | 			var buffer = [_]u8{0} ** (1 + math.max(int.bits, 1)); | ||||||
| 		buffer[buffer_count] = @intCast(u8, (split_value % radix) + '0'); | 			var buffer_start = buffer.len - 1; | ||||||
| 		split_value = (split_value / radix); |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 			{ | 			{ | ||||||
| 		const half_buffer_count = buffer_count / 2; | 				var decomposable_value = value; | ||||||
| 		var index: usize = 0; |  | ||||||
| 
 | 
 | ||||||
| 		while (index < half_buffer_count) : (index += 1) { | 				while (decomposable_value != 0) : (buffer_start -= 1) { | ||||||
| 			io.swap(u8, &buffer[index], &buffer[buffer_count - index - 1]); | 					buffer[buffer_start] = @intCast(u8, (decomposable_value % radix) + '0'); | ||||||
|  | 					decomposable_value = (decomposable_value / radix); | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 	try print(writer, buffer[0 .. buffer_count]); | 			if (int.signedness == .unsigned and value < 0) { | ||||||
|  | 				buffer[buffer_start] = '-'; | ||||||
|  | 			} else { | ||||||
|  | 				switch (format.positive_prefix) { | ||||||
|  | 					.none => buffer_start += 1, | ||||||
|  | 					.plus => buffer[buffer_start] = '+', | ||||||
|  | 					.space => buffer[buffer_start] = ' ', | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			try print(writer, buffer[buffer_start ..]); | ||||||
|  | 		}, | ||||||
|  | 
 | ||||||
|  | 		else => @compileError("`arguments` must be a struct type"), | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub fn print_hexadecimal(writer: io.Writer, value: anytype, format: HexadecimalFormat) PrintError!void { | ||||||
|  | 	// TODO: Implement. | ||||||
|  | 	_ = writer; | ||||||
|  | 	_ = value; | ||||||
|  | 	_ = format; | ||||||
|  | 
 | ||||||
|  | 	unreachable; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | noinline fn print_value(writer: io.Writer, value: anytype) PrintError!void { | ||||||
|  | 	const Value = @TypeOf(value); | ||||||
|  | 
 | ||||||
|  | 	return switch (@typeInfo(Value)) { | ||||||
|  | 		.Int => print_decimal(writer, value, .{}), | ||||||
|  | 		.Float => print_decimal(writer, value, .{}), | ||||||
|  | 
 | ||||||
|  | 		.Pointer => |pointer| switch (pointer.size) { | ||||||
|  | 			.One, .Many, .C => print_hexadecimal(writer, @ptrToInt(value), .{}), | ||||||
|  | 			.Slice => if (pointer.child == u8) print(writer, value) else @compileError(unformattableMessage(Value)), | ||||||
|  | 		}, | ||||||
|  | 
 | ||||||
|  | 		else => @compileError(unformattableMessage(Value)), | ||||||
|  | 	}; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fn unformattableMessage(comptime Value: type) []const u8 { | ||||||
|  | 	return "`" ++ @typeName(Value) ++ "` are not formattable"; | ||||||
| } | } | ||||||
|  | |||||||
| @ -7,17 +7,25 @@ const Context = struct { | |||||||
| 
 | 
 | ||||||
| 	const Self = @This(); | 	const Self = @This(); | ||||||
| 
 | 
 | ||||||
|  | 	const empty_allocation = [0]u8{}; | ||||||
|  | 
 | ||||||
| 	fn reallocate(self: *Self, options: coral.io.AllocationOptions) ?[]u8 { | 	fn reallocate(self: *Self, options: coral.io.AllocationOptions) ?[]u8 { | ||||||
| 		if (options.size == 0) { | 		if (options.size == 0) { | ||||||
| 			if (options.allocation) |allocation| { | 			if (options.allocation) |allocation| { | ||||||
|  | 				if (allocation.ptr != &empty_allocation) { | ||||||
| 					ext.SDL_free(allocation.ptr); | 					ext.SDL_free(allocation.ptr); | ||||||
| 
 |  | ||||||
| 				self.live_allocations -= 1; |  | ||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
|  | 				self.live_allocations -= 1; | ||||||
|  | 
 | ||||||
| 				return null; | 				return null; | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
|  | 			self.live_allocations += 1; | ||||||
|  | 
 | ||||||
|  | 			return &empty_allocation; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
| 		if (options.allocation) |allocation| { | 		if (options.allocation) |allocation| { | ||||||
| 			if (ext.SDL_realloc(allocation.ptr, options.size)) |reallocation| { | 			if (ext.SDL_realloc(allocation.ptr, options.size)) |reallocation| { | ||||||
| 				self.live_allocations += 1; | 				self.live_allocations += 1; | ||||||
|  | |||||||
							
								
								
									
										369
									
								
								source/ona/kym/Ast.zig
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										369
									
								
								source/ona/kym/Ast.zig
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,369 @@ | |||||||
|  | const coral = @import("coral"); | ||||||
|  | 
 | ||||||
|  | const tokens = @import("./tokens.zig"); | ||||||
|  | 
 | ||||||
|  | const types = @import("./types.zig"); | ||||||
|  | 
 | ||||||
|  | allocator: coral.io.Allocator, | ||||||
|  | arena: coral.arena.Stacking, | ||||||
|  | statements: StatementList, | ||||||
|  | error_message: []const u8, | ||||||
|  | 
 | ||||||
|  | pub const BinaryOperator = enum { | ||||||
|  | 	addition, | ||||||
|  | 	subtraction, | ||||||
|  | 	multiplication, | ||||||
|  | 	divsion, | ||||||
|  | 	equals_comparison, | ||||||
|  | 	greater_than_comparison, | ||||||
|  | 	greater_equals_comparison, | ||||||
|  | 	less_than_comparison, | ||||||
|  | 	less_equals_comparison, | ||||||
|  | 
 | ||||||
|  | 	fn token(self: BinaryOperator) tokens.Token { | ||||||
|  | 		return switch (self) { | ||||||
|  | 			.addition => .symbol_plus, | ||||||
|  | 			.subtraction => .symbol_minus, | ||||||
|  | 			.multiplication => .symbol_asterisk, | ||||||
|  | 			.divsion => .symbol_forward_slash, | ||||||
|  | 			.equals_comparison => .symbol_double_equals, | ||||||
|  | 			.greater_than_comparison => .symbol_greater_than, | ||||||
|  | 			.greater_equals_comparison => .symbol_greater_equals, | ||||||
|  | 			.less_than_comparison => .symbol_less_than, | ||||||
|  | 			.less_equals_comparison => .symbol_less_equals, | ||||||
|  | 		}; | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | pub const Expression = union (enum) { | ||||||
|  | 	nil_literal, | ||||||
|  | 	true_literal, | ||||||
|  | 	false_literal, | ||||||
|  | 	integer_literal: types.Integer, | ||||||
|  | 	float_literal: types.Float, | ||||||
|  | 	string_literal: []const u8, | ||||||
|  | 	array_literal: coral.list.Stack(Expression), | ||||||
|  | 
 | ||||||
|  | 	table_literal: coral.list.Stack(struct { | ||||||
|  | 		identifier: []const u8, | ||||||
|  | 		expression: Expression, | ||||||
|  | 	}), | ||||||
|  | 
 | ||||||
|  | 	grouped_expression: *Expression, | ||||||
|  | 
 | ||||||
|  | 	binary_operation: struct { | ||||||
|  | 		operator: BinaryOperator, | ||||||
|  | 		lhs_expression: *Expression, | ||||||
|  | 		rhs_expression: *Expression, | ||||||
|  | 	}, | ||||||
|  | 
 | ||||||
|  | 	unary_operation: struct { | ||||||
|  | 		operator: UnaryOperator, | ||||||
|  | 		expression: *Expression, | ||||||
|  | 	}, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const ExpressionParser = fn (self: *Self, tokenizer: *tokens.Tokenizer) types.ParseError!Expression; | ||||||
|  | 
 | ||||||
|  | const Self = @This(); | ||||||
|  | 
 | ||||||
|  | pub const Statement = union (enum) { | ||||||
|  | 	return_expression: Expression, | ||||||
|  | 	return_nothing, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const StatementList = coral.list.Stack(Statement); | ||||||
|  | 
 | ||||||
|  | const UnaryOperator = enum { | ||||||
|  | 	boolean_negation, | ||||||
|  | 	numeric_negation, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | 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 { | ||||||
|  | 			var expression = try parse_next(self, tokenizer); | ||||||
|  | 
 | ||||||
|  | 			{ | ||||||
|  | 				const allocator = self.arena.as_allocator(); | ||||||
|  | 
 | ||||||
|  | 				inline for (operators) |operator| { | ||||||
|  | 					const token = comptime operator.token(); | ||||||
|  | 
 | ||||||
|  | 					if (tokenizer.current_token == coral.io.tag_of(token)) { | ||||||
|  | 						try self.check_syntax( | ||||||
|  | 							tokenizer.step(.{.include_newlines = true}), | ||||||
|  | 							"expected right-hand side of expression after `" ++ comptime token.text() ++ "`"); | ||||||
|  | 
 | ||||||
|  | 						expression = .{ | ||||||
|  | 							.binary_operation = .{ | ||||||
|  | 								.operator = operator, | ||||||
|  | 								.lhs_expression = try coral.io.allocate_one(allocator, expression), | ||||||
|  | 								.rhs_expression = try coral.io.allocate_one(allocator, try parse_next(self, tokenizer)), | ||||||
|  | 							}, | ||||||
|  | 						}; | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			return expression; | ||||||
|  | 		} | ||||||
|  | 	}.parse; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fn check_syntax(self: *Self, condition: bool, error_message: []const u8) types.ParseError!void { | ||||||
|  | 	if (condition) { | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return self.fail_syntax(error_message); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub fn deinit(self: *Self) void { | ||||||
|  | 	self.arena.clear_allocations(); | ||||||
|  | 	self.statements.deinit(self.allocator); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fn fail_syntax(self: *Self, error_message: []const u8) types.ParseError { | ||||||
|  | 	self.error_message = error_message; | ||||||
|  | 
 | ||||||
|  | 	return error.BadSyntax; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub fn init(allocator: coral.io.Allocator) coral.io.AllocationError!Self { | ||||||
|  | 	return Self{ | ||||||
|  | 		.arena = .{ | ||||||
|  | 			.base_allocator = allocator, | ||||||
|  | 			.min_page_size = 4096, | ||||||
|  | 		}, | ||||||
|  | 
 | ||||||
|  | 		.allocator = allocator, | ||||||
|  | 		.statements = .{}, | ||||||
|  | 		.error_message = "", | ||||||
|  | 	}; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub fn list_statements(self: Self) []const Statement { | ||||||
|  | 	return self.statements.values; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub fn parse(self: *Self, tokenizer: *tokens.Tokenizer) types.ParseError!void { | ||||||
|  | 	self.reset(); | ||||||
|  | 
 | ||||||
|  | 	errdefer self.reset(); | ||||||
|  | 
 | ||||||
|  | 	var has_not_returned_yet = true; | ||||||
|  | 
 | ||||||
|  | 	while (tokenizer.step(.{.include_newlines = false})) { | ||||||
|  | 		switch (tokenizer.current_token) { | ||||||
|  | 			.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: { | ||||||
|  | 					if (tokenizer.step(.{.include_newlines = true})) { | ||||||
|  | 						if (tokenizer.current_token != .newline) { | ||||||
|  | 							break: get_statement .{.return_expression = try self.parse_expression(tokenizer)}; | ||||||
|  | 						} | ||||||
|  | 
 | ||||||
|  | 						if (tokenizer.step(.{.include_newlines = true})) { | ||||||
|  | 							try self.check_syntax( | ||||||
|  | 								tokenizer.current_token == .newline, | ||||||
|  | 								"expected end of declaration after return expression"); | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 
 | ||||||
|  | 					break: get_statement .return_nothing; | ||||||
|  | 				}); | ||||||
|  | 
 | ||||||
|  | 				has_not_returned_yet = false; | ||||||
|  | 			}, | ||||||
|  | 
 | ||||||
|  | 			else => return self.fail_syntax("invalid statement"), | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const parse_comparison = binary_operation_parser(parse_term, &.{ | ||||||
|  | 	.greater_than_comparison, | ||||||
|  | 	.greater_equals_comparison, | ||||||
|  | 	.less_than_comparison, | ||||||
|  | 	.less_equals_comparison | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | const parse_equality = binary_operation_parser(parse_comparison, &.{ | ||||||
|  | 	.equals_comparison, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | const parse_expression = binary_operation_parser(parse_equality, &.{ | ||||||
|  | 	.addition, | ||||||
|  | 	.subtraction, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) types.ParseError!Expression { | ||||||
|  | 	switch (tokenizer.current_token) { | ||||||
|  | 		.symbol_paren_left => { | ||||||
|  | 			try self.check_syntax(tokenizer.step(.{.include_newlines = false}), "expected an expression after `(`"); | ||||||
|  | 
 | ||||||
|  | 			const expression = try self.parse_expression(tokenizer); | ||||||
|  | 
 | ||||||
|  | 			try self.check_syntax( | ||||||
|  | 				tokenizer.step(.{.include_newlines = false}) and tokenizer.current_token == .symbol_paren_right, | ||||||
|  | 				"expected a closing `)` after expression"); | ||||||
|  | 
 | ||||||
|  | 			return Expression{.grouped_expression = try coral.io.allocate_one(self.arena.as_allocator(), expression)}; | ||||||
|  | 		}, | ||||||
|  | 
 | ||||||
|  | 		.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", | ||||||
|  | 					}); | ||||||
|  | 				}, | ||||||
|  | 			}; | ||||||
|  | 		}, | ||||||
|  | 
 | ||||||
|  | 		.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", | ||||||
|  | 					}); | ||||||
|  | 				}, | ||||||
|  | 			}; | ||||||
|  | 		}, | ||||||
|  | 
 | ||||||
|  | 		.string => |value| { | ||||||
|  | 			_ = tokenizer.step(.{.include_newlines = false}); | ||||||
|  | 
 | ||||||
|  | 			return Expression{.string_literal = value}; | ||||||
|  | 		}, | ||||||
|  | 
 | ||||||
|  | 		.symbol_bracket_left => { | ||||||
|  | 			try self.check_syntax(tokenizer.step(.{.include_newlines = false}), "unexpected end of array literal"); | ||||||
|  | 
 | ||||||
|  | 			var expression = Expression{.array_literal = .{}}; | ||||||
|  | 
 | ||||||
|  | 			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); | ||||||
|  | 
 | ||||||
|  | 			while (true) { | ||||||
|  | 				switch (tokenizer.current_token) { | ||||||
|  | 					.symbol_bracket_right => { | ||||||
|  | 						_ = tokenizer.step(.{.include_newlines = false}); | ||||||
|  | 
 | ||||||
|  | 						return expression; | ||||||
|  | 					}, | ||||||
|  | 
 | ||||||
|  | 					else => { | ||||||
|  | 						try self.check_syntax( | ||||||
|  | 							tokenizer.step(.{.include_newlines = false}), | ||||||
|  | 							"expected `]` or expression after `[`"); | ||||||
|  | 
 | ||||||
|  | 						try expression.array_literal.push_one(allocator, try self.parse_expression(tokenizer)); | ||||||
|  | 					}, | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
|  | 
 | ||||||
|  | 		.symbol_brace_left => { | ||||||
|  | 			try self.check_syntax(tokenizer.step(.{.include_newlines = false}), "unexpected end of table literal"); | ||||||
|  | 
 | ||||||
|  | 			var expression = Expression{.table_literal = .{}}; | ||||||
|  | 
 | ||||||
|  | 			coral.debug.assert(expression == .table_literal); | ||||||
|  | 
 | ||||||
|  | 			const allocator = self.arena.as_allocator(); | ||||||
|  | 
 | ||||||
|  | 			while (true) { | ||||||
|  | 				switch (tokenizer.current_token) { | ||||||
|  | 					.symbol_brace_right => { | ||||||
|  | 						_ = tokenizer.step(.{.include_newlines = false}); | ||||||
|  | 
 | ||||||
|  | 						return expression; | ||||||
|  | 					}, | ||||||
|  | 
 | ||||||
|  | 					.local => |identifier| { | ||||||
|  | 						try self.check_syntax( | ||||||
|  | 							tokenizer.step(.{.include_newlines = false}) and tokenizer.current_token == .symbol_equals, | ||||||
|  | 							"expected `=` after identifier"); | ||||||
|  | 
 | ||||||
|  | 						try self.check_syntax(tokenizer.step(.{.include_newlines = false}), "unexpected end after `=`"); | ||||||
|  | 
 | ||||||
|  | 						try expression.table_literal.push_one(allocator, .{ | ||||||
|  | 							.identifier = identifier, | ||||||
|  | 							.expression = try self.parse_expression(tokenizer), | ||||||
|  | 						}); | ||||||
|  | 
 | ||||||
|  | 						switch (tokenizer.current_token) { | ||||||
|  | 							.symbol_comma => _ = tokenizer.step(.{.include_newlines = false}), | ||||||
|  | 
 | ||||||
|  | 							.symbol_brace_right => { | ||||||
|  | 								_ = tokenizer.step(.{.include_newlines = false}); | ||||||
|  | 
 | ||||||
|  | 								return expression; | ||||||
|  | 							}, | ||||||
|  | 
 | ||||||
|  | 							else => return self.fail_syntax("expected `,` or `}` after expression"), | ||||||
|  | 						} | ||||||
|  | 					}, | ||||||
|  | 
 | ||||||
|  | 					else => return self.fail_syntax("expected `}` or fields in table literal"), | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
|  | 
 | ||||||
|  | 		.symbol_minus => { | ||||||
|  | 			try self.check_syntax( | ||||||
|  | 				tokenizer.step(.{.include_newlines = false}), | ||||||
|  | 				"expected expression after numeric negation (`-`)"); | ||||||
|  | 
 | ||||||
|  | 			return Expression{ | ||||||
|  | 				.unary_operation = .{ | ||||||
|  | 					.expression = try coral.io.allocate_one( | ||||||
|  | 						self.arena.as_allocator(), | ||||||
|  | 						try self.parse_factor(tokenizer)), | ||||||
|  | 
 | ||||||
|  | 					.operator = .numeric_negation, | ||||||
|  | 				}, | ||||||
|  | 			}; | ||||||
|  | 		}, | ||||||
|  | 
 | ||||||
|  | 		.symbol_bang => { | ||||||
|  | 			try self.check_syntax( | ||||||
|  | 				tokenizer.step(.{.include_newlines = false}), | ||||||
|  | 				"expected expression after numeric negation (`!`)"); | ||||||
|  | 
 | ||||||
|  | 			return Expression{ | ||||||
|  | 				.unary_operation = .{ | ||||||
|  | 					.expression = try coral.io.allocate_one( | ||||||
|  | 						self.arena.as_allocator(), | ||||||
|  | 						try self.parse_factor(tokenizer)), | ||||||
|  | 
 | ||||||
|  | 					.operator = .boolean_negation, | ||||||
|  | 				}, | ||||||
|  | 			}; | ||||||
|  | 		}, | ||||||
|  | 
 | ||||||
|  | 		else => return self.fail_syntax("unexpected token in expression"), | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const parse_term = binary_operation_parser(parse_factor, &.{ | ||||||
|  | 	.multiplication, | ||||||
|  | 	.divsion, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | pub fn reset(self: *Self) void { | ||||||
|  | 	self.statements.clear(); | ||||||
|  | 	self.arena.clear_allocations(); | ||||||
|  | } | ||||||
| @ -1,6 +1,6 @@ | |||||||
| const Environment = @import("./Environment.zig"); | const Ast = @import("./Ast.zig"); | ||||||
| 
 | 
 | ||||||
| const ast = @import("./ast.zig"); | const Environment = @import("./Environment.zig"); | ||||||
| 
 | 
 | ||||||
| const coral = @import("coral"); | const coral = @import("coral"); | ||||||
| 
 | 
 | ||||||
| @ -25,6 +25,7 @@ const Opcode = enum (u8) { | |||||||
| 	push_integer, | 	push_integer, | ||||||
| 	push_float, | 	push_float, | ||||||
| 	push_object, | 	push_object, | ||||||
|  | 	push_array, | ||||||
| 	push_table, | 	push_table, | ||||||
| 
 | 
 | ||||||
| 	not, | 	not, | ||||||
| @ -50,14 +51,30 @@ fn clear_error_details(self: *Self) void { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub fn compile(self: *Self, data: []const u8) types.RuntimeError!void { | pub fn compile(self: *Self, data: []const u8) types.RuntimeError!void { | ||||||
|  | 	var ast = try Ast.init(self.env.allocator); | ||||||
|  | 
 | ||||||
|  | 	errdefer ast.deinit(); | ||||||
|  | 
 | ||||||
|  | 	{ | ||||||
| 		var tokenizer = tokens.Tokenizer{.source = data}; | 		var tokenizer = tokens.Tokenizer{.source = data}; | ||||||
| 	var parsed_statements = try ast.ParsedStatements.init(self.env.allocator, &tokenizer); |  | ||||||
| 
 | 
 | ||||||
| 	switch (parsed_statements) { | 		ast.parse(&tokenizer) catch |init_error| { | ||||||
| 		.valid => |*statements| { | 			if (init_error == error.BadSyntax) { | ||||||
| 			defer statements.deinit(self.env.allocator); | 				self.clear_error_details(); | ||||||
| 
 | 
 | ||||||
| 			for (statements.list.values) |statement| { | 				var message_buffer = self.message_data.as_buffer(self.env.allocator); | ||||||
|  | 
 | ||||||
|  | 				coral.utf8.print_formatted(message_buffer.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) { | 		switch (statement) { | ||||||
| 			.return_expression => |return_expression| { | 			.return_expression => |return_expression| { | ||||||
| 				try self.compile_expression(return_expression); | 				try self.compile_expression(return_expression); | ||||||
| @ -70,34 +87,9 @@ pub fn compile(self: *Self, data: []const u8) types.RuntimeError!void { | |||||||
| 			}, | 			}, | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		.invalid => |invalid| { |  | ||||||
| 			self.clear_error_details(); |  | ||||||
| 
 |  | ||||||
| 			try self.message_data.push_all(self.env.allocator, "@("); |  | ||||||
| 
 |  | ||||||
| 			var message_buffer = self.message_data.as_buffer(self.env.allocator); |  | ||||||
| 			const message_writer = message_buffer.as_writer(); |  | ||||||
| 
 |  | ||||||
| 			coral.utf8.print_int(@typeInfo(usize).Int, message_writer, tokenizer.lines_stepped) catch { |  | ||||||
| 				return error.OutOfMemory; |  | ||||||
| 			}; |  | ||||||
| 
 |  | ||||||
| 			coral.utf8.print(message_writer, "): ") catch { |  | ||||||
| 				return error.OutOfMemory; |  | ||||||
| 			}; |  | ||||||
| 
 |  | ||||||
| 			coral.utf8.print(message_writer, invalid) catch { |  | ||||||
| 				return error.OutOfMemory; |  | ||||||
| 			}; |  | ||||||
| 
 |  | ||||||
| 			return error.BadSyntax; |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub fn compile_expression(self: *Self, expression: ast.Expression) types.RuntimeError!void { | pub fn compile_expression(self: *Self, expression: Ast.Expression) types.RuntimeError!void { | ||||||
| 	switch (expression) { | 	switch (expression) { | ||||||
| 		.nil_literal => try self.emit_opcode(.push_nil), | 		.nil_literal => try self.emit_opcode(.push_nil), | ||||||
| 		.true_literal => try self.emit_opcode(.push_true), | 		.true_literal => try self.emit_opcode(.push_true), | ||||||
| @ -126,33 +118,46 @@ pub fn compile_expression(self: *Self, expression: ast.Expression) types.Runtime | |||||||
| 			try self.emit_object(try self.intern(literal)); | 			try self.emit_object(try self.intern(literal)); | ||||||
| 		}, | 		}, | ||||||
| 
 | 
 | ||||||
| 		.table_literal => |literal| { | 		.array_literal => |elements| { | ||||||
| 			if (literal.values.len > coral.math.max_int(@typeInfo(types.Integer).Int)) { | 			if (elements.values.len > coral.math.max_int(@typeInfo(types.Integer).Int)) { | ||||||
| 				return error.OutOfMemory; | 				return error.OutOfMemory; | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			for (literal.values) |field| { | 			for (elements.values) |element_expression| { | ||||||
| 				try self.compile_expression(field.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_opcode(.push_object); | ||||||
| 				try self.emit_object(try self.intern(field.identifier)); | 				try self.emit_object(try self.intern(field.identifier)); | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			try self.emit_opcode(.push_table); | 			try self.emit_opcode(.push_table); | ||||||
| 			try self.emit_integer(@intCast(types.Integer, literal.values.len)); | 			try self.emit_integer(@intCast(types.Integer, fields.values.len)); | ||||||
| 		}, | 		}, | ||||||
| 
 | 
 | ||||||
| 		.binary_operation => |operation| { | 		.binary_operation => |operation| { | ||||||
| 			try self.compile_expression(operation.lhs_expression.*); | 			try self.compile_expression(operation.lhs_expression.*); | ||||||
| 			try self.compile_expression(operation.rhs_expression.*); | 			try self.compile_expression(operation.rhs_expression.*); | ||||||
| 
 | 
 | ||||||
| 			try self.emit_opcode(switch (operation.kind) { | 			try self.emit_opcode(switch (operation.operator) { | ||||||
| 				.addition => .add, | 				.addition => .add, | ||||||
| 				.subtraction => .sub, | 				.subtraction => .sub, | ||||||
| 				.multiplication => .mul, | 				.multiplication => .mul, | ||||||
| 				.division => .div, | 				.divsion => .div, | ||||||
| 				.equality_comparison => .compare_eq, | 				.greater_equals_comparison => .compare_eq, | ||||||
| 				.greater_than_comparison => .compare_gt, | 				.greater_than_comparison => .compare_gt, | ||||||
| 				.greater_equals_comparison => .compare_ge, | 				.equals_comparison => .compare_ge, | ||||||
| 				.less_than_comparison => .compare_lt, | 				.less_than_comparison => .compare_lt, | ||||||
| 				.less_equals_comparison => .compare_le, | 				.less_equals_comparison => .compare_le, | ||||||
| 			}); | 			}); | ||||||
| @ -161,7 +166,7 @@ pub fn compile_expression(self: *Self, expression: ast.Expression) types.Runtime | |||||||
| 		.unary_operation => |operation| { | 		.unary_operation => |operation| { | ||||||
| 			try self.compile_expression(operation.expression.*); | 			try self.compile_expression(operation.expression.*); | ||||||
| 
 | 
 | ||||||
| 			try self.emit_opcode(switch (operation.kind) { | 			try self.emit_opcode(switch (operation.operator) { | ||||||
| 				.boolean_negation => .not, | 				.boolean_negation => .not, | ||||||
| 				.numeric_negation => .neg, | 				.numeric_negation => .neg, | ||||||
| 			}); | 			}); | ||||||
| @ -199,24 +204,20 @@ pub fn emit_opcode(self: *Self, opcode: Opcode) coral.io.AllocationError!void { | |||||||
| pub fn error_details(self: Self) []const u8 { | pub fn error_details(self: Self) []const u8 { | ||||||
| 	coral.debug.assert(self.message_data.values.len >= self.message_name_len); | 	coral.debug.assert(self.message_data.values.len >= self.message_name_len); | ||||||
| 
 | 
 | ||||||
| 	return self.message_data.values[self.message_name_len .. ]; | 	return self.message_data.values; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub fn init(env: *Environment, chunk_name: []const u8) coral.io.AllocationError!Self { | pub fn init(env: *Environment, chunk_name: []const u8) coral.io.AllocationError!Self { | ||||||
| 	var bytecode_buffer = try Buffer.init(env.allocator, 0); | 	var message_data = Buffer{}; | ||||||
| 
 | 
 | ||||||
| 	errdefer bytecode_buffer.deinit(env.allocator); | 	try message_data.push_all(env.allocator, chunk_name); | ||||||
| 
 |  | ||||||
| 	var message_data = try Buffer.init(env.allocator, chunk_name.len); |  | ||||||
| 
 | 
 | ||||||
| 	errdefer message_data.deinit(env.allocator); | 	errdefer message_data.deinit(env.allocator); | ||||||
| 
 | 
 | ||||||
| 	message_data.push_all(env.allocator, chunk_name) catch unreachable; |  | ||||||
| 
 |  | ||||||
| 	return Self{ | 	return Self{ | ||||||
| 		.env = env, | 		.env = env, | ||||||
| 		.message_data = message_data, | 		.message_data = message_data, | ||||||
| 		.bytecode_buffer = bytecode_buffer, | 		.bytecode_buffer = .{}, | ||||||
| 		.message_name_len = chunk_name.len, | 		.message_name_len = chunk_name.len, | ||||||
| 	}; | 	}; | ||||||
| } | } | ||||||
|  | |||||||
| @ -235,8 +235,10 @@ pub fn execute_file(self: *Self, fs: file.System, file_path: file.Path) ExecuteF | |||||||
| 
 | 
 | ||||||
| 	defer readable_file.close(); | 	defer readable_file.close(); | ||||||
| 
 | 
 | ||||||
|  | 	var file_source = coral.list.Stack(u8){}; | ||||||
| 	const file_size = (try fs.query_info(file_path)).size; | 	const file_size = (try fs.query_info(file_path)).size; | ||||||
| 	var file_source = try coral.list.Stack(u8).init(self.allocator, file_size); | 
 | ||||||
|  | 	try file_source.grow(self.allocator, file_size); | ||||||
| 
 | 
 | ||||||
| 	defer file_source.deinit(self.allocator); | 	defer file_source.deinit(self.allocator); | ||||||
| 
 | 
 | ||||||
| @ -287,41 +289,35 @@ pub fn get_object(self: *Self, indexable: types.Ref, index: types.Ref) types.Run | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub fn init(allocator: coral.io.Allocator, options: InitOptions) !Self { | pub fn init(allocator: coral.io.Allocator, options: InitOptions) !Self { | ||||||
| 	var values = try ValueStack.init(allocator, options.values_max * options.calls_max); | 	var env = Self{ | ||||||
| 
 |  | ||||||
| 	errdefer values.deinit(allocator); |  | ||||||
| 
 |  | ||||||
| 	var calls = try CallStack.init(allocator, options.calls_max); |  | ||||||
| 
 |  | ||||||
| 	errdefer calls.deinit(allocator); |  | ||||||
| 
 |  | ||||||
| 	var interned = try InternTable.init(allocator); |  | ||||||
| 
 |  | ||||||
| 	errdefer interned.deinit(allocator); |  | ||||||
| 
 |  | ||||||
| 	var heap = try ObjectSlab.init(allocator); |  | ||||||
| 
 |  | ||||||
| 	errdefer heap.deinit(allocator); |  | ||||||
| 
 |  | ||||||
| 	var environment = Self{ |  | ||||||
| 		.global_object = 0, | 		.global_object = 0, | ||||||
| 		.allocator = allocator, | 		.allocator = allocator, | ||||||
| 		.reporter = options.reporter, | 		.reporter = options.reporter, | ||||||
| 		.interned = interned, | 		.interned = .{}, | ||||||
| 		.values = values, | 		.values = .{}, | ||||||
| 		.calls = calls, | 		.calls = .{}, | ||||||
| 		.heap = heap, | 		.heap = .{}, | ||||||
| 	}; | 	}; | ||||||
| 
 | 
 | ||||||
| 	const globals = try environment.new_object(&.{}, .{ | 	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", | 			.identity = "KYM GLOBAL OBJECT OC DO NOT STEAL", | ||||||
| 		}); | 		}); | ||||||
| 
 | 
 | ||||||
| 		coral.debug.assert(globals == .object); | 		coral.debug.assert(globals == .object); | ||||||
| 
 | 
 | ||||||
| 	environment.global_object = globals.object; | 		env.global_object = globals.object; | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	return environment; | 	return env; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub fn intern(self: *Self, string: []const u8) coral.io.AllocationError!types.Ref { | pub fn intern(self: *Self, string: []const u8) coral.io.AllocationError!types.Ref { | ||||||
| @ -360,17 +356,13 @@ pub fn new_object(self: *Self, userdata: []const u8, info: ObjectInfo) coral.io. | |||||||
| 
 | 
 | ||||||
| 	coral.io.copy(allocation, userdata); | 	coral.io.copy(allocation, userdata); | ||||||
| 
 | 
 | ||||||
| 	var fields = try Object.Fields.init(self.allocator); |  | ||||||
| 
 |  | ||||||
| 	errdefer fields.deinit(self.allocator); |  | ||||||
| 
 |  | ||||||
| 	return .{.object = try self.heap.insert(self.allocator, .{ | 	return .{.object = try self.heap.insert(self.allocator, .{ | ||||||
| 		.ref_count = 1, | 		.ref_count = 1, | ||||||
| 
 | 
 | ||||||
| 		.state = .{ | 		.state = .{ | ||||||
| 			.info = info, | 			.info = info, | ||||||
| 			.userdata = allocation, | 			.userdata = allocation, | ||||||
| 			.fields = fields, | 			.fields = .{}, | ||||||
| 		}, | 		}, | ||||||
| 	})}; | 	})}; | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,645 +0,0 @@ | |||||||
| const coral = @import("coral"); |  | ||||||
| 
 |  | ||||||
| const tokens = @import("./tokens.zig"); |  | ||||||
| 
 |  | ||||||
| const types = @import("./types.zig"); |  | ||||||
| 
 |  | ||||||
| pub const BinaryOperation = enum { |  | ||||||
| 	addition, |  | ||||||
| 	subtraction, |  | ||||||
| 	multiplication, |  | ||||||
| 	division, |  | ||||||
| 	equality_comparison, |  | ||||||
| 	greater_than_comparison, |  | ||||||
| 	greater_equals_comparison, |  | ||||||
| 	less_than_comparison, |  | ||||||
| 	less_equals_comparison, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| pub const ParsedExpression = union (enum) { |  | ||||||
| 	valid: Expression, |  | ||||||
| 	invalid: []const u8, |  | ||||||
| 
 |  | ||||||
| 	pub fn init(allocator: coral.io.Allocator, tokenizer: *tokens.Tokenizer) coral.io.AllocationError!ParsedExpression { |  | ||||||
| 		var parsed_lhs_expression = try init_equality(allocator, tokenizer); |  | ||||||
| 
 |  | ||||||
| 		switch (parsed_lhs_expression) { |  | ||||||
| 			.valid => |*lhs_expression| { |  | ||||||
| 				var expression = lhs_expression.*; |  | ||||||
| 				var is_invalid = true; |  | ||||||
| 
 |  | ||||||
| 				defer if (is_invalid) { |  | ||||||
| 					expression.deinit(allocator); |  | ||||||
| 				}; |  | ||||||
| 
 |  | ||||||
| 				if (tokenizer.current_token == .symbol_plus) { |  | ||||||
| 					if (!tokenizer.step()) { |  | ||||||
| 						return ParsedExpression{.invalid = "expected right-hand side of expression after `+`"}; |  | ||||||
| 					} |  | ||||||
| 
 |  | ||||||
| 					var parsed_rhs_expression = try init_equality(allocator, tokenizer); |  | ||||||
| 
 |  | ||||||
| 					switch (parsed_rhs_expression) { |  | ||||||
| 						.valid => |*rhs_expression| { |  | ||||||
| 							errdefer rhs_expression.deinit(allocator); |  | ||||||
| 
 |  | ||||||
| 							expression = try Expression.init_binary_operation( |  | ||||||
| 								allocator, |  | ||||||
| 								.addition, |  | ||||||
| 								lhs_expression, |  | ||||||
| 								rhs_expression); |  | ||||||
| 						}, |  | ||||||
| 
 |  | ||||||
| 						.invalid => |details| return ParsedExpression{.invalid = details}, |  | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				if (tokenizer.current_token == .symbol_minus) { |  | ||||||
| 					if (!tokenizer.step()) { |  | ||||||
| 						return ParsedExpression{.invalid = "expected right-hand side of expression after `-`"}; |  | ||||||
| 					} |  | ||||||
| 
 |  | ||||||
| 					var parsed_rhs_expression = try init_equality(allocator, tokenizer); |  | ||||||
| 
 |  | ||||||
| 					switch (parsed_rhs_expression) { |  | ||||||
| 						.valid => |*rhs_expression| { |  | ||||||
| 							errdefer rhs_expression.deinit(allocator); |  | ||||||
| 
 |  | ||||||
| 							expression = try Expression.init_binary_operation( |  | ||||||
| 								allocator, |  | ||||||
| 								.subtraction, |  | ||||||
| 								lhs_expression, |  | ||||||
| 								rhs_expression); |  | ||||||
| 						}, |  | ||||||
| 
 |  | ||||||
| 						.invalid => |details| return ParsedExpression{.invalid = details}, |  | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				is_invalid = false; |  | ||||||
| 
 |  | ||||||
| 				return ParsedExpression{.valid = expression}; |  | ||||||
| 			}, |  | ||||||
| 
 |  | ||||||
| 			.invalid => |details| return ParsedExpression{.invalid = details}, |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	pub fn init_comparison( |  | ||||||
| 		allocator: coral.io.Allocator, |  | ||||||
| 		tokenizer: *tokens.Tokenizer) coral.io.AllocationError!ParsedExpression { |  | ||||||
| 
 |  | ||||||
| 		var parsed_lhs_expression = try init_term(allocator, tokenizer); |  | ||||||
| 
 |  | ||||||
| 		switch (parsed_lhs_expression) { |  | ||||||
| 			.valid => |*lhs_expression| { |  | ||||||
| 				var expression = lhs_expression.*; |  | ||||||
| 				var is_invalid = true; |  | ||||||
| 
 |  | ||||||
| 				defer if (is_invalid) { |  | ||||||
| 					expression.deinit(allocator); |  | ||||||
| 				}; |  | ||||||
| 
 |  | ||||||
| 				if (tokenizer.current_token == .symbol_greater_than) { |  | ||||||
| 					if (!tokenizer.step()) { |  | ||||||
| 						return ParsedExpression{.invalid = "expected right-hand side of expression after `>`"}; |  | ||||||
| 					} |  | ||||||
| 
 |  | ||||||
| 					var parsed_rhs_expression = try init_term(allocator, tokenizer); |  | ||||||
| 
 |  | ||||||
| 					switch (parsed_rhs_expression) { |  | ||||||
| 						.valid => |*rhs_expression| { |  | ||||||
| 							errdefer rhs_expression.deinit(allocator); |  | ||||||
| 
 |  | ||||||
| 							expression = try Expression.init_binary_operation( |  | ||||||
| 								allocator, |  | ||||||
| 								.greater_than_comparison, |  | ||||||
| 								lhs_expression, |  | ||||||
| 								rhs_expression); |  | ||||||
| 						}, |  | ||||||
| 
 |  | ||||||
| 						.invalid => |details| return ParsedExpression{.invalid = details}, |  | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				if (tokenizer.current_token == .symbol_greater_equals) { |  | ||||||
| 					if (!tokenizer.step()) { |  | ||||||
| 						return ParsedExpression{.invalid = "expected right-hand side of expression after `>=`"}; |  | ||||||
| 					} |  | ||||||
| 
 |  | ||||||
| 					var parsed_rhs_expression = try init_term(allocator, tokenizer); |  | ||||||
| 
 |  | ||||||
| 					switch (parsed_rhs_expression) { |  | ||||||
| 						.valid => |*rhs_expression| { |  | ||||||
| 							errdefer rhs_expression.deinit(allocator); |  | ||||||
| 
 |  | ||||||
| 							expression = try Expression.init_binary_operation( |  | ||||||
| 								allocator, |  | ||||||
| 								.greater_equals_comparison, |  | ||||||
| 								lhs_expression, |  | ||||||
| 								rhs_expression); |  | ||||||
| 						}, |  | ||||||
| 
 |  | ||||||
| 						.invalid => |details| return ParsedExpression{.invalid = details}, |  | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				if (tokenizer.current_token == .symbol_less_than) { |  | ||||||
| 					if (!tokenizer.step()) { |  | ||||||
| 						return ParsedExpression{.invalid = "expected right-hand side of expression after `<`"}; |  | ||||||
| 					} |  | ||||||
| 
 |  | ||||||
| 					var parsed_rhs_expression = try init_term(allocator, tokenizer); |  | ||||||
| 
 |  | ||||||
| 					switch (parsed_rhs_expression) { |  | ||||||
| 						.valid => |*rhs_expression| { |  | ||||||
| 							errdefer rhs_expression.deinit(allocator); |  | ||||||
| 
 |  | ||||||
| 							expression = try Expression.init_binary_operation( |  | ||||||
| 								allocator, |  | ||||||
| 								.less_than_comparison, |  | ||||||
| 								lhs_expression, |  | ||||||
| 								rhs_expression); |  | ||||||
| 						}, |  | ||||||
| 
 |  | ||||||
| 						.invalid => |details| return ParsedExpression{.invalid = details}, |  | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				if (tokenizer.current_token == .symbol_less_equals) { |  | ||||||
| 					if (!tokenizer.step()) { |  | ||||||
| 						return ParsedExpression{.invalid = "expected right-hand side of expression after `<=`"}; |  | ||||||
| 					} |  | ||||||
| 
 |  | ||||||
| 					var parsed_rhs_expression = try init_term(allocator, tokenizer); |  | ||||||
| 
 |  | ||||||
| 					switch (parsed_rhs_expression) { |  | ||||||
| 						.valid => |*rhs_expression| { |  | ||||||
| 							errdefer rhs_expression.deinit(allocator); |  | ||||||
| 
 |  | ||||||
| 							expression = try Expression.init_binary_operation( |  | ||||||
| 								allocator, |  | ||||||
| 								.less_equals_comparison, |  | ||||||
| 								lhs_expression, |  | ||||||
| 								rhs_expression); |  | ||||||
| 						}, |  | ||||||
| 
 |  | ||||||
| 						.invalid => |details| return ParsedExpression{.invalid = details}, |  | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				is_invalid = false; |  | ||||||
| 
 |  | ||||||
| 				return ParsedExpression{.valid = expression}; |  | ||||||
| 			}, |  | ||||||
| 
 |  | ||||||
| 			.invalid => |details| return ParsedExpression{.invalid = details}, |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	fn init_equality( |  | ||||||
| 		allocator: coral.io.Allocator, |  | ||||||
| 		tokenizer: *tokens.Tokenizer) coral.io.AllocationError!ParsedExpression { |  | ||||||
| 
 |  | ||||||
| 		var parsed_lhs_expression = try init_comparison(allocator, tokenizer); |  | ||||||
| 
 |  | ||||||
| 		switch (parsed_lhs_expression) { |  | ||||||
| 			.valid => |*lhs_expression| { |  | ||||||
| 				var expression = lhs_expression.*; |  | ||||||
| 				var is_invalid = true; |  | ||||||
| 
 |  | ||||||
| 				defer if (is_invalid) { |  | ||||||
| 					expression.deinit(allocator); |  | ||||||
| 				}; |  | ||||||
| 
 |  | ||||||
| 				if (tokenizer.current_token == .symbol_double_equals) { |  | ||||||
| 					if (!tokenizer.step()) { |  | ||||||
| 						return ParsedExpression{.invalid = "expected right-hand side of expression after `==`"}; |  | ||||||
| 					} |  | ||||||
| 
 |  | ||||||
| 					var parsed_rhs_expression = try init_comparison(allocator, tokenizer); |  | ||||||
| 
 |  | ||||||
| 					switch (parsed_rhs_expression) { |  | ||||||
| 						.valid => |*rhs_expression| { |  | ||||||
| 							errdefer rhs_expression.deinit(allocator); |  | ||||||
| 
 |  | ||||||
| 							expression = try Expression.init_binary_operation( |  | ||||||
| 								allocator, |  | ||||||
| 								.equality_comparison, |  | ||||||
| 								lhs_expression, |  | ||||||
| 								rhs_expression); |  | ||||||
| 						}, |  | ||||||
| 
 |  | ||||||
| 						.invalid => |details| return ParsedExpression{.invalid = details}, |  | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				is_invalid = false; |  | ||||||
| 
 |  | ||||||
| 				return ParsedExpression{.valid = expression}; |  | ||||||
| 			}, |  | ||||||
| 
 |  | ||||||
| 			.invalid => |details| return ParsedExpression{.invalid = details}, |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	fn init_factor( |  | ||||||
| 		allocator: coral.io.Allocator, |  | ||||||
| 		tokenizer: *tokens.Tokenizer) coral.io.AllocationError!ParsedExpression { |  | ||||||
| 
 |  | ||||||
| 		switch (tokenizer.current_token) { |  | ||||||
| 			.symbol_paren_left => { |  | ||||||
| 				if (!tokenizer.step()) { |  | ||||||
| 					return ParsedExpression{.invalid = "expected an expression after `(`"}; |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				var parsed_expression = try ParsedExpression.init(allocator, tokenizer); |  | ||||||
| 
 |  | ||||||
| 				switch (parsed_expression) { |  | ||||||
| 					.valid => |*expression| { |  | ||||||
| 						var is_invalid = true; |  | ||||||
| 
 |  | ||||||
| 						defer if (is_invalid) { |  | ||||||
| 							expression.deinit(allocator); |  | ||||||
| 						}; |  | ||||||
| 
 |  | ||||||
| 						if ((!tokenizer.step()) or (tokenizer.current_token != .symbol_paren_right)) { |  | ||||||
| 							return ParsedExpression{.invalid = "expected a closing `)` after expression"}; |  | ||||||
| 						} |  | ||||||
| 
 |  | ||||||
| 						is_invalid = false; |  | ||||||
| 
 |  | ||||||
| 						return ParsedExpression{ |  | ||||||
| 							.valid = .{.grouped_expression = try coral.io.allocate_one(allocator, expression.*)}, |  | ||||||
| 						}; |  | ||||||
| 					}, |  | ||||||
| 
 |  | ||||||
| 					.invalid => |details| return ParsedExpression{.invalid = details}, |  | ||||||
| 				} |  | ||||||
| 			}, |  | ||||||
| 
 |  | ||||||
| 			.integer => |value| { |  | ||||||
| 				defer _ = tokenizer.step(); |  | ||||||
| 
 |  | ||||||
| 				return ParsedExpression{ |  | ||||||
| 					.valid = .{ |  | ||||||
| 						.integer_literal = coral.utf8.parse_int( |  | ||||||
| 							@typeInfo(types.Integer).Int, |  | ||||||
| 							value, .{}) catch |parse_error| { |  | ||||||
| 
 |  | ||||||
| 							return ParsedExpression{ |  | ||||||
| 								.invalid = switch (parse_error) { |  | ||||||
| 									error.BadSyntax => "invalid integer literal", |  | ||||||
| 									error.IntOverflow => "integer literal is too big", |  | ||||||
| 								} |  | ||||||
| 							}; |  | ||||||
| 						}, |  | ||||||
| 					}, |  | ||||||
| 				}; |  | ||||||
| 			}, |  | ||||||
| 
 |  | ||||||
| 			.real => |value| { |  | ||||||
| 				defer _ = tokenizer.step(); |  | ||||||
| 
 |  | ||||||
| 				return ParsedExpression{ |  | ||||||
| 					.valid = .{ |  | ||||||
| 						.float_literal = coral.utf8.parse_float( |  | ||||||
| 							@typeInfo(types.Float).Float, |  | ||||||
| 							value) catch |parse_error| { |  | ||||||
| 
 |  | ||||||
| 							return ParsedExpression{ |  | ||||||
| 								.invalid = switch (parse_error) { |  | ||||||
| 									error.BadSyntax => "invalid float literal", |  | ||||||
| 								}, |  | ||||||
| 							}; |  | ||||||
| 						}, |  | ||||||
| 					}, |  | ||||||
| 				}; |  | ||||||
| 			}, |  | ||||||
| 
 |  | ||||||
| 			.string => |value| { |  | ||||||
| 				defer _ = tokenizer.step(); |  | ||||||
| 
 |  | ||||||
| 				return ParsedExpression{ |  | ||||||
| 					.valid = .{ |  | ||||||
| 						.string_literal = value, |  | ||||||
| 					}, |  | ||||||
| 				}; |  | ||||||
| 			}, |  | ||||||
| 
 |  | ||||||
| 			.symbol_minus => { |  | ||||||
| 				if (!tokenizer.step()) { |  | ||||||
| 					return ParsedExpression{.invalid = "expected expression after numeric negation (`-`)"}; |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				var parsed_factor_expression = try init_factor(allocator, tokenizer); |  | ||||||
| 
 |  | ||||||
| 				switch (parsed_factor_expression) { |  | ||||||
| 					.valid => |*factor_expression| { |  | ||||||
| 						errdefer factor_expression.deinit(allocator); |  | ||||||
| 
 |  | ||||||
| 						return ParsedExpression{ |  | ||||||
| 							.valid = try Expression.init_unary_operation( |  | ||||||
| 								allocator, |  | ||||||
| 								.numeric_negation, |  | ||||||
| 								factor_expression), |  | ||||||
| 						}; |  | ||||||
| 					}, |  | ||||||
| 
 |  | ||||||
| 					.invalid => |details| return ParsedExpression{.invalid = details}, |  | ||||||
| 				} |  | ||||||
| 			}, |  | ||||||
| 
 |  | ||||||
| 			.symbol_bang => { |  | ||||||
| 				if (!tokenizer.step()) { |  | ||||||
| 					return ParsedExpression{.invalid = "expected expression after boolean negation (`!`)"}; |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				var parsed_factor_expression = try init_factor(allocator, tokenizer); |  | ||||||
| 
 |  | ||||||
| 				switch (parsed_factor_expression) { |  | ||||||
| 					.valid => |*factor_expression| { |  | ||||||
| 						errdefer factor_expression.deinit(allocator); |  | ||||||
| 
 |  | ||||||
| 						return ParsedExpression{ |  | ||||||
| 							.valid = try Expression.init_unary_operation( |  | ||||||
| 								allocator, |  | ||||||
| 								.boolean_negation, |  | ||||||
| 								factor_expression), |  | ||||||
| 						}; |  | ||||||
| 					}, |  | ||||||
| 
 |  | ||||||
| 					.invalid => |details| return ParsedExpression{.invalid = details}, |  | ||||||
| 				} |  | ||||||
| 			}, |  | ||||||
| 
 |  | ||||||
| 			else => return ParsedExpression{.invalid = "unexpected token in expression"}, |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	pub fn init_term(allocator: coral.io.Allocator, tokenizer: *tokens.Tokenizer) coral.io.AllocationError!ParsedExpression { |  | ||||||
| 		var parsed_lhs_expression = try init_factor(allocator, tokenizer); |  | ||||||
| 
 |  | ||||||
| 		switch (parsed_lhs_expression) { |  | ||||||
| 			.valid => |*lhs_expression| { |  | ||||||
| 				var expression = lhs_expression.*; |  | ||||||
| 				var is_invalid = true; |  | ||||||
| 
 |  | ||||||
| 				defer if (is_invalid) { |  | ||||||
| 					expression.deinit(allocator); |  | ||||||
| 				}; |  | ||||||
| 
 |  | ||||||
| 				if (tokenizer.current_token == .symbol_asterisk) { |  | ||||||
| 					if (!tokenizer.step()) { |  | ||||||
| 						return ParsedExpression{.invalid = "expected right-hand side of expression after `*`"}; |  | ||||||
| 					} |  | ||||||
| 
 |  | ||||||
| 					var parsed_rhs_expression = try init_factor(allocator, tokenizer); |  | ||||||
| 
 |  | ||||||
| 					switch (parsed_rhs_expression) { |  | ||||||
| 						.valid => |*rhs_expression| { |  | ||||||
| 							errdefer rhs_expression.deinit(allocator); |  | ||||||
| 
 |  | ||||||
| 							expression = try Expression.init_binary_operation( |  | ||||||
| 								allocator, |  | ||||||
| 								.multiplication, |  | ||||||
| 								lhs_expression, |  | ||||||
| 								rhs_expression); |  | ||||||
| 						}, |  | ||||||
| 
 |  | ||||||
| 						.invalid => |details| return ParsedExpression{.invalid = details}, |  | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				if (tokenizer.current_token == .symbol_forward_slash) { |  | ||||||
| 					if (!tokenizer.step()) { |  | ||||||
| 						return ParsedExpression{.invalid = "expected right-hand side of expression after `/`"}; |  | ||||||
| 					} |  | ||||||
| 
 |  | ||||||
| 					var parsed_rhs_expression = try init_equality(allocator, tokenizer); |  | ||||||
| 
 |  | ||||||
| 					switch (parsed_rhs_expression) { |  | ||||||
| 						.valid => |*rhs_expression| { |  | ||||||
| 							errdefer rhs_expression.deinit(allocator); |  | ||||||
| 
 |  | ||||||
| 							expression = try Expression.init_binary_operation( |  | ||||||
| 								allocator, |  | ||||||
| 								.division, |  | ||||||
| 								lhs_expression, |  | ||||||
| 								rhs_expression); |  | ||||||
| 						}, |  | ||||||
| 
 |  | ||||||
| 						.invalid => |details| return ParsedExpression{.invalid = details}, |  | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				is_invalid = false; |  | ||||||
| 
 |  | ||||||
| 				return ParsedExpression{.valid = expression}; |  | ||||||
| 			}, |  | ||||||
| 
 |  | ||||||
| 			.invalid => |details| return ParsedExpression{.invalid = details}, |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| pub const ParsedStatements = union (enum) { |  | ||||||
| 	valid: Statements, |  | ||||||
| 	invalid: []const u8, |  | ||||||
| 
 |  | ||||||
| 	pub fn init(allocator: coral.io.Allocator, tokenizer: *tokens.Tokenizer) coral.io.AllocationError!ParsedStatements { |  | ||||||
| 		var statements_list = try Statements.List.init(allocator, 0); |  | ||||||
| 		var has_returned = false; |  | ||||||
| 		var is_invalid = true; |  | ||||||
| 
 |  | ||||||
| 		defer if (is_invalid) { |  | ||||||
| 			for (statements_list.values) |*statement| { |  | ||||||
| 				statement.deinit(allocator); |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			statements_list.deinit(allocator); |  | ||||||
| 		}; |  | ||||||
| 
 |  | ||||||
| 		while (tokenizer.step()) { |  | ||||||
| 			switch (tokenizer.current_token) { |  | ||||||
| 				.newline => {}, |  | ||||||
| 
 |  | ||||||
| 				.keyword_return => { |  | ||||||
| 					if (has_returned) { |  | ||||||
| 						return ParsedStatements{.invalid = "cannot return more than once per function scope"}; |  | ||||||
| 					} |  | ||||||
| 
 |  | ||||||
| 					if (tokenizer.step() and (tokenizer.current_token != .newline)) { |  | ||||||
| 						var parsed_expression = try ParsedExpression.init(allocator, tokenizer); |  | ||||||
| 
 |  | ||||||
| 						switch (parsed_expression) { |  | ||||||
| 							.valid => |*expression| { |  | ||||||
| 								errdefer expression.deinit(allocator); |  | ||||||
| 
 |  | ||||||
| 								try statements_list.push_one(allocator, .{ |  | ||||||
| 									.return_expression = expression.*, |  | ||||||
| 								}); |  | ||||||
| 							}, |  | ||||||
| 
 |  | ||||||
| 							.invalid => |details| { |  | ||||||
| 								return ParsedStatements{.invalid = details}; |  | ||||||
| 							}, |  | ||||||
| 						} |  | ||||||
| 					} else { |  | ||||||
| 						try statements_list.push_one(allocator, .return_nothing); |  | ||||||
| 					} |  | ||||||
| 
 |  | ||||||
| 					if (tokenizer.step() and tokenizer.current_token != .newline) { |  | ||||||
| 						return ParsedStatements{.invalid = "expected newline after expression"}; |  | ||||||
| 					} |  | ||||||
| 
 |  | ||||||
| 					has_returned = true; |  | ||||||
| 				}, |  | ||||||
| 
 |  | ||||||
| 				else => { |  | ||||||
| 					return ParsedStatements{.invalid = "invalid statement"}; |  | ||||||
| 				}, |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		is_invalid = false; |  | ||||||
| 
 |  | ||||||
| 		return ParsedStatements{ |  | ||||||
| 			.valid = .{ |  | ||||||
| 				.list = statements_list, |  | ||||||
| 			}, |  | ||||||
| 		}; |  | ||||||
| 	} |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| pub const Expression = union (enum) { |  | ||||||
| 	nil_literal, |  | ||||||
| 	true_literal, |  | ||||||
| 	false_literal, |  | ||||||
| 	integer_literal: types.Integer, |  | ||||||
| 	float_literal: types.Float, |  | ||||||
| 	string_literal: []const u8, |  | ||||||
| 	table_literal: TableLiteral, |  | ||||||
| 	grouped_expression: *Expression, |  | ||||||
| 
 |  | ||||||
| 	binary_operation: struct { |  | ||||||
| 		kind: BinaryOperation, |  | ||||||
| 		lhs_expression: *Expression, |  | ||||||
| 		rhs_expression: *Expression, |  | ||||||
| 	}, |  | ||||||
| 
 |  | ||||||
| 	unary_operation: struct { |  | ||||||
| 		kind: UnaryOperation, |  | ||||||
| 		expression: *Expression, |  | ||||||
| 	}, |  | ||||||
| 
 |  | ||||||
| 	const TableLiteral = coral.list.Stack(struct { |  | ||||||
| 		identifier: []const u8, |  | ||||||
| 		expression: *Expression, |  | ||||||
| 	}); |  | ||||||
| 
 |  | ||||||
| 	fn deinit(self: *Expression, allocator: coral.io.Allocator) void { |  | ||||||
| 		switch (self.*) { |  | ||||||
| 			.nil_literal, .true_literal, .false_literal, .integer_literal, .float_literal, .string_literal => {}, |  | ||||||
| 
 |  | ||||||
| 			.table_literal => |*literal| { |  | ||||||
| 				for (literal.values) |field| { |  | ||||||
| 					field.expression.deinit(allocator); |  | ||||||
| 					coral.io.deallocate(allocator, field.expression); |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				literal.deinit(allocator); |  | ||||||
| 			}, |  | ||||||
| 
 |  | ||||||
| 			.grouped_expression => |expression| { |  | ||||||
| 				expression.deinit(allocator); |  | ||||||
| 			}, |  | ||||||
| 
 |  | ||||||
| 			.binary_operation => |operation| { |  | ||||||
| 				operation.lhs_expression.deinit(allocator); |  | ||||||
| 				coral.io.deallocate(allocator, operation.lhs_expression); |  | ||||||
| 				operation.rhs_expression.deinit(allocator); |  | ||||||
| 				coral.io.deallocate(allocator, operation.rhs_expression); |  | ||||||
| 			}, |  | ||||||
| 
 |  | ||||||
| 			.unary_operation => |operation| { |  | ||||||
| 				operation.expression.deinit(allocator); |  | ||||||
| 				coral.io.deallocate(allocator, operation.expression); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	fn init_binary_operation( |  | ||||||
| 		allocator: coral.io.Allocator, |  | ||||||
| 		kind: BinaryOperation, |  | ||||||
| 		lhs_expression: *const Expression, |  | ||||||
| 		rhs_operation: *const Expression) coral.io.AllocationError!Expression { |  | ||||||
| 
 |  | ||||||
| 		const allocated_lhs_expression = try coral.io.allocate_one(allocator, lhs_expression.*); |  | ||||||
| 
 |  | ||||||
| 		errdefer coral.io.deallocate(allocator, allocated_lhs_expression); |  | ||||||
| 
 |  | ||||||
| 		const allocated_rhs_expression = try coral.io.allocate_one(allocator, rhs_operation.*); |  | ||||||
| 
 |  | ||||||
| 		errdefer coral.io.deallocate(allocator, allocated_rhs_expression); |  | ||||||
| 
 |  | ||||||
| 		return Expression{ |  | ||||||
| 			.binary_operation = .{ |  | ||||||
| 				.kind = kind, |  | ||||||
| 				.lhs_expression = allocated_lhs_expression, |  | ||||||
| 				.rhs_expression = allocated_rhs_expression, |  | ||||||
| 			}, |  | ||||||
| 		}; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	fn init_unary_operation( |  | ||||||
| 		allocator: coral.io.Allocator, |  | ||||||
| 		kind: UnaryOperation, |  | ||||||
| 		expression: *const Expression) coral.io.AllocationError!Expression { |  | ||||||
| 
 |  | ||||||
| 		const allocated_expression = try coral.io.allocate_one(allocator, expression.*); |  | ||||||
| 
 |  | ||||||
| 		errdefer coral.io.deallocate(allocator, allocated_expression); |  | ||||||
| 
 |  | ||||||
| 		return Expression{ |  | ||||||
| 			.unary_operation = .{ |  | ||||||
| 				.kind = kind, |  | ||||||
| 				.expression = allocated_expression, |  | ||||||
| 			}, |  | ||||||
| 		}; |  | ||||||
| 	} |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| pub const Statements = struct { |  | ||||||
| 	list: List, |  | ||||||
| 
 |  | ||||||
| 	const List = coral.list.Stack(union (enum) { |  | ||||||
| 		return_expression: Expression, |  | ||||||
| 		return_nothing, |  | ||||||
| 
 |  | ||||||
| 		const Self = @This(); |  | ||||||
| 
 |  | ||||||
| 		fn deinit(self: *Self, allocator: coral.io.Allocator) void { |  | ||||||
| 			switch (self.*) { |  | ||||||
| 				.return_expression => |*expression| { |  | ||||||
| 					expression.deinit(allocator); |  | ||||||
| 				}, |  | ||||||
| 
 |  | ||||||
| 				.return_nothing => {}, |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	}); |  | ||||||
| 
 |  | ||||||
| 	pub fn deinit(self: *Statements, allocator: coral.io.Allocator) void { |  | ||||||
| 		for (self.list.values) |*statement| { |  | ||||||
| 			statement.deinit(allocator); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		self.list.deinit(allocator); |  | ||||||
| 	} |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| pub const UnaryOperation = enum { |  | ||||||
| 	boolean_negation, |  | ||||||
| 	numeric_negation, |  | ||||||
| }; |  | ||||||
| @ -7,7 +7,6 @@ pub const Token = union(enum) { | |||||||
| 	global: []const u8, | 	global: []const u8, | ||||||
| 	local: []const u8, | 	local: []const u8, | ||||||
| 
 | 
 | ||||||
| 	symbol_assign, |  | ||||||
| 	symbol_plus, | 	symbol_plus, | ||||||
| 	symbol_minus, | 	symbol_minus, | ||||||
| 	symbol_asterisk, | 	symbol_asterisk, | ||||||
| @ -44,25 +43,31 @@ pub const Token = union(enum) { | |||||||
| 		return switch (self) { | 		return switch (self) { | ||||||
| 			.unknown => |unknown| @ptrCast([*]const u8, &unknown)[0 .. 1], | 			.unknown => |unknown| @ptrCast([*]const u8, &unknown)[0 .. 1], | ||||||
| 			.newline => "newline", | 			.newline => "newline", | ||||||
| 			.identifier_global => |identifier| identifier, |  | ||||||
| 			.identifier_local => |identifier| identifier, |  | ||||||
| 
 | 
 | ||||||
| 			.assign => "=", | 			.global => |identifier| identifier, | ||||||
| 			.plus => "+", | 			.local => |identifier| identifier, | ||||||
| 			.minus => "-", | 
 | ||||||
| 			.asterisk => "*", | 			.symbol_plus => "+", | ||||||
| 			.forward_slash => "/", | 			.symbol_minus => "-", | ||||||
| 			.paren_left => "(", | 			.symbol_asterisk => "*", | ||||||
| 			.paren_right => ")", | 			.symbol_forward_slash => "/", | ||||||
| 			.bang => "!", | 			.symbol_paren_left => "(", | ||||||
| 			.comma => ",", | 			.symbol_paren_right => ")", | ||||||
| 			.at => "@", | 			.symbol_bang => "!", | ||||||
| 			.brace_left => "{", | 			.symbol_comma => ",", | ||||||
| 			.brace_right => "}", | 			.symbol_at => "@", | ||||||
| 			.bracket_left => "[", | 			.symbol_brace_left => "{", | ||||||
| 			.bracket_right => "]", | 			.symbol_brace_right => "}", | ||||||
| 			.period => ".", | 			.symbol_bracket_left => "[", | ||||||
| 			.arrow => "=>", | 			.symbol_bracket_right => "]", | ||||||
|  | 			.symbol_period => ".", | ||||||
|  | 			.symbol_lambda => "=>", | ||||||
|  | 			.symbol_less_than => "<", | ||||||
|  | 			.symbol_less_equals => "<=", | ||||||
|  | 			.symbol_greater_than => ">", | ||||||
|  | 			.symbol_greater_equals => ">=", | ||||||
|  | 			.symbol_equals => "=", | ||||||
|  | 			.symbol_double_equals => "==", | ||||||
| 
 | 
 | ||||||
| 			.integer => |literal| literal, | 			.integer => |literal| literal, | ||||||
| 			.real => |literal| literal, | 			.real => |literal| literal, | ||||||
| @ -80,25 +85,25 @@ pub const Token = union(enum) { | |||||||
| pub const Tokenizer = struct { | pub const Tokenizer = struct { | ||||||
| 	source: []const u8, | 	source: []const u8, | ||||||
| 	lines_stepped: usize = 1, | 	lines_stepped: usize = 1, | ||||||
| 	previous_token: Token = .newline, | 	current_token: Token = .{.unknown = 0}, | ||||||
| 	current_token: Token = .newline, |  | ||||||
| 
 | 
 | ||||||
| 	pub fn has_next(self: Tokenizer) bool { | 	const StepOptions = struct { | ||||||
| 		return self.source.len != 0; | 		include_newlines: bool, | ||||||
| 	} | 	}; | ||||||
| 
 |  | ||||||
| 	pub fn step(self: *Tokenizer) bool { |  | ||||||
| 		self.previous_token = self.current_token; |  | ||||||
| 
 | 
 | ||||||
|  | 	pub fn step(self: *Tokenizer, options: StepOptions) bool { | ||||||
| 		var cursor = @as(usize, 0); | 		var cursor = @as(usize, 0); | ||||||
| 
 | 
 | ||||||
| 		defer self.source = self.source[cursor ..]; | 		defer self.source = self.source[cursor ..]; | ||||||
| 
 | 
 | ||||||
| 		while (self.has_next()) switch (self.source[cursor]) { | 		defer @import("std").debug.print("{s}\n", .{self.current_token.text()}); | ||||||
|  | 
 | ||||||
|  | 		while (cursor < self.source.len) { | ||||||
|  | 			switch (self.source[cursor]) { | ||||||
| 				'#' => { | 				'#' => { | ||||||
| 					cursor += 1; | 					cursor += 1; | ||||||
| 
 | 
 | ||||||
| 				while (self.has_next() and (self.source[cursor] == '\n')) { | 					while (cursor < self.source.len and self.source[cursor] == '\n') { | ||||||
| 						cursor += 1; | 						cursor += 1; | ||||||
| 					} | 					} | ||||||
| 				}, | 				}, | ||||||
| @ -107,10 +112,12 @@ pub const Tokenizer = struct { | |||||||
| 
 | 
 | ||||||
| 				'\n' => { | 				'\n' => { | ||||||
| 					cursor += 1; | 					cursor += 1; | ||||||
| 				self.lines_stepped += 1; |  | ||||||
| 					self.current_token = .newline; | 					self.current_token = .newline; | ||||||
|  | 					self.lines_stepped += 1; | ||||||
| 
 | 
 | ||||||
|  | 					if (options.include_newlines) { | ||||||
| 						return true; | 						return true; | ||||||
|  | 					} | ||||||
| 				}, | 				}, | ||||||
| 
 | 
 | ||||||
| 				'0' ... '9' => { | 				'0' ... '9' => { | ||||||
| @ -118,13 +125,13 @@ pub const Tokenizer = struct { | |||||||
| 
 | 
 | ||||||
| 					cursor += 1; | 					cursor += 1; | ||||||
| 
 | 
 | ||||||
| 				while (self.has_next()) switch (self.source[cursor]) { | 					while (cursor < self.source.len) switch (self.source[cursor]) { | ||||||
| 						'0' ... '9' => cursor += 1, | 						'0' ... '9' => cursor += 1, | ||||||
| 
 | 
 | ||||||
| 						'.' => { | 						'.' => { | ||||||
| 							cursor += 1; | 							cursor += 1; | ||||||
| 
 | 
 | ||||||
| 						while (self.has_next()) switch (self.source[cursor]) { | 							while (cursor < self.source.len) switch (self.source[cursor]) { | ||||||
| 								'0' ... '9' => cursor += 1, | 								'0' ... '9' => cursor += 1, | ||||||
| 								else => break, | 								else => break, | ||||||
| 							}; | 							}; | ||||||
| @ -159,40 +166,52 @@ pub const Tokenizer = struct { | |||||||
| 					switch (identifier[0]) { | 					switch (identifier[0]) { | ||||||
| 						'n' => if (coral.io.ends_with(identifier, "il")) { | 						'n' => if (coral.io.ends_with(identifier, "il")) { | ||||||
| 							self.current_token = .keyword_nil; | 							self.current_token = .keyword_nil; | ||||||
|  | 
 | ||||||
|  | 							return true; | ||||||
| 						}, | 						}, | ||||||
| 
 | 
 | ||||||
| 						'f' => if (coral.io.ends_with(identifier, "alse")) { | 						'f' => if (coral.io.ends_with(identifier, "alse")) { | ||||||
| 							self.current_token = .keyword_false; | 							self.current_token = .keyword_false; | ||||||
|  | 
 | ||||||
|  | 							return true; | ||||||
| 						}, | 						}, | ||||||
| 
 | 
 | ||||||
| 						't' => if (coral.io.ends_with(identifier, "rue")) { | 						't' => if (coral.io.ends_with(identifier, "rue")) { | ||||||
| 							self.current_token = .keyword_true; | 							self.current_token = .keyword_true; | ||||||
|  | 
 | ||||||
|  | 							return true; | ||||||
| 						}, | 						}, | ||||||
| 
 | 
 | ||||||
| 						'r' => if (coral.io.ends_with(identifier, "eturn")) { | 						'r' => if (coral.io.ends_with(identifier, "eturn")) { | ||||||
| 							self.current_token = .keyword_return; | 							self.current_token = .keyword_return; | ||||||
|  | 
 | ||||||
|  | 							return true; | ||||||
| 						}, | 						}, | ||||||
| 
 | 
 | ||||||
| 						's' => if (coral.io.ends_with(identifier, "elf")) { | 						's' => if (coral.io.ends_with(identifier, "elf")) { | ||||||
| 							self.current_token = .keyword_self; | 							self.current_token = .keyword_self; | ||||||
|  | 
 | ||||||
|  | 							return true; | ||||||
| 						}, | 						}, | ||||||
| 
 | 
 | ||||||
| 					else => self.current_token = .{.local = identifier}, | 						else => {}, | ||||||
| 					} | 					} | ||||||
| 
 | 
 | ||||||
|  | 					self.current_token = .{.local = identifier}; | ||||||
|  | 
 | ||||||
| 					return true; | 					return true; | ||||||
| 				}, | 				}, | ||||||
| 
 | 
 | ||||||
| 				'@' => { | 				'@' => { | ||||||
| 					cursor += 1; | 					cursor += 1; | ||||||
| 
 | 
 | ||||||
| 				if (self.has_next()) switch (self.source[cursor]) { | 					if (cursor < self.source.len) switch (self.source[cursor]) { | ||||||
| 						'A'...'Z', 'a'...'z', '_' => { | 						'A'...'Z', 'a'...'z', '_' => { | ||||||
| 							const begin = cursor; | 							const begin = cursor; | ||||||
| 
 | 
 | ||||||
| 							cursor += 1; | 							cursor += 1; | ||||||
| 
 | 
 | ||||||
| 						while (self.has_next()) switch (self.source[cursor]) { | 							while (cursor < self.source.len) switch (self.source[cursor]) { | ||||||
| 								'0'...'9', 'A'...'Z', 'a'...'z', '_' => cursor += 1, | 								'0'...'9', 'A'...'Z', 'a'...'z', '_' => cursor += 1, | ||||||
| 								else => break, | 								else => break, | ||||||
| 							}; | 							}; | ||||||
| @ -209,7 +228,7 @@ pub const Tokenizer = struct { | |||||||
| 
 | 
 | ||||||
| 							cursor += 1; | 							cursor += 1; | ||||||
| 
 | 
 | ||||||
| 						while (self.has_next()) switch (self.source[cursor]) { | 							while (cursor < self.source.len) switch (self.source[cursor]) { | ||||||
| 								'"' => break, | 								'"' => break, | ||||||
| 								else => cursor += 1, | 								else => cursor += 1, | ||||||
| 							}; | 							}; | ||||||
| @ -235,7 +254,7 @@ pub const Tokenizer = struct { | |||||||
| 
 | 
 | ||||||
| 					cursor += 1; | 					cursor += 1; | ||||||
| 
 | 
 | ||||||
| 				while (self.has_next()) switch (self.source[cursor]) { | 					while (cursor < self.source.len) switch (self.source[cursor]) { | ||||||
| 						'"' => break, | 						'"' => break, | ||||||
| 						else => cursor += 1, | 						else => cursor += 1, | ||||||
| 					}; | 					}; | ||||||
| @ -319,7 +338,7 @@ pub const Tokenizer = struct { | |||||||
| 				'=' => { | 				'=' => { | ||||||
| 					cursor += 1; | 					cursor += 1; | ||||||
| 
 | 
 | ||||||
| 				if (self.has_next()) { | 					if (cursor < self.source.len) { | ||||||
| 						switch (self.source[cursor]) { | 						switch (self.source[cursor]) { | ||||||
| 							'=' => { | 							'=' => { | ||||||
| 								cursor += 1; | 								cursor += 1; | ||||||
| @ -347,7 +366,7 @@ pub const Tokenizer = struct { | |||||||
| 				'<' => { | 				'<' => { | ||||||
| 					cursor += 1; | 					cursor += 1; | ||||||
| 
 | 
 | ||||||
| 				if (self.has_next() and (self.source[cursor] == '=')) { | 					if (cursor < self.source.len and (self.source[cursor] == '=')) { | ||||||
| 						cursor += 1; | 						cursor += 1; | ||||||
| 						self.current_token = .symbol_less_equals; | 						self.current_token = .symbol_less_equals; | ||||||
| 
 | 
 | ||||||
| @ -362,7 +381,7 @@ pub const Tokenizer = struct { | |||||||
| 				'>' => { | 				'>' => { | ||||||
| 					cursor += 1; | 					cursor += 1; | ||||||
| 
 | 
 | ||||||
| 				if (self.has_next() and (self.source[cursor] == '=')) { | 					if (cursor < self.source.len and (self.source[cursor] == '=')) { | ||||||
| 						cursor += 1; | 						cursor += 1; | ||||||
| 						self.current_token = .symbol_greater_equals; | 						self.current_token = .symbol_greater_equals; | ||||||
| 
 | 
 | ||||||
| @ -387,7 +406,8 @@ pub const Tokenizer = struct { | |||||||
| 
 | 
 | ||||||
| 					return true; | 					return true; | ||||||
| 				}, | 				}, | ||||||
| 		}; | 			} | ||||||
|  | 		} | ||||||
| 
 | 
 | ||||||
| 		self.current_token = .newline; | 		self.current_token = .newline; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -28,10 +28,13 @@ pub const Ref = union (Primitive) { | |||||||
| 	object: Object, | 	object: Object, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| pub const RuntimeError = coral.io.AllocationError || CheckError || error { | pub const ParseError = error { | ||||||
|  | 	OutOfMemory, | ||||||
| 	BadSyntax, | 	BadSyntax, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | pub const RuntimeError = CheckError || ParseError; | ||||||
|  | 
 | ||||||
| pub const Val = union (Primitive) { | pub const Val = union (Primitive) { | ||||||
| 	nil, | 	nil, | ||||||
| 	false, | 	false, | ||||||
|  | |||||||
| @ -59,17 +59,15 @@ pub fn run_app(base_file_system: file.System) void { | |||||||
| 	const Logger = struct { | 	const Logger = struct { | ||||||
| 		const Self = @This(); | 		const Self = @This(); | ||||||
| 
 | 
 | ||||||
| 		fn log(_: *Self, message: []const u8) void { | 		fn log(_: *const Self, message: []const u8) void { | ||||||
| 			ext.SDL_LogError(ext.SDL_LOG_CATEGORY_APPLICATION, "%.*s", message.len, message.ptr); | 			ext.SDL_LogError(ext.SDL_LOG_CATEGORY_APPLICATION, "%.*s", message.len, message.ptr); | ||||||
| 		} | 		} | ||||||
| 	}; | 	}; | ||||||
| 
 | 
 | ||||||
| 	var logger = Logger{}; |  | ||||||
| 
 |  | ||||||
| 	var script_environment = kym.Environment.init(heap.allocator, .{ | 	var script_environment = kym.Environment.init(heap.allocator, .{ | ||||||
| 		.values_max = 512, | 		.values_max = 512, | ||||||
| 		.calls_max = 512, | 		.calls_max = 512, | ||||||
| 		.reporter = kym.Environment.Reporter.bind(Logger, &logger, Logger.log), | 		.reporter = kym.Environment.Reporter.bind(Logger, &.{}, Logger.log), | ||||||
| 	}) catch { | 	}) catch { | ||||||
| 		return ext.SDL_LogError(ext.SDL_LOG_CATEGORY_APPLICATION, "failed to initialize Kym vm\n"); | 		return ext.SDL_LogError(ext.SDL_LOG_CATEGORY_APPLICATION, "failed to initialize Kym vm\n"); | ||||||
| 	}; | 	}; | ||||||
|  | |||||||
| @ -1,3 +1,5 @@ | |||||||
|  | const coral = @import("coral"); | ||||||
|  | 
 | ||||||
| const ona = @import("ona"); | const ona = @import("ona"); | ||||||
| 
 | 
 | ||||||
| pub fn main() !void { | pub fn main() !void { | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user