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