Compare commits
	
		
			No commits in common. "7b266fde587ec56da79847b8fe3eb8e85080459d" and "c1f174a513f5f5da05591095fbef837432428683" have entirely different histories.
		
	
	
		
			7b266fde58
			...
			c1f174a513
		
	
		
							
								
								
									
										20
									
								
								build.zig
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								build.zig
									
									
									
									
									
								
							| @ -18,27 +18,17 @@ pub fn build(b: *std.Build) void { | ||||
| 	}); | ||||
| 
 | ||||
| 	b.installArtifact(create: { | ||||
| 		const compile_step = b.addExecutable(.{ | ||||
| 		const runner_exe = b.addExecutable(.{ | ||||
| 			.name = "runner", | ||||
| 			.root_source_file = .{ .path = "source/runner.zig" }, | ||||
| 			.target = target, | ||||
| 			.optimize = optimize, | ||||
| 		}); | ||||
| 
 | ||||
| 		compile_step.addModule("ona", ona_module); | ||||
| 		compile_step.linkLibC(); | ||||
| 		compile_step.linkSystemLibrary("SDL2"); | ||||
| 		runner_exe.addModule("ona", ona_module); | ||||
| 		runner_exe.linkLibC(); | ||||
| 		runner_exe.linkSystemLibrary("SDL2"); | ||||
| 
 | ||||
| 		break: create compile_step; | ||||
| 	}); | ||||
| 
 | ||||
| 	b.step("test", "Run unit tests").dependOn(create: { | ||||
| 		const tests = b.addTest(.{ | ||||
| 			.root_source_file = .{.path = "source/test.zig"}, | ||||
| 			.target = target, | ||||
| 			.optimize = optimize, | ||||
| 		}); | ||||
| 
 | ||||
| 		break: create &tests.step; | ||||
| 		break: create runner_exe; | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| @ -24,7 +24,11 @@ pub const Allocator = struct { | ||||
| 	pub fn bind(comptime State: type, state: *State, comptime actions: Actions(State)) Allocator { | ||||
| 		const is_zero_aligned = @alignOf(State) == 0; | ||||
| 
 | ||||
| 		const ErasedActions = struct { | ||||
| 		return .{ | ||||
| 			.context = if (is_zero_aligned) state else @ptrCast(state), | ||||
| 
 | ||||
| 			.actions = &.{ | ||||
| 				.deallocate = struct { | ||||
| 					fn deallocate(context: *anyopaque, allocation: []Byte) void { | ||||
| 						if (is_zero_aligned) { | ||||
| 							return actions.deallocator(@ptrCast(context), allocation); | ||||
| @ -32,7 +36,9 @@ pub const Allocator = struct { | ||||
| 
 | ||||
| 						return actions.deallocate(@ptrCast(@alignCast(context)), allocation); | ||||
| 					} | ||||
| 				}.deallocate, | ||||
| 
 | ||||
| 				.reallocate = struct { | ||||
| 					fn reallocate(context: *anyopaque, return_address: usize, existing_allocation: ?[]Byte, size: usize) AllocationError![]Byte { | ||||
| 						if (is_zero_aligned) { | ||||
| 							return actions.reallocator(@ptrCast(context), return_address, existing_allocation, size); | ||||
| @ -40,14 +46,7 @@ pub const Allocator = struct { | ||||
| 
 | ||||
| 						return actions.reallocate(@ptrCast(@alignCast(context)), return_address, existing_allocation, size); | ||||
| 					} | ||||
| 		}; | ||||
| 
 | ||||
| 		return .{ | ||||
| 			.context = if (is_zero_aligned) state else @ptrCast(state), | ||||
| 
 | ||||
| 			.actions = &.{ | ||||
| 				.deallocate = ErasedActions.deallocate, | ||||
| 				.reallocate = ErasedActions.reallocate, | ||||
| 				}.reallocate, | ||||
| 			} | ||||
| 		}; | ||||
| 	} | ||||
|  | ||||
| @ -1,5 +1,3 @@ | ||||
| const debug = @import("./debug.zig"); | ||||
| 
 | ||||
| const io = @import("./io.zig"); | ||||
| 
 | ||||
| const math = @import("./math.zig"); | ||||
| @ -8,11 +6,6 @@ pub const DecimalFormat = struct { | ||||
| 	delimiter: []const io.Byte, | ||||
| 	positive_prefix: enum {none, plus, space}, | ||||
| 
 | ||||
| 	const default = DecimalFormat{ | ||||
| 		.delimiter = "", | ||||
| 		.positive_prefix = .none, | ||||
| 	}; | ||||
| 
 | ||||
| 	pub fn parse(self: DecimalFormat, utf8: []const io.Byte, comptime Decimal: type) ?Decimal { | ||||
| 		if (utf8.len == 0) { | ||||
| 			return null; | ||||
| @ -111,170 +104,5 @@ pub const DecimalFormat = struct { | ||||
| 			else => @compileError("`" ++ @typeName(Decimal) ++ "` cannot be formatted as a decimal string"), | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn print(self: DecimalFormat, writer: io.Writer, value: anytype) PrintError!void { | ||||
| 		if (value == 0) { | ||||
| 			return print_string(writer, switch (self.positive_prefix) { | ||||
| 				.none => "0", | ||||
| 				.plus => "+0", | ||||
| 				.space => " 0", | ||||
| 			}); | ||||
| 		} | ||||
| 
 | ||||
| 		const ValueType = @TypeOf(value); | ||||
| 
 | ||||
| 		switch (@typeInfo(ValueType)) { | ||||
| 			.Int => |int| { | ||||
| 				const radix = 10; | ||||
| 				var buffer = [_]u8{0} ** (1 + math.max(int.bits, 1)); | ||||
| 				var buffer_start = buffer.len - 1; | ||||
| 
 | ||||
| 				{ | ||||
| 					var decomposable_value = value; | ||||
| 
 | ||||
| 					while (decomposable_value != 0) : (buffer_start -= 1) { | ||||
| 						buffer[buffer_start] = @intCast((decomposable_value % radix) + '0'); | ||||
| 						decomposable_value = (decomposable_value / radix); | ||||
| 					} | ||||
| 				} | ||||
| 
 | ||||
| 				if (int.signedness == .unsigned and value < 0) { | ||||
| 					buffer[buffer_start] = '-'; | ||||
| 				} else { | ||||
| 					switch (self.positive_prefix) { | ||||
| 						.none => buffer_start += 1, | ||||
| 						.plus => buffer[buffer_start] = '+', | ||||
| 						.space => buffer[buffer_start] = ' ', | ||||
| 					} | ||||
| 				} | ||||
| 
 | ||||
| 				try print_string(writer, buffer[buffer_start ..]); | ||||
| 			}, | ||||
| 
 | ||||
| 			else => unformattableMessage(ValueType), | ||||
| 		} | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| pub const HexadecimalFormat = struct { | ||||
| 	delimiter: []const u8 = "", | ||||
| 	positive_prefix: enum {none, plus, space} = .none, | ||||
| 	casing: enum {lower, upper} = .lower, | ||||
| 
 | ||||
| 	const default = HexadecimalFormat{ | ||||
| 		.delimiter = "", | ||||
| 		.positive_prefix = .none, | ||||
| 		.casing = .lower, | ||||
| 	}; | ||||
| 
 | ||||
| 	pub fn print(self: HexadecimalFormat, writer: io.Writer, value: anytype) PrintError!void { | ||||
| 		// TODO: Implement. | ||||
| 		_ = self; | ||||
| 		_ = writer; | ||||
| 		_ = value; | ||||
| 
 | ||||
| 		unreachable; | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| pub const PrintError = error { | ||||
| 	PrintFailed, | ||||
| 	PrintIncomplete, | ||||
| }; | ||||
| 
 | ||||
| pub fn print_string(writer: io.Writer, utf8: []const io.Byte) PrintError!void { | ||||
| 	if ((writer.invoke(utf8) orelse return error.PrintFailed) != utf8.len) { | ||||
| 		return error.PrintIncomplete; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| 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_string(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_string(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_string(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"), | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| noinline fn print_value(writer: io.Writer, value: anytype) PrintError!void { | ||||
| 	const Value = @TypeOf(value); | ||||
| 
 | ||||
| 	return switch (@typeInfo(Value)) { | ||||
| 		.Int => DecimalFormat.default.print(writer, value), | ||||
| 		.Float => DecimalFormat.default.print(writer, value), | ||||
| 
 | ||||
| 		.Pointer => |pointer| switch (pointer.size) { | ||||
| 			.One, .Many, .C => HexadecimalFormat.default.print(writer, @intFromPtr(value)), | ||||
| 			.Slice => if (pointer.child == u8) print_string(writer, value) else @compileError(unformattableMessage(Value)), | ||||
| 		}, | ||||
| 
 | ||||
| 		else => @compileError(unformattableMessage(Value)), | ||||
| 	}; | ||||
| } | ||||
| 
 | ||||
| fn unformattableMessage(comptime Value: type) []const u8 { | ||||
| 	return "type `" ++ @typeName(Value) ++ "` is not formattable with this formatter"; | ||||
| } | ||||
|  | ||||
| @ -21,14 +21,14 @@ pub const Manifest = struct { | ||||
| 
 | ||||
| 		defer env.discard(title); | ||||
| 
 | ||||
| 		const title_string = try env.get_string(title); | ||||
| 		const title_string = try env.to_string(title); | ||||
| 
 | ||||
| 		const width = @as(u16, get: { | ||||
| 			const ref = try env.get_field(manifest, "width"); | ||||
| 
 | ||||
| 			defer env.discard(ref); | ||||
| 
 | ||||
| 			break: get @intFromFloat(env.get_float(ref) catch @as(f64, @floatFromInt(self.width))); | ||||
| 			break: get @intFromFloat(env.to_float(ref) catch @as(f64, @floatFromInt(self.width))); | ||||
| 		}); | ||||
| 
 | ||||
| 		const height = @as(u16, get: { | ||||
| @ -36,7 +36,7 @@ pub const Manifest = struct { | ||||
| 
 | ||||
| 			defer env.discard(ref); | ||||
| 
 | ||||
| 			break: get @intFromFloat(env.get_float(ref) catch @as(f64, @floatFromInt(self.height))); | ||||
| 			break: get @intFromFloat(env.to_float(ref) catch @as(f64, @floatFromInt(self.height))); | ||||
| 		}); | ||||
| 
 | ||||
| 		const tick_rate = @as(f32, get: { | ||||
| @ -44,7 +44,7 @@ pub const Manifest = struct { | ||||
| 
 | ||||
| 			defer env.discard(ref); | ||||
| 
 | ||||
| 			break: get @floatCast(env.get_float(ref) catch self.tick_rate); | ||||
| 			break: get @floatCast(env.to_float(ref) catch self.tick_rate); | ||||
| 		}); | ||||
| 
 | ||||
| 		{ | ||||
|  | ||||
| @ -2,8 +2,6 @@ const Ast = @import("./kym/Ast.zig"); | ||||
| 
 | ||||
| const State = @import("./kym/State.zig"); | ||||
| 
 | ||||
| const Table = @import("./kym/Table.zig"); | ||||
| 
 | ||||
| const coral = @import("coral"); | ||||
| 
 | ||||
| const file = @import("./file.zig"); | ||||
| @ -158,7 +156,7 @@ pub const ObjectInfo = struct { | ||||
| 	call: *const fn (context: CallContext) RuntimeError!*RuntimeRef = default_call, | ||||
| 	clean: *const fn (userdata: []u8) void = default_clean, | ||||
| 	get: *const fn (context: IndexContext) RuntimeError!*RuntimeRef = default_get, | ||||
| 	set: *const fn (context: IndexContext, value: *const RuntimeRef) RuntimeError!void = default_set, | ||||
| 	set: *const fn (context: IndexContext, Any: *const RuntimeRef) RuntimeError!void = default_set, | ||||
| 
 | ||||
| 	fn cast(object_info: *const anyopaque) *const ObjectInfo { | ||||
| 		return @ptrCast(@alignCast(object_info)); | ||||
| @ -221,6 +219,119 @@ pub const RuntimeEnv = struct { | ||||
| 		data: []const coral.io.Byte, | ||||
| 	}; | ||||
| 
 | ||||
| 	const Table = struct { | ||||
| 		state: *State, | ||||
| 		fields: FieldTable, | ||||
| 		array: ArrayList, | ||||
| 
 | ||||
| 		const ArrayList = coral.list.Stack(State.Variant); | ||||
| 
 | ||||
| 		const FieldTable = coral.map.Table([]const coral.io.Byte, struct { | ||||
| 			field: *State.Object, | ||||
| 			value: State.Variant, | ||||
| 
 | ||||
| 			const Self = @This(); | ||||
| 
 | ||||
| 			fn release_objects(self: Self, state: *State) void { | ||||
| 				state.release(self.field); | ||||
| 
 | ||||
| 				if (self.value == .object) { | ||||
| 					state.release(self.value.object); | ||||
| 				} | ||||
| 			} | ||||
| 		}, coral.map.string_table_traits); | ||||
| 
 | ||||
| 		fn free(self: *Table) void { | ||||
| 			{ | ||||
| 				var field_iterator = FieldTable.Iterable{.table = &self.fields}; | ||||
| 
 | ||||
| 				while (field_iterator.next()) |entry| { | ||||
| 					entry.value.release_objects(self.state); | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			self.fields.free(); | ||||
| 			self.array.free(); | ||||
| 		} | ||||
| 
 | ||||
| 		fn get_field(self: *Table, field_name: *State.Object) State.Variant { | ||||
| 			const field = self.fields.lookup(field_name.userdata) orelse return .nil; | ||||
| 
 | ||||
| 			if (field.value == .object) { | ||||
| 				return .{.object = self.state.acquire_instance(field.value.object)}; | ||||
| 			} | ||||
| 
 | ||||
| 			return field.value; | ||||
| 		} | ||||
| 
 | ||||
| 		fn get_index(self: *Table, index: usize) State.Variant { | ||||
| 			return self.array.values[index]; | ||||
| 		} | ||||
| 
 | ||||
| 		fn make(allocator: coral.io.Allocator, state: *State) Table { | ||||
| 			return .{ | ||||
| 				.state = state, | ||||
| 				.fields = FieldTable.make(allocator), | ||||
| 				.array = ArrayList.make(allocator), | ||||
| 			}; | ||||
| 		} | ||||
| 
 | ||||
| 		const object_info = ObjectInfo{ | ||||
| 			.clean = struct { | ||||
| 				fn clean(userdata: []u8) void { | ||||
| 					@as(*Table, @ptrCast(@alignCast(userdata.ptr))).free(); | ||||
| 				} | ||||
| 			}.clean, | ||||
| 
 | ||||
| 			.get = struct { | ||||
| 				fn get(context: IndexContext) RuntimeError!*RuntimeRef { | ||||
| 					const table = @as(*Table, @ptrCast(@alignCast(context.userdata.ptr))); | ||||
| 
 | ||||
| 					switch (try context.index.fetch(context.env)) { | ||||
| 						.nil => return context.env.raise(error.BadOperation, "cannot index a table with nil"), | ||||
| 						.true => return context.env.raise(error.BadOperation, "cannot index a table with true"), | ||||
| 						.false => return context.env.raise(error.BadOperation, "cannot index a table with false"), | ||||
| 
 | ||||
| 						.object => |index_object| { | ||||
| 							const value = table.get_field(index_object); | ||||
| 
 | ||||
| 							errdefer if (value == .object) { | ||||
| 								context.env.state.release(value.object); | ||||
| 							}; | ||||
| 
 | ||||
| 							return @ptrFromInt(try context.env.bound_refs.insert(value)); | ||||
| 						}, | ||||
| 
 | ||||
| 						.number => |index_number| { | ||||
| 							const value = table.get_index(@intFromFloat(index_number)); | ||||
| 
 | ||||
| 							errdefer if (value == .object) { | ||||
| 								context.env.state.release(value.object); | ||||
| 							}; | ||||
| 
 | ||||
| 							return @ptrFromInt(try context.env.bound_refs.insert(value)); | ||||
| 						}, | ||||
| 					} | ||||
| 				} | ||||
| 			}.get, | ||||
| 		}; | ||||
| 
 | ||||
| 		fn set_field(self: *Table, field_name: *State.Object, value: State.Variant) coral.io.AllocationError!void { | ||||
| 			const previous_entry = try self.fields.replace(field_name.userdata, .{ | ||||
| 				.field = field_name, | ||||
| 				.value = value, | ||||
| 			}); | ||||
| 
 | ||||
| 			if (previous_entry) |entry| { | ||||
| 				entry.value.release_objects(self.state); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		fn set_index(self: *Table, index: usize, value: State.Variant) coral.io.AllocationError!void { | ||||
| 			self.array.values[index] = value; | ||||
| 		} | ||||
| 	}; | ||||
| 
 | ||||
| 	const VariantSlab = coral.map.Slab(State.Variant); | ||||
| 
 | ||||
| 	pub fn discard(self: *RuntimeEnv, ref: *RuntimeRef) void { | ||||
| @ -247,13 +358,14 @@ pub const RuntimeEnv = struct { | ||||
| 						var popped = @as(usize, 0); | ||||
| 
 | ||||
| 						while (popped < size) : (popped += 1) { | ||||
| 							try table.set_field( | ||||
| 								try to_object(self, try self.state.pop_value()), | ||||
| 								try self.state.pop_value()); | ||||
| 							switch (try self.state.pop_value()) { | ||||
| 								.object => |field| try table.set_field(field, try self.state.pop_value()), | ||||
| 								else => return self.raise(error.BadOperation, "attempt to set a non-object field"), | ||||
| 							} | ||||
| 						} | ||||
| 					} | ||||
| 
 | ||||
| 					const table_object = try self.state.acquire_new(coral.io.bytes_of(&table), &table_info); | ||||
| 					const table_object = try self.state.acquire_new(coral.io.bytes_of(&table), &Table.object_info); | ||||
| 
 | ||||
| 					errdefer self.state.release(table_object); | ||||
| 
 | ||||
| @ -270,79 +382,58 @@ pub const RuntimeEnv = struct { | ||||
| 
 | ||||
| 				.not => { | ||||
| 					try self.state.push_value(switch (try self.state.pop_value()) { | ||||
| 						.nil => return self.raise(error.BadOperation, "cannot convert nil to true or false"), | ||||
| 						.nil => return self.raise(error.BadOperation, "cannot not nil"), | ||||
| 						.false => .true, | ||||
| 						.true => .false, | ||||
| 						.number => return self.raise(error.BadOperation, "cannot convert a number to true or false"), | ||||
| 						.object => return self.raise(error.BadOperation, "cannot convert an object to true or false"), | ||||
| 						.number => return self.raise(error.BadOperation, "cannot not a number"), | ||||
| 						.object => return self.raise(error.BadOperation, "cannot not an object"), | ||||
| 					}); | ||||
| 				}, | ||||
| 
 | ||||
| 				.neg => { | ||||
| 					try self.state.push_value(.{.number = -(try to_number(self, try self.state.pop_value()))}); | ||||
| 					try self.state.push_value(switch (try self.state.pop_value()) { | ||||
| 						.nil => return self.raise(error.BadOperation, "cannot not nil"), | ||||
| 						.false => return self.raise(error.BadOperation, "cannot not false"), | ||||
| 						.true => return self.raise(error.BadOperation, "cannot not true"), | ||||
| 						.number => |number| .{.number = -number}, | ||||
| 						.object => return self.raise(error.BadOperation, "cannot not an object"), | ||||
| 					}); | ||||
| 				}, | ||||
| 
 | ||||
| 				.add => { | ||||
| 					const lhs_number = try to_number(self, try self.state.pop_value()); | ||||
| 					const rhs_number = try to_number(self, try self.state.pop_value()); | ||||
| 
 | ||||
| 					try self.state.push_value(.{.number = lhs_number + rhs_number}); | ||||
| 				}, | ||||
| 
 | ||||
| 				.sub => { | ||||
| 					const lhs_number = try to_number(self, try self.state.pop_value()); | ||||
| 					const rhs_number = try to_number(self, try self.state.pop_value()); | ||||
| 
 | ||||
| 					try self.state.push_value(.{.number = lhs_number - rhs_number}); | ||||
| 				}, | ||||
| 
 | ||||
| 				.mul => { | ||||
| 					const lhs_number = try to_number(self, try self.state.pop_value()); | ||||
| 					const rhs_number = try to_number(self, try self.state.pop_value()); | ||||
| 
 | ||||
| 					try self.state.push_value(.{.number = lhs_number * rhs_number}); | ||||
| 				}, | ||||
| 
 | ||||
| 				.div => { | ||||
| 					const lhs_number = try to_number(self, try self.state.pop_value()); | ||||
| 					const rhs_number = try to_number(self, try self.state.pop_value()); | ||||
| 
 | ||||
| 					try self.state.push_value(.{.number = lhs_number / rhs_number}); | ||||
| 				}, | ||||
| 
 | ||||
| 				.eql => { | ||||
| 					const lhs = try self.state.pop_value(); | ||||
| 					const rhs = try self.state.pop_value(); | ||||
| 
 | ||||
| 					try self.state.push_value(if (lhs.equals(rhs)) .true else .false); | ||||
| 				}, | ||||
| 
 | ||||
| 				.cgt => { | ||||
| 					const lhs_number = try to_number(self, try self.state.pop_value()); | ||||
| 					const rhs_number = try to_number(self, try self.state.pop_value()); | ||||
| 
 | ||||
| 					try self.state.push_value(if (lhs_number > rhs_number) .true else .false); | ||||
| 				}, | ||||
| 
 | ||||
| 				.clt => { | ||||
| 					const lhs_number = try to_number(self, try self.state.pop_value()); | ||||
| 					const rhs_number = try to_number(self, try self.state.pop_value()); | ||||
| 
 | ||||
| 					try self.state.push_value(if (lhs_number < rhs_number) .true else .false); | ||||
| 				}, | ||||
| 
 | ||||
| 				.cge => { | ||||
| 					const lhs_number = try to_number(self, try self.state.pop_value()); | ||||
| 					const rhs_number = try to_number(self, try self.state.pop_value()); | ||||
| 
 | ||||
| 					try self.state.push_value(if (lhs_number >= rhs_number) .true else .false); | ||||
| 				}, | ||||
| 
 | ||||
| 				.cle => { | ||||
| 					const lhs_number = try to_number(self, try self.state.pop_value()); | ||||
| 					const rhs_number = try to_number(self, try self.state.pop_value()); | ||||
| 
 | ||||
| 					try self.state.push_value(if (lhs_number <= rhs_number) .true else .false); | ||||
| 				}, | ||||
| 			} | ||||
| 		} | ||||
| @ -404,7 +495,7 @@ pub const RuntimeEnv = struct { | ||||
| 
 | ||||
| 		defer self.discard(interned_field); | ||||
| 
 | ||||
| 		const indexable_object = try to_object(self, try indexable.fetch(self)); | ||||
| 		const indexable_object = try indexable.fetch_object(self); | ||||
| 
 | ||||
| 		return ObjectInfo.cast(indexable_object.userinfo).get(.{ | ||||
| 			.env = self, | ||||
| @ -414,20 +505,6 @@ pub const RuntimeEnv = struct { | ||||
| 		}); | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn get_float(self: *RuntimeEnv, ref: *const RuntimeRef) RuntimeError!State.Float { | ||||
| 		return to_number(self, try ref.fetch(self)); | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn get_string(self: *RuntimeEnv, ref: *const RuntimeRef) RuntimeError![]const u8 { | ||||
| 		const object = try to_object(self, try ref.fetch(self)); | ||||
| 
 | ||||
| 		if (ObjectInfo.cast(object.userinfo) != &string_info) { | ||||
| 			return self.raise(error.BadOperation, "object is not a string"); | ||||
| 		} | ||||
| 
 | ||||
| 		return object.userdata; | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn intern(self: *RuntimeEnv, data: []const u8) RuntimeError!*RuntimeRef { | ||||
| 		const data_object = try self.state.acquire_interned(data, &string_info); | ||||
| 
 | ||||
| @ -455,16 +532,34 @@ pub const RuntimeEnv = struct { | ||||
| 		return @ptrFromInt(try self.bound_refs.insert(.{.object = data_object})); | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn nil(self: *RuntimeEnv) RuntimeError!*RuntimeRef { | ||||
| 		return @ptrFromInt(try self.bound_refs.insert(.nil)); | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn raise(self: *RuntimeEnv, runtime_error: RuntimeError, error_message: []const u8) RuntimeError { | ||||
| 		// TODO: Print stack trace from state. | ||||
| 		coral.utf8.print_formatted(self.err_writer, "{name}@{line}: {message}", .{ | ||||
| 			.name = "???", | ||||
| 			.line = @as(u64, 0), | ||||
| 			.message = error_message, | ||||
| 		}) catch return error.SystemFailure; | ||||
| 		if (self.err_writer.invoke(error_message) == null) { | ||||
| 			return error.SystemFailure; | ||||
| 		} | ||||
| 
 | ||||
| 		return runtime_error; | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn to_float(self: *RuntimeEnv, ref: *const RuntimeRef) RuntimeError!State.Float { | ||||
| 		return ref.fetch_number(self); | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn to_string(self: *RuntimeEnv, ref: *const RuntimeRef) RuntimeError![]const u8 { | ||||
| 		const object = try switch (try ref.fetch(self)) { | ||||
| 			.object => |object| object, | ||||
| 			else => self.raise(error.BadOperation, "cannot convert to object"), | ||||
| 		}; | ||||
| 
 | ||||
| 		if (ObjectInfo.cast(object.userinfo) != &string_info) { | ||||
| 			return self.raise(error.BadOperation, "object is not a string"); | ||||
| 		} | ||||
| 
 | ||||
| 		return object.userdata; | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| pub const RuntimeError = coral.io.AllocationError || State.PopError || error { | ||||
| @ -477,101 +572,27 @@ pub const RuntimeRef = opaque { | ||||
| 	fn fetch(self: *const RuntimeRef, env: *RuntimeEnv) RuntimeError!State.Variant { | ||||
| 		return env.bound_refs.lookup(@intFromPtr(self)) orelse env.raise(error.BadOperation, "stale ref"); | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| fn table_clean(userdata: []u8) void { | ||||
| 	@as(*Table, @ptrCast(@alignCast(userdata.ptr))).free(); | ||||
| } | ||||
| 
 | ||||
| fn table_get(context: IndexContext) RuntimeError!*RuntimeRef { | ||||
| 	const table = @as(*Table, @ptrCast(@alignCast(context.userdata.ptr))); | ||||
| 
 | ||||
| 	switch (try context.index.fetch(context.env)) { | ||||
| 		.nil => return context.env.raise(error.BadOperation, "cannot index a table with nil"), | ||||
| 		.true => return context.env.raise(error.BadOperation, "cannot index a table with true"), | ||||
| 		.false => return context.env.raise(error.BadOperation, "cannot index a table with false"), | ||||
| 
 | ||||
| 		.object => |index_object| { | ||||
| 			const value = table.get_field(index_object); | ||||
| 
 | ||||
| 			errdefer if (value == .object) { | ||||
| 				context.env.state.release(value.object); | ||||
| 			}; | ||||
| 
 | ||||
| 			return @ptrFromInt(try context.env.bound_refs.insert(value)); | ||||
| 		}, | ||||
| 
 | ||||
| 		.number => |index_number| { | ||||
| 			const value = table.get_index(@intFromFloat(index_number)); | ||||
| 
 | ||||
| 			errdefer if (value == .object) { | ||||
| 				context.env.state.release(value.object); | ||||
| 			}; | ||||
| 
 | ||||
| 			return @ptrFromInt(try context.env.bound_refs.insert(value)); | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| const table_info = ObjectInfo{ | ||||
| 	.clean = table_clean, | ||||
| 	.get = table_get, | ||||
| 	.set = table_set, | ||||
| }; | ||||
| 
 | ||||
| fn table_set(context: IndexContext, value: *const RuntimeRef) RuntimeError!void { | ||||
| 	const table = @as(*Table, @ptrCast(@alignCast(context.userdata.ptr))); | ||||
| 
 | ||||
| 	switch (try context.index.fetch(context.env)) { | ||||
| 		.nil => return context.env.raise(error.BadOperation, "cannot index a table with nil"), | ||||
| 		.true => return context.env.raise(error.BadOperation, "cannot index a table with true"), | ||||
| 		.false => return context.env.raise(error.BadOperation, "cannot index a table with false"), | ||||
| 
 | ||||
| 		.object => |index_object| { | ||||
| 			const fetched_value = try value.fetch(context.env); | ||||
| 
 | ||||
| 			if (fetched_value == .object) { | ||||
| 				try table.set_field(index_object, .{ | ||||
| 					.object = context.env.state.acquire_instance(fetched_value.object), | ||||
| 				}); | ||||
| 			} else { | ||||
| 				try table.set_field(index_object, fetched_value); | ||||
| 			} | ||||
| 		}, | ||||
| 
 | ||||
| 		.number => |index_number| { | ||||
| 			const fetched_value = try value.fetch(context.env); | ||||
| 
 | ||||
| 			if (fetched_value == .object) { | ||||
| 				try table.set_index(@intFromFloat(index_number), .{ | ||||
| 					.object = context.env.state.acquire_instance(fetched_value.object), | ||||
| 				}); | ||||
| 			} else { | ||||
| 				try table.set_index(@intFromFloat(index_number), fetched_value); | ||||
| 			} | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| fn to_number(env: *RuntimeEnv, variant: State.Variant) RuntimeError!State.Float { | ||||
| 	return switch (variant) { | ||||
| 	fn fetch_number(self: *const RuntimeRef, env: *RuntimeEnv) RuntimeError!State.Float { | ||||
| 		return switch (try self.fetch(env)) { | ||||
| 			.nil => env.raise(error.BadOperation, "cannot convert nil to number"), | ||||
| 			.true => env.raise(error.BadOperation, "cannot convert true to number"), | ||||
| 			.false => env.raise(error.BadOperation, "cannot convert false to number"), | ||||
| 			.number => |number| number, | ||||
| 			.object => env.raise(error.BadOperation, "cannot convert object to number"), | ||||
| 		}; | ||||
| } | ||||
| 	} | ||||
| 
 | ||||
| fn to_object(env: *RuntimeEnv, variant: State.Variant) RuntimeError!*State.Object { | ||||
| 	return switch (variant) { | ||||
| 	fn fetch_object(self: *const RuntimeRef, env: *RuntimeEnv) RuntimeError!*State.Object { | ||||
| 		return switch (try self.fetch(env)) { | ||||
| 			.nil => env.raise(error.BadOperation, "cannot convert nil to object"), | ||||
| 			.true => env.raise(error.BadOperation, "cannot convert true to object"), | ||||
| 			.false => env.raise(error.BadOperation, "cannot convert false to object"), | ||||
| 			.number => env.raise(error.BadOperation, "cannot convert number to object"), | ||||
| 			.object => |object| object, | ||||
| 		}; | ||||
| } | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| const string_info = ObjectInfo{ | ||||
| 
 | ||||
|  | ||||
| @ -36,24 +36,6 @@ pub const Variant = union (enum) { | ||||
| 	false, | ||||
| 	number: Float, | ||||
| 	object: *Object, | ||||
| 
 | ||||
| 	pub fn equals(self: Variant, other: Variant) bool { | ||||
| 		return switch (self) { | ||||
| 			.nil => other == .nil, | ||||
| 			.true => other == .true, | ||||
| 			.false => other == .false, | ||||
| 
 | ||||
| 			.number => |number| switch (other) { | ||||
| 				.number => |other_number| number == other_number, | ||||
| 				else => false, | ||||
| 			}, | ||||
| 
 | ||||
| 			.object => |object| switch (other) { | ||||
| 				.object => |other_object| object == other_object, | ||||
| 				else => false, | ||||
| 			}, | ||||
| 		}; | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| pub fn acquire_instance(_: *Self, object: *Object) *Object { | ||||
|  | ||||
| @ -1,76 +0,0 @@ | ||||
| const State = @import("./State.zig"); | ||||
| 
 | ||||
| const coral = @import("coral"); | ||||
| 
 | ||||
| state: *State, | ||||
| fields: FieldTable, | ||||
| array: ArrayList, | ||||
| 
 | ||||
| const ArrayList = coral.list.Stack(State.Variant); | ||||
| 
 | ||||
| const Field = struct { | ||||
| 	field: *State.Object, | ||||
| 	value: State.Variant, | ||||
| 
 | ||||
| 	fn release_objects(self: Field, state: *State) void { | ||||
| 		state.release(self.field); | ||||
| 
 | ||||
| 		if (self.value == .object) { | ||||
| 			state.release(self.value.object); | ||||
| 		} | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| const FieldTable = coral.map.Table([]const coral.io.Byte, Field, coral.map.string_table_traits); | ||||
| 
 | ||||
| const Self = @This(); | ||||
| 
 | ||||
| pub fn free(self: *Self) void { | ||||
| 	{ | ||||
| 		var field_iterator = FieldTable.Iterable{.table = &self.fields}; | ||||
| 
 | ||||
| 		while (field_iterator.next()) |entry| { | ||||
| 			entry.value.release_objects(self.state); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	self.fields.free(); | ||||
| 	self.array.free(); | ||||
| } | ||||
| 
 | ||||
| pub fn get_field(self: *Self, field_name: *State.Object) State.Variant { | ||||
| 	const field = self.fields.lookup(field_name.userdata) orelse return .nil; | ||||
| 
 | ||||
| 	if (field.value == .object) { | ||||
| 		return .{.object = self.state.acquire_instance(field.value.object)}; | ||||
| 	} | ||||
| 
 | ||||
| 	return field.value; | ||||
| } | ||||
| 
 | ||||
| pub fn get_index(self: *Self, index: usize) State.Variant { | ||||
| 	return self.array.values[index]; | ||||
| } | ||||
| 
 | ||||
| pub fn make(allocator: coral.io.Allocator, state: *State) Self { | ||||
| 	return .{ | ||||
| 		.state = state, | ||||
| 		.fields = FieldTable.make(allocator), | ||||
| 		.array = ArrayList.make(allocator), | ||||
| 	}; | ||||
| } | ||||
| 
 | ||||
| pub fn set_field(self: *Self, field_name: *State.Object, value: State.Variant) coral.io.AllocationError!void { | ||||
| 	const previous_entry = try self.fields.replace(field_name.userdata, .{ | ||||
| 		.field = field_name, | ||||
| 		.value = value, | ||||
| 	}); | ||||
| 
 | ||||
| 	if (previous_entry) |entry| { | ||||
| 		entry.value.release_objects(self.state); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| pub fn set_index(self: *Self, index: usize, value: State.Variant) coral.io.AllocationError!void { | ||||
| 	self.array.values[index] = value; | ||||
| } | ||||
| @ -1,3 +0,0 @@ | ||||
| const coral = @import("coral"); | ||||
| 
 | ||||
| const ona = @import("ona"); | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user