From 8d361634d73166be529d59b1d0037849d1dcf634 Mon Sep 17 00:00:00 2001 From: kayomn Date: Mon, 10 Jul 2023 23:06:04 +0100 Subject: [PATCH] Tidy up Kym API --- source/ona/app.zig | 8 +- source/ona/kym.zig | 318 ++++++++++++++++++--------------------- source/ona/kym/State.zig | 18 +++ source/ona/kym/Table.zig | 76 ++++++++++ 4 files changed, 245 insertions(+), 175 deletions(-) create mode 100644 source/ona/kym/Table.zig diff --git a/source/ona/app.zig b/source/ona/app.zig index a48727b..22ea8b8 100644 --- a/source/ona/app.zig +++ b/source/ona/app.zig @@ -21,14 +21,14 @@ pub const Manifest = struct { defer env.discard(title); - const title_string = try env.to_string(title); + const title_string = try env.get_string(title); const width = @as(u16, get: { const ref = try env.get_field(manifest, "width"); defer env.discard(ref); - break: get @intFromFloat(env.to_float(ref) catch @as(f64, @floatFromInt(self.width))); + break: get @intFromFloat(env.get_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.to_float(ref) catch @as(f64, @floatFromInt(self.height))); + break: get @intFromFloat(env.get_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.to_float(ref) catch self.tick_rate); + break: get @floatCast(env.get_float(ref) catch self.tick_rate); }); { diff --git a/source/ona/kym.zig b/source/ona/kym.zig index c86b4b6..353fb6a 100644 --- a/source/ona/kym.zig +++ b/source/ona/kym.zig @@ -2,6 +2,8 @@ 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"); @@ -156,7 +158,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, Any: *const RuntimeRef) RuntimeError!void = default_set, + set: *const fn (context: IndexContext, value: *const RuntimeRef) RuntimeError!void = default_set, fn cast(object_info: *const anyopaque) *const ObjectInfo { return @ptrCast(@alignCast(object_info)); @@ -219,119 +221,6 @@ 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 { @@ -358,14 +247,13 @@ pub const RuntimeEnv = struct { var popped = @as(usize, 0); while (popped < size) : (popped += 1) { - 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"), - } + try table.set_field( + try to_object(self, try self.state.pop_value()), + try self.state.pop_value()); } } - const table_object = try self.state.acquire_new(coral.io.bytes_of(&table), &Table.object_info); + const table_object = try self.state.acquire_new(coral.io.bytes_of(&table), &table_info); errdefer self.state.release(table_object); @@ -382,58 +270,79 @@ pub const RuntimeEnv = struct { .not => { try self.state.push_value(switch (try self.state.pop_value()) { - .nil => return self.raise(error.BadOperation, "cannot not nil"), + .nil => return self.raise(error.BadOperation, "cannot convert nil to true or false"), .false => .true, .true => .false, - .number => return self.raise(error.BadOperation, "cannot not a number"), - .object => return self.raise(error.BadOperation, "cannot not an object"), + .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"), }); }, .neg => { - 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"), - }); + try self.state.push_value(.{.number = -(try to_number(self, try self.state.pop_value()))}); }, .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); }, } } @@ -495,7 +404,7 @@ pub const RuntimeEnv = struct { defer self.discard(interned_field); - const indexable_object = try indexable.fetch_object(self); + const indexable_object = try to_object(self, try indexable.fetch(self)); return ObjectInfo.cast(indexable_object.userinfo).get(.{ .env = self, @@ -505,6 +414,20 @@ 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); @@ -532,10 +455,6 @@ 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 { if (self.err_writer.invoke(error_message) == null) { return error.SystemFailure; @@ -543,23 +462,6 @@ pub const RuntimeEnv = struct { 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 { @@ -572,28 +474,102 @@ 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 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 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, - }; - } }; +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) { + .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) { + .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{ }; diff --git a/source/ona/kym/State.zig b/source/ona/kym/State.zig index 6a2c6c6..ef98d59 100644 --- a/source/ona/kym/State.zig +++ b/source/ona/kym/State.zig @@ -36,6 +36,24 @@ 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 { diff --git a/source/ona/kym/Table.zig b/source/ona/kym/Table.zig new file mode 100644 index 0000000..57f1d0d --- /dev/null +++ b/source/ona/kym/Table.zig @@ -0,0 +1,76 @@ +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; +}