From bb8cb43843a07093460f698b208195057a37d325 Mon Sep 17 00:00:00 2001 From: kayomn Date: Wed, 24 May 2023 00:33:02 +0000 Subject: [PATCH] Refactor project structure and Ona APIs --- build.zig | 14 +- source/kym/kym.zig | 1 - source/kym/vm.zig | 335 ------------------------ source/ona/file.zig | 184 +++++++++++++ source/ona/files.zig | 201 -------------- source/ona/heap.zig | 46 ++++ source/ona/kym.zig | 5 + source/ona/kym/Environment.zig | 420 ++++++++++++++++++++++++++++++ source/{ => ona}/kym/bytecode.zig | 276 +++++++------------- source/{ => ona}/kym/tokens.zig | 0 source/ona/kym/types.zig | 56 ++++ source/ona/ona.zig | 257 ++++++++---------- source/runner.zig | 116 +-------- 13 files changed, 917 insertions(+), 994 deletions(-) delete mode 100755 source/kym/kym.zig delete mode 100755 source/kym/vm.zig create mode 100644 source/ona/file.zig delete mode 100755 source/ona/files.zig create mode 100644 source/ona/heap.zig create mode 100755 source/ona/kym.zig create mode 100644 source/ona/kym/Environment.zig rename source/{ => ona}/kym/bytecode.zig (63%) rename source/{ => ona}/kym/tokens.zig (100%) create mode 100644 source/ona/kym/types.zig diff --git a/build.zig b/build.zig index d1c7c14..fa792d2 100755 --- a/build.zig +++ b/build.zig @@ -9,18 +9,7 @@ pub fn build(builder: *std.Build) void { .dependencies = &.{ .{ .name = "coral", - .module = coral_module - }, - }, - }); - - const kym_module = builder.createModule(.{ - .source_file = .{.path = "./source/kym/kym.zig"}, - - .dependencies = &.{ - .{ - .name = "coral", - .module = coral_module + .module = coral_module, }, }, }); @@ -36,7 +25,6 @@ pub fn build(builder: *std.Build) void { ona_exe.addModule("coral", coral_module); ona_exe.addModule("ona", ona_module); - ona_exe.addModule("kym", kym_module); // ona_exe.addIncludeDir("./ext"); ona_exe.linkSystemLibrary("SDL2"); diff --git a/source/kym/kym.zig b/source/kym/kym.zig deleted file mode 100755 index 8f9f30c..0000000 --- a/source/kym/kym.zig +++ /dev/null @@ -1 +0,0 @@ -pub const vm = @import("./vm.zig"); diff --git a/source/kym/vm.zig b/source/kym/vm.zig deleted file mode 100755 index 796d6b0..0000000 --- a/source/kym/vm.zig +++ /dev/null @@ -1,335 +0,0 @@ -const coral = @import("coral"); - -pub const CompileError = coral.io.AllocationError || error { - -}; - -pub const Environment = struct { - allocator: coral.io.Allocator, - globals: ValueTable, - stack: coral.stack.Dense(Value), - calls: coral.stack.Dense(CallFrame), - - const CallFrame = struct { - object: ?*Object, - ip: usize, - slots: []Value, - }; - - pub const InitOptions = struct { - stack_max: u32, - calls_max: u32, - }; - - pub const NewOptions = struct { - userdata: []const u8 = &.{}, - identity: ?*const anyopaque = null, - behavior: *const Object.Behavior = &.{}, - }; - - pub const NewScriptOptions = struct { - name: []const u8, - data: []const u8, - }; - - pub fn call(self: *Environment, object: *Object, arguments: []const Value) RuntimeError!Value { - var global_object = Object{ - .ref_count = 0, - .identity = &self.globals, - .userdata = &.{}, - .fields = self.globals, - .behavior = &.{}, - }; - - return object.behavior.caller(.{ - .env = self, - .args = arguments, - .caller = &global_object, - .callee = object, - }); - } - - pub fn check(self: Environment, condition: bool, failure_message: []const u8) RuntimeError!void { - if (condition) return; - - // TODO: Emit failure message. - _ = self; - _ = failure_message; - - return error.CheckFailure; - } - - pub fn deinit(self: *Environment) void { - self.stack.deinit(self.allocator); - self.calls.deinit(self.allocator); - } - - pub fn global_set(self: *Environment, global_name: []const u8, value: Value) coral.io.AllocationError!void { - try self.globals.assign(self.allocator, global_name, value); - } - - pub fn init(allocator: coral.io.Allocator, options: InitOptions) !Environment { - var environment = Environment{ - .allocator = allocator, - .globals = .{}, - .stack = .{}, - .calls = .{}, - }; - - errdefer { - environment.stack.deinit(allocator); - environment.calls.deinit(allocator); - } - - try environment.stack.grow(allocator, options.stack_max); - try environment.calls.grow(allocator, options.calls_max); - - return environment; - } - - pub fn new(self: *Environment, options: NewOptions) coral.io.AllocationError!*Object { - const object = try coral.io.allocate_one(Object, self.allocator); - - errdefer coral.io.deallocate(self.allocator, object); - - const userdata = try coral.io.allocate_many(u8, options.userdata.len, self.allocator); - - errdefer coral.io.deallocate(self.allocator, userdata); - - coral.io.copy(userdata, options.userdata); - - object.* = .{ - .userdata = userdata, - .ref_count = 1, - .identity = options.identity orelse userdata.ptr, - .behavior = options.behavior, - .fields = .{}, - }; - - return object; - } - - pub fn new_array(self: *Environment) coral.io.AllocationError!*Object { - // TODO: Implement. - return self.new(.none, .{}); - } - - pub fn new_script(self: *Environment, options: NewScriptOptions) coral.io.AllocationError!*Object { - // TODO: Implement. - _ = self; - _ = options; - - return error.OutOfMemory; - } - - pub fn new_string(self: *Environment, string_data: []const u8) coral.io.AllocationError!*Object { - const object = try self.new(.{ - .userdata = string_data, - .behavior = &.{}, - }); - - errdefer self.release(object); - - return object; - } - - pub fn new_string_from(self: *Environment, formats: []const coral.format.Value) coral.io.AllocationError!*Object { - // TODO: Implement. - coral.format.print(coral.io.null_writer, formats); - - return self.new_string(""); - } - - pub fn new_string_from_integer(self: *Environment, integer: Integer) coral.io.AllocationError!*Object { - // TODO: Implement. - _ = integer; - - return self.new_string("0"); - } - - pub fn new_table(self: *Environment) coral.io.AllocationError!*Object { - // TODO: Implement. - return self.new(.none, .{}); - } - - pub fn raw_get(_: Environment, object: *Object, field_name: []const u8) Value { - return object.fields.lookup(field_name) orelse .nil; - } - - pub fn raw_set(environment: Environment, object: *Object, field_name: []const u8, value: Value) coral.io.AllocationError!void { - try object.fields.assign(environment.allocator, field_name, value); - } - - pub fn release(self: Environment, object: *Object) void { - coral.debug.assert(object.ref_count != 0); - - object.ref_count -= 1; - - if (object.ref_count == 0) { - coral.io.deallocate(self.allocator, object.userdata); - coral.io.deallocate(self.allocator, object); - } - } - - pub fn user_cast(self: *Environment, object: *Object, expected_identity: *const anyopaque, comptime CastTarget: type) RuntimeError!*CastTarget { - // TODO: Emit failure message. - _ = self; - - if (object.identity != expected_identity) { - // Identity does not match what was expected. - return error.InvalidOperation; - } - - if (object.userdata.len != @sizeOf(CastTarget)) { - // Userdata size does not match target type. - return error.InvalidOperation; - } - - return @ptrCast(*CastTarget, @alignCast(@alignOf(CastTarget), object.userdata)); - } - - pub fn virtual_call(self: *Environment, object: *Object, index: Value, arguments: []const Value) RuntimeError!Value { - const value = try self.virtual_get(object, index); - - switch (value) { - .object => |callee| { - defer self.release(callee); - - return callee.behavior.caller(.{ - .environment = self, - .object = callee, - .caller = object, - .arguments = arguments, - }); - }, - - else => return error.InvalidOperation, - } - } - - pub fn virtual_get(self: *Environment, object: *Object, index: Value) RuntimeError!Value { - return object.behavior.getter(.{ - .environment = self, - .object = object, - .index = index, - }); - } - - pub fn virtual_set(self: *Environment, object: *Object, index: Value, value: Value) RuntimeError!void { - return object.behavior.setter(.{ - .environment = self, - .object = object, - .index = index, - .value = value, - }); - } -}; - -pub const Float = f32; - -pub const Integer = i32; - -pub const Object = struct { - ref_count: usize, - identity: *const anyopaque, - userdata: []u8, - behavior: *const Behavior, - fields: ValueTable, - - pub const Behavior = struct { - caller: *const Caller = default_call, - deinitialize: *const Deinitializer = default_deinitialize, - getter: *const Getter = default_get, - setter: *const Setter = default_set, - - fn default_call(_: CallContext) RuntimeError!Value { - return error.InvalidOperation; - } - - fn default_deinitialize(_: DeinitializeContext) void { - // Nothing to deinitialize by default. - } - - fn default_get(context: GetContext) RuntimeError!Value { - switch (context.index) { - .object => |index| { - if (!index.is_string()) return error.InvalidOperation; - - return context.obj.fields.lookup(index.userdata) orelse .nil; - }, - - .integer => |integer| { - const index = context.env.new_string_from(&.{.{.signed = integer}}); - - defer context.env.release(index); - - return context.obj.fields.lookup(index.userdata) orelse .nil; - }, - - else => return error.InvalidOperation, - } - } - - fn default_set(_: SetContext) RuntimeError!void { - return error.InvalidOperation; - } - }; - - pub const CallContext = struct { - env: *Environment, - caller: *Object, - callee: *Object, - args: []const Value, - }; - - pub const Caller = fn (context: CallContext) RuntimeError!Value; - - pub const DeinitializeContext = struct { - env: *Environment, - obj: *Object, - }; - - pub const Deinitializer = fn (context: DeinitializeContext) void; - - pub const GetContext = struct { - env: *Environment, - obj: *const Object, - index: Value, - }; - - pub const Getter = fn (context: GetContext) RuntimeError!Value; - - pub const SetContext = struct { - env: *Environment, - obj: *Object, - index: Value, - value: Value, - }; - - pub const Setter = fn (context: SetContext) RuntimeError!void; - - pub fn as_value(self: *Object) Value { - return .{.object = self}; - } - - pub fn is_string(self: Object) bool { - // Userdata represents a string (of bytes) if it's identity is it's userdata. - return self.identity == @ptrCast(*const anyopaque, self.userdata.ptr); - } -}; - -pub const RuntimeError = coral.io.AllocationError || error { - InvalidOperation, - CheckFailure, -}; - -pub const Value = union(enum) { - nil, - false, - true, - float: Float, - integer: Integer, - object: *Object, -}; - -const ValueTable = coral.table.Hashed(coral.table.string_key, Value); diff --git a/source/ona/file.zig b/source/ona/file.zig new file mode 100644 index 0000000..966a65c --- /dev/null +++ b/source/ona/file.zig @@ -0,0 +1,184 @@ +const coral = @import("coral"); + +const ext = @import("./ext.zig"); + +pub const Contents = struct { + allocator: coral.io.Allocator, + data: []u8, + + pub const InitError = coral.io.AllocationError || Readable.ReadError; + + pub fn deinit(self: *Contents) void { + coral.io.deallocate(self.allocator, self.data); + } + + pub fn init(allocator: coral.io.Allocator, readable_file: *Readable) InitError!Contents { + const file_offset = try readable_file.skip(0); + const file_size = try readable_file.seek_end(); + + _ = try readable_file.seek(file_offset); + + const allocation = try coral.io.allocate_many(u8, file_size, allocator); + + errdefer coral.io.deallocate(allocator, allocation); + + if (try readable_file.read(allocation) != allocation.len) { + // Read less than was allocated for. + return error.FileUnavailable; + } + + return Contents{ + .allocator = allocator, + .data = allocation, + }; + } +}; + +pub const Path = extern struct { + data: [4096]u8 = [_]u8{0} ** 4096, + + pub const cwd = Path.from(&.{"./"}); + + pub const ValidationError = error { + PathTooLong, + }; + + pub fn from(components: []const []const u8) Path { + // TODO: Implement proper parsing / removal of duplicate path delimiters. + var path = Path{}; + + { + var writable_slice = coral.io.WritableMemory{.slice = &path.data}; + + for (components) |component| { + if (writable_slice.write(component) != component.len) { + break; + } + } + } + + return path; + } + + pub fn joined(self: Path, other: Path) Path { + var path = Path{}; + + { + var writable = coral.io.WritableMemory{.slice = &path.data}; + var written = @as(usize, 0); + + for (&self.data) |byte| { + if ((byte == 0) or !(writable.put(byte))) { + break; + } + + written += 1; + } + + if ((written > 0) and (path.data[written - 1] != '/') and writable.put('/')) { + written += 1; + } + + for (&other.data) |byte| { + if ((byte == 0) or !(writable.put(byte))) { + break; + } + + written += 1; + } + } + + return path; + } + + pub fn to_string(self: Path) ValidationError![:0]const u8 { + const sentineled_data = get_sentineled_data: { + const last_index = self.data.len - 1; + + if (self.data[last_index] != 0) { + return error.PathTooLong; + } + + break: get_sentineled_data self.data[0 .. last_index:0]; + }; + + return sentineled_data[0 .. coral.io.sentinel_index(u8, 0, sentineled_data):0]; + } +}; + +pub const Readable = opaque { + pub const ReadError = error { + FileUnavailable, + }; + + pub fn close(self: *Readable) bool { + return ext.SDL_RWclose(rw_ops_cast(self)) == 0; + } + + pub fn read(self: *Readable, buffer: []u8) ReadError!usize { + ext.SDL_ClearError(); + + const bytes_read = ext.SDL_RWread(rw_ops_cast(self), buffer.ptr, @sizeOf(u8), buffer.len); + + if ((bytes_read == 0) and (ext.SDL_GetError() != null)) { + return error.FileUnavailable; + } + + return bytes_read; + } + + pub fn seek(self: *Readable, cursor: u64) ReadError!u64 { + // TODO: Fix safety of int cast. + const byte_offset = ext.SDL_RWseek(rw_ops_cast(self), @intCast(i64, cursor), ext.RW_SEEK_SET); + + if (byte_offset < 0) { + return error.FileUnavailable; + } + + return @intCast(u64, byte_offset); + } + + pub fn seek_end(self: *Readable) ReadError!usize { + const byte_offset = ext.SDL_RWseek(rw_ops_cast(self), 0, ext.RW_SEEK_END); + + if (byte_offset < 0) { + return error.FileUnavailable; + } + + return @intCast(u64, byte_offset); + } + + pub fn skip(self: *Readable, offset: i64) ReadError!u64 { + const byte_offset = ext.SDL_RWseek(rw_ops_cast(self), offset, ext.RW_SEEK_CUR); + + if (byte_offset < 0) { + return error.FileUnavailable; + } + + return @intCast(u64, byte_offset); + } +}; + +pub const System = union (enum) { + sandboxed_path: *const Path, + + pub const OpenError = Path.ValidationError || error { + FileNotFound, + }; + + pub fn open_readable(self: System, path: Path) OpenError!*Readable { + switch (self) { + .sandboxed_path => |sandboxed_path| { + const absolute_path = sandboxed_path.joined(path); + + return @ptrCast(*Readable, ext.SDL_RWFromFile(try absolute_path.to_string(), "rb") orelse { + return error.FileNotFound; + }); + }, + } + } +}; + +fn rw_ops_cast(ptr: *anyopaque) *ext.SDL_RWops { + return @ptrCast(*ext.SDL_RWops, @alignCast(@alignOf(ext.SDL_RWops), ptr)); +} diff --git a/source/ona/files.zig b/source/ona/files.zig deleted file mode 100755 index edf35a7..0000000 --- a/source/ona/files.zig +++ /dev/null @@ -1,201 +0,0 @@ -const coral = @import("coral"); - -const ext = @import("./ext.zig"); - -pub const FileAccessor = struct { - context: *anyopaque, - - actions: *const struct { - open_readable: *const fn (context: *anyopaque, file_path: []const u8) OpenError!*ReadableFile, - open_writable: *const fn (context: *anyopaque, file_path: []const u8) OpenError!*WritableFile, - query: *const fn (context: *anyopaque, file_path: []const u8) QueryError!FileInfo, - }, - - pub fn bind(comptime State: type, state: *State) FileAccessor { - const Actions = struct { - fn as_concrete(context: *anyopaque) *State { - return @ptrCast(*State, @alignCast(@alignOf(State), context)); - } - - fn open_readable(context: *anyopaque, file_path: []const u8) OpenError!*ReadableFile { - return as_concrete(context).open_readable(file_path); - } - - fn open_writable(context: *anyopaque, file_path: []const u8) OpenError!*WritableFile { - return as_concrete(context).open_writable(file_path); - } - - fn query(context: *anyopaque, file_path: []const u8) QueryError!FileInfo { - return as_concrete(context).query(file_path); - } - }; - - return .{ - .context = @ptrCast(*anyopaque, state), - - .actions = &.{ - .open_readable = Actions.open_readable, - .open_writable = Actions.open_writable, - .query = Actions.query, - }, - }; - } - - pub fn open_readable(self: FileAccessor, file_path: []const u8) OpenError!*ReadableFile { - return self.actions.open_readable(self.context, file_path); - } - - pub fn open_writable(self: FileAccessor, file_path: []const u8) OpenError!*WritableFile { - return self.actions.open_readable(self.context, file_path); - } - - pub fn query(self: FileAccessor, file_path: []const u8) QueryError!FileInfo { - return self.actions.query(self.context, file_path); - } -}; - -pub const FileInfo = struct { - size: u64, -}; - -pub const FileSandbox = struct { - prefix: []const u8, - - flags: packed struct { - is_readable: bool = false, - is_writable: bool = false, - is_queryable: bool = false, - }, - - const native_path_max = 4095; - - fn native_path_of(file_sandbox: *FileSandbox, file_path: []const u8) [native_path_max + 1]u8 { - var native_path = [_]u8{0} ** (native_path_max + 1); - - if ((file_sandbox.prefix.len + file_path.len) < native_path_max) { - coral.io.copy(&native_path, file_sandbox.prefix); - coral.io.copy(native_path[file_sandbox.prefix.len ..], file_path); - } - - return native_path; - } - - pub fn open_readable(file_sandbox: *FileSandbox, file_path: []const u8) OpenError!*ReadableFile { - if (!file_sandbox.flags.is_readable) return error.AccessDenied; - - return @ptrCast(*ReadableFile, ext.SDL_RWFromFile(&file_sandbox.native_path_of(file_path), "rb") orelse { - return error.FileNotFound; - }); - } - - pub fn open_writable(file_sandbox: *FileSandbox, file_path: []const u8) OpenError!*WritableFile { - if (!file_sandbox.flags.is_writable) return error.AccessDenied; - - return @ptrCast(*WritableFile, ext.SDL_RWFromFile(&file_sandbox.native_path_of(file_path), "wb") orelse { - return error.FileNotFound; - }); - } - - pub fn query(file_sandbox: *FileSandbox, file_path: []const u8) QueryError!FileInfo { - if (!file_sandbox.flags.is_queryable) return error.AccessDenied; - - const rw_ops = ext.SDL_RWFromFile(&file_sandbox.native_path_of(file_path), "rb") orelse { - return error.FileNotFound; - }; - - defer _ = ext.SDL_RWclose(rw_ops); - - const file_size = ext.SDL_RWsize(rw_ops); - - if (file_size < 0) return error.FileNotFound; - - return FileInfo{ - .size = @intCast(u64, file_size), - }; - } -}; - -pub const OpenError = QueryError || error {TooManyFiles}; - -pub const ReadableFile = opaque { - pub fn as_reader(self: *ReadableFile) coral.io.Reader { - return coral.io.Reader.bind(self, read); - } - - fn as_rw_ops(self: *ReadableFile) *ext.SDL_RWops { - return @ptrCast(*ext.SDL_RWops, @alignCast(@alignOf(ext.SDL_RWops), self)); - } - - pub fn close(self: *ReadableFile) bool { - return ext.SDL_RWclose(self.as_rw_ops()) != 0; - } - - pub fn read(self: *ReadableFile, buffer: []u8) coral.io.ReadError!usize { - ext.SDL_ClearError(); - - const buffer_read = ext.SDL_RWread(self.as_rw_ops(), buffer.ptr, @sizeOf(u8), buffer.len); - - if ((buffer_read == 0) and (ext.SDL_GetError().* != 0)) return error.IoUnavailable; - - return buffer_read; - } - - pub fn rewind(self: *ReadableFile) SeekError!void { - return self.seek(0); - } - - pub fn seek(self: *ReadableFile, absolute: u64) SeekError!void { - ext.SDL_ClearError(); - - // TODO: Fix int cast. - const sought = ext.SDL_RWseek(self.as_rw_ops(), @intCast(i64, absolute), ext.RW_SEEK_SET); - - if ((sought == -1) and (ext.SDL_GetError().* != 0)) return error.IoUnavailable; - } -}; - -pub const QueryError = error { - FileNotFound, - AccessDenied, -}; - -pub const SeekError = error { - IoUnavailable, -}; - -pub const WritableFile = opaque { - pub fn as_writer(self: *WritableFile) coral.io.Writer { - return coral.io.Writer.bind(WritableFile, self); - } - - fn as_rw_ops(self: *WritableFile) *ext.SDL_RWops { - return @ptrCast(*ext.SDL_RWops, @alignCast(@alignOf(ext.SDL_RWops), self)); - } - - pub fn close(self: *WritableFile) bool { - return ext.SDL_RWclose(self.as_rw_ops()) != 0; - } - - pub fn rewind(self: *WritableFile) SeekError!void { - return self.seek(0); - } - - pub fn seek(self: *WritableFile, absolute: u64) SeekError!void { - ext.SDL_ClearError(); - - // TODO: Fix int cast. - const sought = ext.SDL_RWseek(self.as_rw_ops(), @intCast(i64, absolute), ext.RW_SEEK_SET); - - if ((sought == -1) and (ext.SDL_GetError().* != 0)) return error.IoUnavailable; - } - - pub fn write(self: *WritableFile, buffer: []const u8) coral.io.WriteError!usize { - ext.SDL_ClearError(); - - const buffer_read = ext.SDL_RWwrite(self.as_rw_ops(), buffer.ptr, @sizeOf(u8), buffer.len); - - if ((buffer_read == 0) and (ext.SDL_GetError().* != 0)) return error.IoUnavailable; - - return buffer_read; - } -}; diff --git a/source/ona/heap.zig b/source/ona/heap.zig new file mode 100644 index 0000000..14cfd1a --- /dev/null +++ b/source/ona/heap.zig @@ -0,0 +1,46 @@ +const coral = @import("coral"); + +const ext = @import("./ext.zig"); + +const Context = struct { + live_allocations: usize, + + const Self = @This(); + + fn reallocate(self: *Self, options: coral.io.AllocationOptions) ?[]u8 { + if (options.size == 0) { + if (options.allocation) |allocation| { + ext.SDL_free(allocation.ptr); + + self.live_allocations -= 1; + } + + return null; + } + + if (options.allocation) |allocation| { + if (ext.SDL_realloc(allocation.ptr, options.size)) |reallocation| { + self.live_allocations += 1; + + return @ptrCast([*]u8, reallocation)[0 .. options.size]; + } + } + + if (ext.SDL_malloc(options.size)) |allocation| { + self.live_allocations += 1; + + return @ptrCast([*]u8, allocation)[0 .. options.size]; + } + + return null; + } +}; + +var context = Context{ + .live_allocations = 0, +}; + +/// +/// Heap allocator. +/// +pub const allocator = coral.io.Allocator.bind(Context, &context, Context.reallocate); diff --git a/source/ona/kym.zig b/source/ona/kym.zig new file mode 100755 index 0000000..3cda842 --- /dev/null +++ b/source/ona/kym.zig @@ -0,0 +1,5 @@ +pub const Environment = @import("./kym/Environment.zig"); + +const coral = @import("coral"); + +const types = @import("./kym/types.zig"); diff --git a/source/ona/kym/Environment.zig b/source/ona/kym/Environment.zig new file mode 100644 index 0000000..bb308f0 --- /dev/null +++ b/source/ona/kym/Environment.zig @@ -0,0 +1,420 @@ +const bytecode = @import("./bytecode.zig"); + +const coral = @import("coral"); + +const file = @import("../file.zig"); + +const tokens = @import("./tokens.zig"); + +const types = @import("./types.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 ExecuteFileError = file.System.OpenError || file.Readable.ReadError || types.CompileError; + +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 fn release(self: *Object, env: *Self) bool { + coral.debug.assert(self.ref_count != 0); + + self.ref_count -= 1; + + if (self.ref_count == 0) { + coral.io.deallocate(env.allocator, self.state.userdata); + self.state.fields.deinit(env.allocator); + + return false; + } + + return true; + } +}; + +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(u32, 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.values.deinit(self.allocator); + self.calls.deinit(self.allocator); +} + +pub fn discard(self: *Self, val: types.Val) void { + switch (val) { + .object => |object| { + if (!self.heap.fetch(object).release(self)) { + self.heap.remove(object); + } + }, + + else => {}, + } +} + +pub fn execute_file(self: *Self, allocator: coral.io.Allocator, fs: file.System, file_path: file.Path) ExecuteFileError!types.Val { + const typeid = ""; + + const Behaviors = struct { + fn deinitialize(context: ObjectInfo.DeinitializeContext) void { + (context.env.native_cast(context.obj, typeid, bytecode.Chunk) catch unreachable).deinit(); + } + }; + + var chunk = try bytecode.Chunk.init(allocator, self, try file_path.to_string()); + + errdefer chunk.deinit(); + + { + const readable_file = try fs.open_readable(file_path); + + defer if (!readable_file.close()) { + @panic("Failed to close script file"); + }; + + var file_contents = try file.Contents.init(allocator, readable_file); + + defer file_contents.deinit(); + + var tokenizer = tokens.Tokenizer{.source = file_contents.data}; + + try chunk.parse(self, &tokenizer); + } + + const script = try self.new_object(coral.io.bytes_of(&chunk), .{ + .identity = typeid, + .deinitializer = Behaviors.deinitialize, + }); + + self.discard(script); + + return try self.call(script.as_ref(), null, &.{}); +} + +pub fn fail(self: *Self, failure_message: []const u8) error { CheckFailed } { + 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 = self.heap.fetch(indexable.object).state.fields.lookup(self.heap.fetch(field.object)) orelse { + return .nil; + }; + + if (value == .object) { + self.heap.fetch(value.object).acquire(); + } + + 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 values = try ValueStack.init(allocator, options.values_max * options.calls_max); + + errdefer values.deinit(allocator); + + var calls = try CallStack.init(allocator, options.calls_max); + + errdefer calls.deinit(allocator); + + var interned = try InternTable.init(allocator); + + errdefer interned.deinit(allocator); + + var heap = try ObjectSlab.init(allocator); + + errdefer heap.deinit(allocator); + + var environment = Self{ + .global_object = 0, + .allocator = allocator, + .reporter = options.reporter, + .interned = interned, + .values = values, + .calls = calls, + .heap = heap, + }; + + const globals = try environment.new_object(&.{}, .{ + .identity = "KYM GLOBAL OBJECT OC DO NOT STEAL", + }); + + coral.debug.assert(globals == .object); + + environment.global_object = globals.object; + + return environment; +} + +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 alignment = @alignOf(Type); + const is_expected_type = (object.state.info.identity == id) and (object.state.userdata.len == alignment); + + try self.check(is_expected_type, "invalid object cast: native type"); + + return @ptrCast(*Type, @alignCast(alignment, 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(u8, userdata.len, self.allocator); + + errdefer coral.io.deallocate(self.allocator, allocation); + + coral.io.copy(allocation, userdata); + + var fields = try Object.Fields.init(self.allocator); + + errdefer fields.deinit(self.allocator); + + return .{.object = try self.heap.insert(self.allocator, .{ + .ref_count = 1, + + .state = .{ + .info = info, + .userdata = allocation, + .fields = 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 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/kym/bytecode.zig b/source/ona/kym/bytecode.zig similarity index 63% rename from source/kym/bytecode.zig rename to source/ona/kym/bytecode.zig index bd09cd7..e189207 100755 --- a/source/kym/bytecode.zig +++ b/source/ona/kym/bytecode.zig @@ -1,169 +1,103 @@ +const Environment = @import("./Environment.zig"); + const coral = @import("coral"); const tokens = @import("./tokens.zig"); -const vm = @import("./vm.zig"); +const types = @import("./types.zig"); pub const Chunk = struct { - vm_state: *vm.State, + env: *Environment, + allocator: coral.io.Allocator, arity: usize, - name_string: *vm.ObjectInstance, - interned_instances: *vm.ObjectInstance, - bytecode_buffer: Buffer, + name: []u8, + bytecodes: Buffer, + objects: Objects, - const Buffer = coral.stack.Dense(u8); + const Buffer = coral.list.Stack(u8); - const InstanceStack = coral.stack.Dense(*vm.ObjectInstance); + const Objects = coral.list.Stack(types.Object); pub fn deinit(self: *Chunk) void { - self.bytecode_buffer.deinit(); + for (self.objects.values) |object| { + if (!self.env.heap.fetch(object).release(self.env)) { + self.env.heap.remove(object); + } + } + + coral.io.deallocate(self.allocator, self.name); + self.bytecodes.deinit(self.allocator); + self.objects.deinit(self.allocator); } - fn emit_byte(self: *Chunk, byte: u8) !void { - return self.bytecode_buffer.push_one(byte); + fn emit_byte(self: *Chunk, byte: u8) coral.io.AllocationError!void { + return self.bytecodes.push_one(self.allocator, byte); } - fn emit_float(self: *Chunk, value: Float) !void { - return self.bytecode_buffer.push_all(coral.io.bytes_of(&value)); + fn emit_closure(self: *Chunk, chunk: Chunk) coral.io.AllocationError!void { + const value = try self.env.new_object(coral.io.bytes_of(&chunk), .{ + + }); + + coral.debug.assert(value == .object); + try self.objects.push_one(self.allocator, value.object); } - fn emit_integer(self: *Chunk, value: Integer) !void { - return self.bytecode_buffer.push_all(coral.io.bytes_of(&value)); + fn emit_float(self: *Chunk, value: types.Float) coral.io.AllocationError!void { + return self.bytecodes.push_all(self.allocator, coral.io.bytes_of(&value)); } - fn emit_opcode(self: *Chunk, opcode: Opcode) !void { - return self.bytecode_buffer.push_one(@enumToInt(opcode)); + fn emit_integer(self: *Chunk, value: types.Integer) coral.io.AllocationError!void { + return self.bytecodes.push_all(self.allocator, coral.io.bytes_of(&value)); } - pub fn fetch_byte(self: Chunk, cursor: *usize) ?u8 { - if (cursor.* >= self.bytecode_buffer.values.len) return null; - - defer cursor.* += 1; - - return self.bytecode_buffer.values[cursor.*]; + fn emit_opcode(self: *Chunk, opcode: Opcode) coral.io.AllocationError!void { + return self.bytecodes.push_one(self.allocator, @enumToInt(opcode)); } - pub fn fetch_float(self: Chunk, cursor: *usize) ?Float { - const operand_size = @sizeOf(Float); - const updated_cursor = cursor.* + operand_size; + fn emit_string(self: *Chunk, string: []const u8) coral.io.AllocationError!void { + const interned_string = try self.env.intern(string); - if (updated_cursor > self.bytecode_buffer.values.len) return null; + coral.debug.assert(interned_string == .object); - var operand_bytes align(@alignOf(Float)) = [_]u8{0} ** operand_size; - - coral.io.copy(&operand_bytes, self.bytecode_buffer.values[cursor.* .. updated_cursor]); - - cursor.* = updated_cursor; - - return @bitCast(Float, operand_bytes); + return try self.bytecodes.push_all(self.allocator, coral.io.bytes_of(&interned_string)); } - pub fn fetch_integer(self: Chunk, cursor: *usize) ?Integer { - const operand_size = @sizeOf(Integer); - const updated_cursor = cursor.* + operand_size; - - if (updated_cursor > self.bytecode_buffer.values.len) return null; - - var operand_bytes align(@alignOf(Integer)) = [_]u8{0} ** operand_size; - - coral.io.copy(&operand_bytes, self.bytecode_buffer.values[cursor.* .. updated_cursor]); - - cursor.* = updated_cursor; - - return @bitCast(Integer, operand_bytes); - } - - pub fn fetch_interned(self: Chunk, cursor: *usize) ?*vm.ObjectInstance { - const interned_value = self.vm_state.get(self.interned_instances, .{ - .integer = self.fetch_integer(cursor) orelse return null - }) catch return null; - - coral.debug.assert(interned_value == .instance); - - return interned_value.instance; - } - - pub fn fetch_opcode(self: Chunk, cursor: *usize) ?Opcode { - return @intToEnum(Opcode, self.fetch_byte(cursor) orelse return null); - } - - pub fn init(vm_state: *vm.State, name: []const u8) !Chunk { + pub fn init(allocator: coral.io.Allocator, env: *Environment, name: []const u8) !Chunk { const assumed_average_bytecode_size = 1024; - var bytecode_buffer = try Buffer.init(vm_state.allocator, assumed_average_bytecode_size); + var bytecodes = try Buffer.init(allocator, assumed_average_bytecode_size); - errdefer bytecode_buffer.deinit(); + errdefer bytecodes.deinit(allocator); + + var objects = try Objects.init(allocator, 1); + + errdefer objects.deinit(allocator); + + const name_copy = try coral.io.allocate_many(u8, name.len, allocator); + + errdefer coral.io.deallocate(name_copy); + + coral.io.copy(name_copy, name); return Chunk{ - .name_string = try vm_state.new_string(name), - .interned_instances = try vm_state.new_array(), - - .vm_state = vm_state, - .bytecode_buffer = bytecode_buffer, + .allocator = allocator, + .env = env, + .name = name_copy, + .bytecodes = bytecodes, + .objects = objects, .arity = 0, }; } - fn intern_chunk(self: *Chunk, chunk: *Chunk) !vm.Integer { - var constant_slot = @as(vm.Integer, 0); - const interned_count = try self.vm_state.count(self.interned_instances); - - while (constant_slot < interned_count) : (constant_slot += 1) { - const interned_value = try self.vm_state.get(self.interned_instances, .{.integer = constant_slot}); - - coral.debug.assert(interned_value == .instance); - - switch (self.vm_state.userdata_of(interned_value.instance).*) { - .chunk => |interned_chunk| if (interned_chunk == chunk) return constant_slot, - else => continue, - } - } - - try self.vm_state.set(self.interned_instances, .{.integer = constant_slot}, .{ - .instance = try self.vm_state.new(.{.chunk = chunk}, .{ - .caller = struct { - fn call(state: *vm.State, instance: *vm.ObjectInstance, _: *vm.ObjectInstance, arguments: []const vm.Value) vm.RuntimeError!vm.Value { - const instance_chunk = - @ptrCast(*Chunk, @alignCast(@alignOf(Chunk), state.userdata_of(instance).native)); - - coral.debug.assert(instance_chunk.vm_state == state); - - return vm.execute_chunk(instance_chunk, arguments); - } - }.call, - }) - }); - - return constant_slot; - } - - fn intern_string(self: *Chunk, string: []const u8) !vm.Integer { - var constant_slot = @as(vm.Integer, 0); - const interned_count = try self.vm_state.count(self.interned_instances); - - while (constant_slot < interned_count) : (constant_slot += 1) { - const interned_value = try self.vm_state.get(self.interned_instances, .{.integer = constant_slot}); - - coral.debug.assert(interned_value == .instance); - - switch (self.vm_state.userdata_of(interned_value.instance).*) { - .string => |interned_string| if (coral.io.equals(interned_string, string)) return constant_slot, - else => continue, - } - } - - try self.vm_state.set(self.interned_instances, .{.integer = constant_slot}, .{ - .instance = try self.vm_state.new_string(string) - }); - - return constant_slot; - } - - pub fn parse(self: *Chunk, script_tokenizer: *tokens.Tokenizer) ParseError!void { + pub fn parse(self: *Chunk, env: *Environment, script_tokenizer: *tokens.Tokenizer) types.CompileError!void { errdefer self.reset(); self.reset(); - var parser = Parser{.tokenizer = script_tokenizer}; + var parser = Parser{ + .env = env, + .tokenizer = script_tokenizer + }; while (true) { parser.step() catch |step_error| switch (step_error) { @@ -183,14 +117,10 @@ pub const Chunk = struct { } pub fn reset(self: *Chunk) void { - self.bytecode_buffer.clear(); + self.bytecodes.clear(); } }; -pub const Float = f32; - -pub const Integer = i32; - pub const Opcode = enum(u8) { pop, push_nil, @@ -199,9 +129,8 @@ pub const Opcode = enum(u8) { push_zero, push_integer, push_float, - push_array, + push_object, push_table, - push_interned, not, neg, @@ -219,13 +148,8 @@ pub const Opcode = enum(u8) { set_local, }; -pub const ParseError = vm.RuntimeError || Parser.StepError || tokens.Token.ExpectError || error { - OutOfMemory, - IntOverflow, - UndefinedLocal, -}; - const Parser = struct { + env: *Environment, tokenizer: *tokens.Tokenizer, current_token: tokens.Token = .newline, previous_token: tokens.Token = .newline, @@ -287,12 +211,8 @@ const Parser = struct { const operator_tokens = &.{.symbol_assign, .symbol_plus, .symbol_dash, .symbol_asterisk, .symbol_forward_slash, .symbol_paren_left, .symbol_comma}; - fn parse_closure(self: *Parser, parent_chunk: *Chunk) ParseError!void { - const closure_chunk = parent_chunk.vm_state.allocator.allocate_one(Chunk) orelse return error.OutOfMemory; - - errdefer parent_chunk.vm_state.allocator.deallocate(closure_chunk); - - closure_chunk.* = try Chunk.init(parent_chunk.vm_state, switch (self.previous_token) { + fn parse_closure(self: *Parser, parent_chunk: *Chunk) types.CompileError!void { + var closure_chunk = try Chunk.init(self.env.allocator, self.env, switch (self.previous_token) { .local_identifier => |identifier| identifier, .symbol_assign, .symbol_paren_left, .symbol_comma => "", else => return error.UnexpectedToken, @@ -336,7 +256,7 @@ const Parser = struct { switch (self.current_token) { .symbol_brace_right => break, - else => if (try self.parse_statement(closure_chunk)) continue, + else => if (try self.parse_statement(&closure_chunk)) continue, } while (true) { @@ -352,12 +272,11 @@ const Parser = struct { } } - try parent_chunk.emit_opcode(.push_interned); - try parent_chunk.emit_integer(try parent_chunk.intern_chunk(closure_chunk)); - try parent_chunk.emit_opcode(.push_closure); + try parent_chunk.emit_opcode(.push_object); + try parent_chunk.emit_closure(closure_chunk); } - fn parse_expression(self: *Parser, chunk: *Chunk) ParseError!void { + fn parse_expression(self: *Parser, chunk: *Chunk) types.CompileError!void { var operators = OperatorStack{}; var local_depth = @as(usize, 0); @@ -393,7 +312,7 @@ const Parser = struct { .integer_literal => |literal| { try self.previous_token.expect_any(operator_tokens); - const value = coral.utf8.parse_signed(@bitSizeOf(i64), literal) + const value = coral.utf8.parse_signed(@bitSizeOf(types.Integer), literal) catch |parse_error| switch (parse_error) { error.BadSyntax => unreachable, error.IntOverflow => return error.IntOverflow, @@ -412,7 +331,7 @@ const Parser = struct { .real_literal => |literal| { try self.previous_token.expect_any(operator_tokens); - try chunk.emit_float(coral.utf8.parse_float(@bitSizeOf(f64), literal) catch |parse_error| { + try chunk.emit_float(coral.utf8.parse_float(@bitSizeOf(types.Float), literal) catch |parse_error| { switch (parse_error) { // Already validated to be a real by the tokenizer so this cannot fail, as real syntax is a // subset of float syntax. @@ -425,8 +344,8 @@ const Parser = struct { .string_literal => |literal| { try self.previous_token.expect_any(operator_tokens); - try chunk.emit_opcode(.push_interned); - try chunk.emit_integer(try chunk.intern_string(literal)); + try chunk.emit_opcode(.push_object); + try chunk.emit_string(literal); try self.step(); }, @@ -479,7 +398,7 @@ const Parser = struct { switch (self.previous_token) { .global_identifier => |identifier| { try chunk.emit_opcode(.get_global); - try chunk.emit_integer(try chunk.intern_string(identifier)); + try chunk.emit_string(identifier); }, .local_identifier => |identifier| { @@ -489,7 +408,7 @@ const Parser = struct { }); } else { try chunk.emit_opcode(.get_index); - try chunk.emit_integer(try chunk.intern_string(identifier)); + try chunk.emit_string(identifier); } }, @@ -510,13 +429,13 @@ const Parser = struct { }); } else { try chunk.emit_opcode(.get_index); - try chunk.emit_integer(try chunk.intern_string(identifier)); + try chunk.emit_string(identifier); } }, .global_identifier => |identifier| { try chunk.emit_opcode(.get_global); - try chunk.emit_integer(try chunk.intern_string(identifier)); + try chunk.emit_string(identifier); }, else => { @@ -532,7 +451,7 @@ const Parser = struct { local_depth += 1; - var argument_count = @as(Integer, 0); + var argument_count = @as(types.Integer, 0); while (true) { try self.step(); @@ -564,7 +483,7 @@ const Parser = struct { else => false, }; - var field_count = @as(Integer, 0); + var field_count = @as(types.Integer, 0); while (true) { try self.step(); @@ -574,11 +493,10 @@ const Parser = struct { .local_identifier => { // Create local copy of identifier because step() will overwrite captures. - const interned_identifier = - try chunk.intern_string(self.current_token.local_identifier); + const identifier = self.current_token.local_identifier; - try chunk.emit_opcode(.push_interned); - try chunk.emit_integer(interned_identifier); + try chunk.emit_opcode(.push_object); + try chunk.emit_string(identifier); try self.step(); switch (self.current_token) { @@ -590,8 +508,8 @@ const Parser = struct { }, .symbol_brace_right => { - try chunk.emit_opcode(.push_interned); - try chunk.emit_integer(interned_identifier); + try chunk.emit_opcode(.push_object); + try chunk.emit_string(identifier); field_count += 1; @@ -599,8 +517,8 @@ const Parser = struct { }, .symbol_comma => { - try chunk.emit_opcode(.push_interned); - try chunk.emit_integer(interned_identifier); + try chunk.emit_opcode(.push_object); + try chunk.emit_string(identifier); field_count += 1; }, @@ -642,7 +560,7 @@ const Parser = struct { } } - fn parse_operator(self: *Parser, chunk: *Chunk, operators: *OperatorStack, rhs_operator: Operator) ParseError!void { + fn parse_operator(self: *Parser, chunk: *Chunk, operators: *OperatorStack, rhs_operator: Operator) types.CompileError!void { try self.previous_token.expect_any(operator_tokens); while (operators.pop()) |lhs_operator| { @@ -655,7 +573,7 @@ const Parser = struct { try self.step(); } - fn parse_statement(self: *Parser, chunk: *Chunk) ParseError!bool { + fn parse_statement(self: *Parser, chunk: *Chunk) types.CompileError!bool { var local_depth = @as(usize, 0); while (true) { @@ -694,7 +612,7 @@ const Parser = struct { try self.step(); try self.current_token.expect(.local_identifier); try chunk.emit_opcode(.get_global); - try chunk.emit_integer(try chunk.intern_string(identifier)); + try chunk.emit_string(identifier); local_depth += 1; }, @@ -707,12 +625,10 @@ const Parser = struct { try self.current_token.expect(.local_identifier); if (local_depth == 0) { - try chunk.emit_byte(self.resolve_local(identifier) orelse { - return error.UndefinedLocal; - }); + try chunk.emit_byte(self.resolve_local(identifier) orelse return error.UndefinedLocal); } else { try chunk.emit_opcode(.get_index); - try chunk.emit_integer(try chunk.intern_string(identifier)); + try chunk.emit_string(identifier); } local_depth += 1; @@ -735,7 +651,7 @@ const Parser = struct { } } else { try chunk.emit_opcode(.set_index); - try chunk.emit_integer(try chunk.intern_string(identifier)); + try chunk.emit_string(identifier); } try self.step(); @@ -759,19 +675,19 @@ const Parser = struct { }); } else { try chunk.emit_opcode(.get_index); - try chunk.emit_integer(try chunk.intern_string(identifier)); + try chunk.emit_string(identifier); } }, .global_identifier => |identifier| { try chunk.emit_opcode(.get_global); - try chunk.emit_integer(try chunk.intern_string(identifier)); + try chunk.emit_string(identifier); }, else => return error.UnexpectedToken, } - var argument_count = @as(Integer, 0); + var argument_count = @as(types.Integer, 0); while (true) { try self.step(); diff --git a/source/kym/tokens.zig b/source/ona/kym/tokens.zig similarity index 100% rename from source/kym/tokens.zig rename to source/ona/kym/tokens.zig diff --git a/source/ona/kym/types.zig b/source/ona/kym/types.zig new file mode 100644 index 0000000..4351b09 --- /dev/null +++ b/source/ona/kym/types.zig @@ -0,0 +1,56 @@ +const coral = @import("coral"); + +pub const CompileError = coral.io.AllocationError || RuntimeError || error { + UnexpectedEnd, + UnexpectedToken, + UndefinedLocal, +}; + +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 RuntimeError = coral.io.AllocationError || error { + IntOverflow, + CheckFailed, +}; + +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 46b4596..b6b1929 100755 --- a/source/ona/ona.zig +++ b/source/ona/ona.zig @@ -1,38 +1,118 @@ -pub const canvas = @import("./canvas.zig"); +const builtin = @import("builtin"); + +const canvas = @import("./canvas.zig"); const coral = @import("coral"); const ext = @import("./ext.zig"); -pub const files = @import("./files.zig"); +pub const file = @import("./file.zig"); -pub const App = opaque { - pub fn Starter(comptime errors: type) type { - return fn (app_state: *App) errors!void; +pub const heap = @import("./heap.zig"); + +const kym = @import("./kym.zig"); + +const AppManifest = struct { + title: [255:0]u8 = [_:0]u8{0} ** 255, + 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(heap.allocator, fs, file.Path.from(&.{file_path})); + + defer env.discard(manifest); + + const manifest_ref = manifest.as_ref(); + + { + const title = try env.get_field(manifest_ref, try env.intern("title")); + + defer env.discard(title); + + const title_string = try env.string_cast(title.as_ref()); + + try env.check(title_string.len <= self.title.len, "`title` cannot exceed 255 bytes in length"); + coral.io.copy(&self.title, title_string); + } + + { + const width = try env.get_field(manifest_ref, try env.intern("width")); + + errdefer env.discard(width); + + self.width = try coral.math.checked_cast(u16, try env.to_integer(width.as_ref())); + } + + { + const height = try env.get_field(manifest_ref, try env.intern("height")); + + errdefer env.discard(height); + + self.width = try coral.math.checked_cast(u16, try env.to_integer(height.as_ref())); + } } +}; - const State = struct { - base_file_sandbox: files.FileSandbox, - window: *ext.SDL_Window, - renderer: *ext.SDL_Renderer, - last_tick: u64, - tick_rate: u64 = 16, - canvas_items: coral.slots.Dense(coral.slots.addressable_key, canvas.Item), +pub fn run_app(base_file_system: file.System) void { + const Logger = struct { + const Self = @This(); - fn cast(self: *App) *State { - return @ptrCast(*State, @alignCast(@alignOf(State), self)); + fn log(_: *Self, message: []const u8) void { + ext.SDL_LogError(ext.SDL_LOG_CATEGORY_APPLICATION, "%.*s", message.len, message.ptr); } }; - pub fn data_fs(self: *App) files.FileAccessor { - return files.FileAccessor.bind(files.FileSandbox, &State.cast(self).base_file_sandbox); + var logger = Logger{}; + + var script_environment = kym.Environment.init(heap.allocator, .{ + .values_max = 512, + .calls_max = 512, + .reporter = kym.Environment.Reporter.bind(Logger, &logger, Logger.log), + }) catch { + return ext.SDL_LogError(ext.SDL_LOG_CATEGORY_APPLICATION, "failed to initialize Kym vm\n"); + }; + + defer script_environment.deinit(); + + const app_file_name = "app.ona"; + var app_manifest = AppManifest{}; + + app_manifest.load_script(&script_environment, base_file_system, app_file_name) catch { + return ext.SDL_LogError(ext.SDL_LOG_CATEGORY_APPLICATION, "failed to load %s\n", app_file_name); + }; + + if (ext.SDL_Init(ext.SDL_INIT_EVERYTHING) != 0) { + return ext.SDL_LogError(ext.SDL_LOG_CATEGORY_APPLICATION, "%s\n", ext.SDL_GetError()); } - pub fn loop(self: *App) void { - const state = State.cast(self); + defer ext.SDL_Quit(); + + { + const base_prefix = ext.SDL_GetBasePath() orelse { + return ext.SDL_LogError(ext.SDL_LOG_CATEGORY_APPLICATION, "%s\n", ext.SDL_GetError()); + }; + + defer ext.SDL_free(base_prefix); + + const window_flags = 0; + const window_pos = ext.SDL_WINDOWPOS_CENTERED; + + const window = ext.SDL_CreateWindow(&app_manifest.title, window_pos, window_pos, app_manifest.width, app_manifest.height, window_flags) orelse { + return ext.SDL_LogError(ext.SDL_LOG_CATEGORY_APPLICATION, "%s\n", ext.SDL_GetError()); + }; + + defer ext.SDL_DestroyWindow(window); + + const renderer_flags = 0; + + const renderer = ext.SDL_CreateRenderer(window, -1, renderer_flags) orelse { + return ext.SDL_LogError(ext.SDL_LOG_CATEGORY_APPLICATION, "%s\n", ext.SDL_GetError()); + }; + + defer ext.SDL_DestroyRenderer(renderer); while (true) { - const current_tick = ext.SDL_GetTicks64(); + // TODO: Delta timing. var event = @as(ext.SDL_Event, undefined); while (ext.SDL_PollEvent(&event) != 0) { @@ -42,140 +122,17 @@ pub const App = opaque { } } - if ((current_tick - state.last_tick) >= state.tick_rate) { - // TODO: Perform game updates. - if (ext.SDL_SetRenderDrawColor(state.renderer, 255, 255, 255, 255) != 0) { - return log_error(&.{.{.string = coral.io.slice_sentineled(u8, 0, ext.SDL_GetError())}}); - } - - if (ext.SDL_RenderClear(state.renderer) != 0) { - return log_error(&.{.{.string = coral.io.slice_sentineled(u8, 0, ext.SDL_GetError())}}); - } - - for (state.canvas_items.values) |canvas_item| { - switch (canvas_item.options) { - .sprite => {}, - } - } - - ext.SDL_RenderPresent(state.renderer); - - state.last_tick = current_tick; - } - } - } - - pub fn run(comptime errors: type, start: *const Starter(errors)) errors!void { - if (ext.SDL_Init(ext.SDL_INIT_EVERYTHING) != 0) { - return log_error(&.{.{.string = coral.io.slice_sentineled(u8, 0, ext.SDL_GetError())}}); - } - - const base_prefix = ext.SDL_GetBasePath() orelse { - return log_error(&.{.{.string = coral.io.slice_sentineled(u8, 0, ext.SDL_GetError())}}); - }; - - defer ext.SDL_free(base_prefix); - - const window_flags = 0; - - const window = ext.SDL_CreateWindow("ona", -1, -1, 640, 480, window_flags) orelse { - return log_error(&.{.{.string = coral.io.slice_sentineled(u8, 0, ext.SDL_GetError())}}); - }; - - defer ext.SDL_DestroyWindow(window); - - const renderer_flags = 0; - - const renderer = ext.SDL_CreateRenderer(window, -1, renderer_flags) orelse { - return log_error(&.{.{.string = coral.io.slice_sentineled(u8, 0, ext.SDL_GetError())}}); - }; - - defer ext.SDL_DestroyRenderer(renderer); - - return start(@ptrCast(*App, &App.State{ - .window = window, - .renderer = renderer, - .last_tick = 0, - .canvas_items = .{}, - - .base_file_sandbox = .{ - .prefix = coral.io.slice_sentineled(u8, 0, base_prefix), - - .flags = .{ - .is_readable = true, - .is_queryable = true, - } - }, - })); - } -}; - -pub const allocator = coral.io.Allocator.bind(&heap, @TypeOf(heap).reallocate); - -var heap = struct { - live_allocations: usize = 0, - - const Self = @This(); - - pub fn reallocate(self: *Self, options: coral.io.AllocationOptions) ?[]u8 { - if (options.size == 0) { - if (options.allocation) |allocation| { - ext.SDL_free(allocation.ptr); - - self.live_allocations -= 1; + if (ext.SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0) != 0) { + return ext.SDL_LogError(ext.SDL_LOG_CATEGORY_APPLICATION, "%s\n", ext.SDL_GetError()); } - return null; - } - - if (options.allocation) |allocation| { - if (ext.SDL_realloc(allocation.ptr, options.size)) |reallocation| { - self.live_allocations += 1; - - return @ptrCast([*]u8, reallocation)[0 .. options.size]; + if (ext.SDL_RenderClear(renderer) != 0) { + return ext.SDL_LogError(ext.SDL_LOG_CATEGORY_APPLICATION, "%s\n", ext.SDL_GetError()); } + + // TODO: Render here. + + ext.SDL_RenderPresent(renderer); } - - if (ext.SDL_malloc(options.size)) |allocation| { - self.live_allocations += 1; - - return @ptrCast([*]u8, allocation)[0 .. options.size]; - } - - return null; } -}{}; - -pub const CanvasItem = struct { - app: *App, - id: coral.slots.Slot(coral.slots.addressable_key), - - pub fn deinit(self: *CanvasItem) void { - coral.debug.assert(App.State.cast(self.app).canvas_items.remove(self.id)); - } - - pub fn init(app: *App, canvas_item: canvas.Item) coral.io.AllocationError!CanvasItem { - const slot = try App.State.cast(app).canvas_items.insert(allocator, canvas_item); - - return CanvasItem{ - .app = app, - .id = slot, - }; - } -}; - -pub fn log_debug(values: []const coral.format.Value) void { - var message_memory = [_]u8{0} ** 4096; - var message_buffer = coral.buffer.Fixed{.data = &message_memory}; - const message_length = coral.format.print(message_buffer.as_writer(), values) catch return; - - ext.SDL_LogDebug(ext.SDL_LOG_CATEGORY_APPLICATION, "%.*s\n", message_length, &message_buffer); -} - -pub fn log_error(values: []const coral.format.Value) void { - var message_memory = [_]u8{0} ** 4096; - var message_buffer = coral.buffer.Fixed{.data = &message_memory}; - const message_length = coral.format.print(message_buffer.as_writer(), values) catch return; - - ext.SDL_LogError(ext.SDL_LOG_CATEGORY_APPLICATION, "%.*s\n", message_length, &message_buffer); } diff --git a/source/runner.zig b/source/runner.zig index 7088c2b..98d9614 100755 --- a/source/runner.zig +++ b/source/runner.zig @@ -1,117 +1,5 @@ -const coral = @import("coral"); - -const kym = @import("kym"); - const ona = @import("ona"); -const transform_typeid = @typeName(ona.canvas.Transform); - -fn new_canvas_item(environment: *kym.vm.Environment, canvas_item: ona.CanvasItem) !*kym.vm.Object { - const typeid = @typeName(ona.CanvasItem); - - var object = try environment.new(.{ - .userdata = coral.io.bytes_of(&canvas_item), - .identity = typeid, - }); - - errdefer environment.release(object); - - return object; -} - -fn new_transform(environment: *kym.Environment, transform: ona.canvas.Transform) !*kym.vm.Object { - var object = try environment.new(.{ - .userdata = coral.io.bytes_of(&transform), - .identity = transform_typeid, - }); - - errdefer environment.release(object); - - return object; -} - -pub fn main() anyerror!void { - return ona.App.run(anyerror, start); -} - -fn start(app: *ona.App) anyerror!void { - var kym_environment = try kym.vm.Environment.init(ona.allocator, .{ - .stack_max = 256, - .calls_max = 256, - }); - - defer kym_environment.deinit(); - - const Ona = struct { - app: *ona.App, - - const typeid = @typeName(ona.App); - }; - - var ona_lib = try kym_environment.new(.{ - .userdata = coral.io.bytes_of(&Ona{ - .app = app, - }), - - .identity = Ona.typeid, - }); - - defer kym_environment.release(ona_lib); - - try kym_environment.global_set("ona", ona_lib.as_value()); - - { - var sprite_creator = try kym_environment.new(.{.behavior = &.{.caller = struct { - fn call(context: kym.vm.Object.CallContext) kym.vm.RuntimeError!kym.vm.Value { - try context.env.check(context.args.len == 2, "2 arguments expected"); - - var item = try ona.CanvasItem.init((try context.env.user_cast(context.caller, Ona.typeid, Ona)).app, .{ - .transform = (try context.env.user_cast(context.args[0].object, transform_typeid, ona.canvas.Transform)).*, - .options = .{.sprite = .{}}, - }); - - errdefer item.deinit(); - - return (try new_canvas_item(context.env, item)).as_value(); - } - }.call}}); - - defer kym_environment.release(sprite_creator); - - try kym_environment.raw_set(ona_lib, "create_sprite", sprite_creator.as_value()); - } - - { - const index_path = "index.kym"; - const index_file = try app.data_fs().open_readable(index_path); - - defer if (!index_file.close()) ona.log_error(&.{.{.string = "failed to close "}, .{.string = index_path}}); - - const index_size = (try app.data_fs().query(index_path)).size; - const index_allocation = try coral.io.allocate_many(u8, index_size, ona.allocator); - - defer coral.io.deallocate(ona.allocator, index_allocation); - - var index_buffer = coral.buffer.Fixed{.data = index_allocation[0 .. index_size]}; - - { - var stream_buffer = [_]u8{0} ** 1024; - - if ((try coral.io.stream(index_buffer.as_writer(), index_file.as_reader(), &stream_buffer)) != index_size) - return error.IoUnavailable; - } - - { - const index_script = try kym_environment.new_script(.{ - .name = index_path, - .data = index_buffer.data - }); - - defer kym_environment.release(index_script); - - _ = try kym_environment.call(index_script, &.{}); - } - } - - app.loop(); +pub fn main() !void { + ona.run_app(.{.sandboxed_path = &ona.file.Path.cwd}); }