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: {
|
b.installArtifact(create: {
|
||||||
const compile_step = b.addExecutable(.{
|
const runner_exe = b.addExecutable(.{
|
||||||
.name = "runner",
|
.name = "runner",
|
||||||
.root_source_file = .{ .path = "source/runner.zig" },
|
.root_source_file = .{ .path = "source/runner.zig" },
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
});
|
});
|
||||||
|
|
||||||
compile_step.addModule("ona", ona_module);
|
runner_exe.addModule("ona", ona_module);
|
||||||
compile_step.linkLibC();
|
runner_exe.linkLibC();
|
||||||
compile_step.linkSystemLibrary("SDL2");
|
runner_exe.linkSystemLibrary("SDL2");
|
||||||
|
|
||||||
break: create compile_step;
|
break: create runner_exe;
|
||||||
});
|
|
||||||
|
|
||||||
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;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,11 @@ pub const Allocator = struct {
|
||||||
pub fn bind(comptime State: type, state: *State, comptime actions: Actions(State)) Allocator {
|
pub fn bind(comptime State: type, state: *State, comptime actions: Actions(State)) Allocator {
|
||||||
const is_zero_aligned = @alignOf(State) == 0;
|
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 {
|
fn deallocate(context: *anyopaque, allocation: []Byte) void {
|
||||||
if (is_zero_aligned) {
|
if (is_zero_aligned) {
|
||||||
return actions.deallocator(@ptrCast(context), allocation);
|
return actions.deallocator(@ptrCast(context), allocation);
|
||||||
|
@ -32,7 +36,9 @@ pub const Allocator = struct {
|
||||||
|
|
||||||
return actions.deallocate(@ptrCast(@alignCast(context)), allocation);
|
return actions.deallocate(@ptrCast(@alignCast(context)), allocation);
|
||||||
}
|
}
|
||||||
|
}.deallocate,
|
||||||
|
|
||||||
|
.reallocate = struct {
|
||||||
fn reallocate(context: *anyopaque, return_address: usize, existing_allocation: ?[]Byte, size: usize) AllocationError![]Byte {
|
fn reallocate(context: *anyopaque, return_address: usize, existing_allocation: ?[]Byte, size: usize) AllocationError![]Byte {
|
||||||
if (is_zero_aligned) {
|
if (is_zero_aligned) {
|
||||||
return actions.reallocator(@ptrCast(context), return_address, existing_allocation, size);
|
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 actions.reallocate(@ptrCast(@alignCast(context)), return_address, existing_allocation, size);
|
||||||
}
|
}
|
||||||
};
|
}.reallocate,
|
||||||
|
|
||||||
return .{
|
|
||||||
.context = if (is_zero_aligned) state else @ptrCast(state),
|
|
||||||
|
|
||||||
.actions = &.{
|
|
||||||
.deallocate = ErasedActions.deallocate,
|
|
||||||
.reallocate = ErasedActions.reallocate,
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
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");
|
||||||
|
@ -8,11 +6,6 @@ pub const DecimalFormat = struct {
|
||||||
delimiter: []const io.Byte,
|
delimiter: []const io.Byte,
|
||||||
positive_prefix: enum {none, plus, space},
|
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 {
|
pub fn parse(self: DecimalFormat, utf8: []const io.Byte, comptime Decimal: type) ?Decimal {
|
||||||
if (utf8.len == 0) {
|
if (utf8.len == 0) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -111,170 +104,5 @@ pub const DecimalFormat = struct {
|
||||||
else => @compileError("`" ++ @typeName(Decimal) ++ "` cannot be formatted as a decimal string"),
|
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);
|
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 width = @as(u16, get: {
|
||||||
const ref = try env.get_field(manifest, "width");
|
const ref = try env.get_field(manifest, "width");
|
||||||
|
|
||||||
defer env.discard(ref);
|
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: {
|
const height = @as(u16, get: {
|
||||||
|
@ -36,7 +36,7 @@ pub const Manifest = struct {
|
||||||
|
|
||||||
defer env.discard(ref);
|
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: {
|
const tick_rate = @as(f32, get: {
|
||||||
|
@ -44,7 +44,7 @@ pub const Manifest = struct {
|
||||||
|
|
||||||
defer env.discard(ref);
|
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 State = @import("./kym/State.zig");
|
||||||
|
|
||||||
const Table = @import("./kym/Table.zig");
|
|
||||||
|
|
||||||
const coral = @import("coral");
|
const coral = @import("coral");
|
||||||
|
|
||||||
const file = @import("./file.zig");
|
const file = @import("./file.zig");
|
||||||
|
@ -158,7 +156,7 @@ pub const ObjectInfo = struct {
|
||||||
call: *const fn (context: CallContext) RuntimeError!*RuntimeRef = default_call,
|
call: *const fn (context: CallContext) RuntimeError!*RuntimeRef = default_call,
|
||||||
clean: *const fn (userdata: []u8) void = default_clean,
|
clean: *const fn (userdata: []u8) void = default_clean,
|
||||||
get: *const fn (context: IndexContext) RuntimeError!*RuntimeRef = default_get,
|
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 {
|
fn cast(object_info: *const anyopaque) *const ObjectInfo {
|
||||||
return @ptrCast(@alignCast(object_info));
|
return @ptrCast(@alignCast(object_info));
|
||||||
|
@ -221,6 +219,119 @@ pub const RuntimeEnv = struct {
|
||||||
data: []const coral.io.Byte,
|
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);
|
const VariantSlab = coral.map.Slab(State.Variant);
|
||||||
|
|
||||||
pub fn discard(self: *RuntimeEnv, ref: *RuntimeRef) void {
|
pub fn discard(self: *RuntimeEnv, ref: *RuntimeRef) void {
|
||||||
|
@ -247,13 +358,14 @@ pub const RuntimeEnv = struct {
|
||||||
var popped = @as(usize, 0);
|
var popped = @as(usize, 0);
|
||||||
|
|
||||||
while (popped < size) : (popped += 1) {
|
while (popped < size) : (popped += 1) {
|
||||||
try table.set_field(
|
switch (try self.state.pop_value()) {
|
||||||
try to_object(self, try self.state.pop_value()),
|
.object => |field| try table.set_field(field, try self.state.pop_value()),
|
||||||
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);
|
errdefer self.state.release(table_object);
|
||||||
|
|
||||||
|
@ -270,79 +382,58 @@ pub const RuntimeEnv = struct {
|
||||||
|
|
||||||
.not => {
|
.not => {
|
||||||
try self.state.push_value(switch (try self.state.pop_value()) {
|
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,
|
.false => .true,
|
||||||
.true => .false,
|
.true => .false,
|
||||||
.number => return self.raise(error.BadOperation, "cannot convert a number to true or false"),
|
.number => return self.raise(error.BadOperation, "cannot not a number"),
|
||||||
.object => return self.raise(error.BadOperation, "cannot convert an object to true or false"),
|
.object => return self.raise(error.BadOperation, "cannot not an object"),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
.neg => {
|
.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 => {
|
.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 => {
|
.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 => {
|
.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 => {
|
.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 => {
|
.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 => {
|
.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 => {
|
.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 => {
|
.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 => {
|
.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);
|
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(.{
|
return ObjectInfo.cast(indexable_object.userinfo).get(.{
|
||||||
.env = self,
|
.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 {
|
pub fn intern(self: *RuntimeEnv, data: []const u8) RuntimeError!*RuntimeRef {
|
||||||
const data_object = try self.state.acquire_interned(data, &string_info);
|
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}));
|
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 {
|
pub fn raise(self: *RuntimeEnv, runtime_error: RuntimeError, error_message: []const u8) RuntimeError {
|
||||||
// TODO: Print stack trace from state.
|
if (self.err_writer.invoke(error_message) == null) {
|
||||||
coral.utf8.print_formatted(self.err_writer, "{name}@{line}: {message}", .{
|
return error.SystemFailure;
|
||||||
.name = "???",
|
}
|
||||||
.line = @as(u64, 0),
|
|
||||||
.message = error_message,
|
|
||||||
}) catch return error.SystemFailure;
|
|
||||||
|
|
||||||
return runtime_error;
|
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 {
|
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 {
|
fn fetch(self: *const RuntimeRef, env: *RuntimeEnv) RuntimeError!State.Variant {
|
||||||
return env.bound_refs.lookup(@intFromPtr(self)) orelse env.raise(error.BadOperation, "stale ref");
|
return env.bound_refs.lookup(@intFromPtr(self)) orelse env.raise(error.BadOperation, "stale ref");
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
fn table_clean(userdata: []u8) void {
|
fn fetch_number(self: *const RuntimeRef, env: *RuntimeEnv) RuntimeError!State.Float {
|
||||||
@as(*Table, @ptrCast(@alignCast(userdata.ptr))).free();
|
return switch (try self.fetch(env)) {
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
.nil => env.raise(error.BadOperation, "cannot convert nil to number"),
|
.nil => env.raise(error.BadOperation, "cannot convert nil to number"),
|
||||||
.true => env.raise(error.BadOperation, "cannot convert true to number"),
|
.true => env.raise(error.BadOperation, "cannot convert true to number"),
|
||||||
.false => env.raise(error.BadOperation, "cannot convert false to number"),
|
.false => env.raise(error.BadOperation, "cannot convert false to number"),
|
||||||
.number => |number| number,
|
.number => |number| number,
|
||||||
.object => env.raise(error.BadOperation, "cannot convert object to number"),
|
.object => env.raise(error.BadOperation, "cannot convert object to number"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_object(env: *RuntimeEnv, variant: State.Variant) RuntimeError!*State.Object {
|
fn fetch_object(self: *const RuntimeRef, env: *RuntimeEnv) RuntimeError!*State.Object {
|
||||||
return switch (variant) {
|
return switch (try self.fetch(env)) {
|
||||||
.nil => env.raise(error.BadOperation, "cannot convert nil to object"),
|
.nil => env.raise(error.BadOperation, "cannot convert nil to object"),
|
||||||
.true => env.raise(error.BadOperation, "cannot convert true to object"),
|
.true => env.raise(error.BadOperation, "cannot convert true to object"),
|
||||||
.false => env.raise(error.BadOperation, "cannot convert false to object"),
|
.false => env.raise(error.BadOperation, "cannot convert false to object"),
|
||||||
.number => env.raise(error.BadOperation, "cannot convert number to object"),
|
.number => env.raise(error.BadOperation, "cannot convert number to object"),
|
||||||
.object => |object| object,
|
.object => |object| object,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const string_info = ObjectInfo{
|
const string_info = ObjectInfo{
|
||||||
|
|
||||||
|
|
|
@ -36,24 +36,6 @@ pub const Variant = union (enum) {
|
||||||
false,
|
false,
|
||||||
number: Float,
|
number: Float,
|
||||||
object: *Object,
|
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 {
|
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…
Reference in New Issue