From 1ea115b277bb833bf3d7df48a38b8351e27a7366 Mon Sep 17 00:00:00 2001 From: kayomn Date: Fri, 21 Jul 2023 23:03:25 +0100 Subject: [PATCH] Replace globals with native call interface --- debug/app.ona | 4 +- source/coral/io.zig | 43 ++++-- source/coral/list.zig | 28 +++- source/coral/map.zig | 21 ++- source/ona/app.zig | 95 +++---------- source/ona/kym.zig | 294 +++++++++++++++++++++++---------------- source/ona/kym/Ast.zig | 10 +- source/ona/kym/Chunk.zig | 72 ++++++---- source/ona/kym/Table.zig | 5 +- source/ona/ona.zig | 90 ++++++------ source/runner.zig | 4 +- 11 files changed, 380 insertions(+), 286 deletions(-) diff --git a/debug/app.ona b/debug/app.ona index 69c8d59..37c0243 100644 --- a/debug/app.ona +++ b/debug/app.ona @@ -1,8 +1,8 @@ -title = "Afterglow" +@log_info("game is loading") return { - title = title, + title = "Afterglow", width = 1280, height = 800, tick_rate = 60, diff --git a/source/coral/io.zig b/source/coral/io.zig index ce7a6b2..8882160 100644 --- a/source/coral/io.zig +++ b/source/coral/io.zig @@ -116,18 +116,32 @@ pub fn Functor(comptime Output: type, comptime Input: type) type { pub fn bind(comptime State: type, state: *const State, comptime invoker: fn (capture: *const State, input: Input) Output) Self { const is_zero_aligned = @alignOf(State) == 0; + const Invoker = struct { + fn invoke(context: *const anyopaque, input: Input) Output { + if (is_zero_aligned) { + return invoker(@ptrCast(context), input); + } + + return invoker(@ptrCast(@alignCast(context)), input); + } + }; + return .{ .context = if (is_zero_aligned) state else @ptrCast(state), + .invoker = Invoker.invoke, + }; + } - .invoker = struct { - fn invoke(context: *const anyopaque, input: Input) Output { - if (is_zero_aligned) { - return invoker(@ptrCast(context), input); - } + pub fn from(comptime invoker: fn (input: Input) Output) Self { + const Invoker = struct { + fn invoke(_: *const anyopaque, input: Input) Output { + return invoker(input); + } + }; - return invoker(@ptrCast(@alignCast(context)), input); - } - }.invoke, + return .{ + .context = &.{}, + .invoker = Invoker.invoke, }; } @@ -162,6 +176,19 @@ pub fn Generator(comptime Output: type, comptime Input: type) type { }; } + pub fn from(comptime invoker: fn (input: Input) Output) Self { + const Invoker = struct { + fn invoke(_: *const anyopaque, input: Input) Output { + return invoker(input); + } + }; + + return .{ + .context = &.{}, + .invoker = Invoker.invoke, + }; + } + pub fn invoke(self: Self, input: Input) Output { return self.invoker(self.context, input); } diff --git a/source/coral/list.zig b/source/coral/list.zig index d9b794d..a265cb5 100644 --- a/source/coral/list.zig +++ b/source/coral/list.zig @@ -78,7 +78,7 @@ pub fn Stack(comptime Value: type) type { return null; } - return &self.values[self.values.len - 1]; + return self.values[self.values.len - 1]; } pub fn pop(self: *Self) ?Value { @@ -93,6 +93,22 @@ pub fn Stack(comptime Value: type) type { return self.values[last_index]; } + 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(values.len + values.len); + } + + const offset_index = self.values.len; + + self.values = self.values.ptr[0 .. new_length]; + + for (0 .. values.len) |index| { + self.values[offset_index + index] = values[index]; + } + } + pub fn push_one(self: *Self, value: Value) io.AllocationError!void { if (self.values.len == self.capacity) { try self.grow(math.max(1, self.capacity)); @@ -106,3 +122,13 @@ pub fn Stack(comptime Value: type) type { } }; } + +pub fn stack_as_writer(self: *ByteStack) io.Writer { + return io.Writer.bind(ByteStack, self, write_stack); +} + +fn write_stack(stack: *ByteStack, bytes: []const io.Byte) ?usize { + stack.push_all(bytes) catch return null; + + return bytes.len; +} diff --git a/source/coral/map.zig b/source/coral/map.zig index d42b7fc..f9c961f 100644 --- a/source/coral/map.zig +++ b/source/coral/map.zig @@ -103,6 +103,13 @@ pub fn Slab(comptime Value: type) type { }; } +pub fn StringTable(comptime Value: type) type { + return Table([]const io.Byte, Value, .{ + .hash = hash_string, + .match = io.equals, + }); +} + pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTraits(Key)) type { const load_max = 0.75; @@ -143,7 +150,7 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra pub const Iterable = struct { table: *Self, - iterations: usize = 0, + iterations: usize, pub fn next(self: *Iterable) ?Entry { while (self.iterations < self.table.entries.len) { @@ -160,6 +167,13 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra const Self = @This(); + pub fn as_iterable(self: *Self) Iterable { + return .{ + .table = self, + .iterations = 0, + }; + } + pub fn remove(self: *Self, key: Key) ?Entry { const hash_max = math.min(math.max_int(@typeInfo(usize).Int), self.entries.len); var hashed_key = math.wrap(traits.hash(key), math.min_int(@typeInfo(usize).Int), hash_max); @@ -332,8 +346,3 @@ fn hash_string(key: []const io.Byte) usize { return hash_code; } - -pub const string_table_traits = TableTraits([]const io.Byte){ - .hash = hash_string, - .match = io.equals, -}; diff --git a/source/ona/app.zig b/source/ona/app.zig index 99a2430..2a22b06 100644 --- a/source/ona/app.zig +++ b/source/ona/app.zig @@ -12,14 +12,14 @@ pub const Manifest = struct { height: u16 = 480, tick_rate: f32 = 60.0, - pub fn load(self: *Manifest, env: *kym.RuntimeEnv, global_ref: ?*const kym.RuntimeRef, file_access: file.Access) kym.RuntimeError!void { - var manifest_ref = try env.execute_file(global_ref, file_access, file.Path.from(&.{"app.ona"})); + pub fn load(self: *Manifest, env: *kym.RuntimeEnv, file_access: file.Access) kym.RuntimeError!void { + const manifest_ref = try env.execute_file(file_access, file.Path.from(&.{"app.ona"})); - defer env.discard(&manifest_ref); + defer env.discard(manifest_ref); - var title_ref = try kym.get_dynamic_field(env, manifest_ref, "title"); + const title_ref = try kym.get_field(env, manifest_ref, "title"); - defer env.discard(&title_ref); + defer env.discard(title_ref); const title_string = switch (env.unbox(title_ref)) { .string => |string| string, @@ -27,9 +27,9 @@ pub const Manifest = struct { }; const width = @as(u16, get: { - var ref = try kym.get_dynamic_field(env, manifest_ref, "width"); + const ref = try kym.get_field(env, manifest_ref, "width"); - defer env.discard(&ref); + defer env.discard(ref); // TODO: Add safety-checks to int cast. break: get switch (env.unbox(ref)) { @@ -39,9 +39,9 @@ pub const Manifest = struct { }); const height = @as(u16, get: { - var ref = try kym.get_dynamic_field(env, manifest_ref, "height"); + const ref = try kym.get_field(env, manifest_ref, "height"); - defer env.discard(&ref); + defer env.discard(ref); // TODO: Add safety-checks to int cast. break: get switch (env.unbox(ref)) { @@ -51,9 +51,9 @@ pub const Manifest = struct { }); const tick_rate = @as(f32, get: { - var ref = try kym.get_dynamic_field(env, manifest_ref, "tick_rate"); + const ref = try kym.get_field(env, manifest_ref, "tick_rate"); - defer env.discard(&ref); + defer env.discard(ref); break: get switch (env.unbox(ref)) { .number => |number| @floatCast(number), @@ -74,69 +74,14 @@ pub const Manifest = struct { } }; -pub const LogSeverity = enum { - info, - warn, - fail, -}; +pub fn log_info(message: []const coral.io.Byte) void { + ext.SDL_LogInfo(ext.SDL_LOG_CATEGORY_APPLICATION, "%.*s", @as(c_int, @intCast(message.len)), message.ptr); +} -pub const WritableLog = struct { - severity: LogSeverity, - write_buffer: coral.list.ByteStack, +pub fn log_warn(message: []const coral.io.Byte) void { + ext.SDL_LogWarn(ext.SDL_LOG_CATEGORY_APPLICATION, "%.*s", @as(c_int, @intCast(message.len)), message.ptr); +} - pub fn as_writer(self: *WritableLog) coral.io.Writer { - return coral.io.Writer.bind(WritableLog, self, struct { - fn write(writable_log: *WritableLog, bytes: []const coral.io.Byte) ?usize { - writable_log.write(bytes) catch return null; - - return bytes.len; - } - }.write); - } - - pub fn free(self: *WritableLog) void { - self.write_buffer.free(); - } - - pub fn make(log_severity: LogSeverity, allocator: coral.io.Allocator) WritableLog { - return .{ - .severity = log_severity, - .write_buffer = coral.list.ByteStack.make(allocator), - }; - } - - pub fn write(self: *WritableLog, bytes: []const coral.io.Byte) coral.io.AllocationError!void { - const format_string = "%.*s"; - var line_written = @as(usize, 0); - - for (bytes) |byte| { - if (byte == '\n') { - ext.SDL_LogError( - ext.SDL_LOG_CATEGORY_APPLICATION, - format_string, - self.write_buffer.values.len, - self.write_buffer.values.ptr); - - self.write_buffer.clear(); - - line_written = 0; - - continue; - } - - try self.write_buffer.push_one(byte); - - line_written += 1; - } - - if (self.write_buffer.values.len == 0) { - ext.SDL_LogError( - ext.SDL_LOG_CATEGORY_APPLICATION, - format_string, - self.write_buffer.values.len, - self.write_buffer.values.ptr); - - self.write_buffer.clear(); - } - } -}; +pub fn log_fail(message: []const coral.io.Byte) void { + ext.SDL_LogError(ext.SDL_LOG_CATEGORY_APPLICATION, "%.*s", @as(c_int, @intCast(message.len)), message.ptr); +} diff --git a/source/ona/kym.zig b/source/ona/kym.zig index 091e86b..d57ce67 100644 --- a/source/ona/kym.zig +++ b/source/ona/kym.zig @@ -10,30 +10,34 @@ const file = @import("./file.zig"); const tokens = @import("./kym/tokens.zig"); +pub const Args = []const ?*const RuntimeRef; + pub const CallContext = struct { env: *RuntimeEnv, caller: ?*const RuntimeRef, userdata: []coral.io.Byte, - args: []const ?*const RuntimeRef = &.{}, - - pub fn arg_at(self: CallContext, index: u8) RuntimeError!*const RuntimeRef { - if (!coral.math.is_clamped(index, 0, self.args.len - 1)) { - return self.env.check_fail("argument out of bounds"); - } - - return self.args[@as(usize, index)]; - } }; -pub const Callable = coral.io.Generator(RuntimeError!?*RuntimeRef, CallContext); - pub const DynamicObject = struct { userdata: []coral.io.Byte, typeinfo: *const Typeinfo, }; +pub const ErrorHandler = coral.io.Generator(void, ErrorInfo); + +pub const ErrorInfo = struct { + message: []const coral.io.Byte, + frames: []const Frame, +}; + pub const Float = f64; +pub const Frame = struct { + name: []const coral.io.Byte, + arg_count: u8, + locals_top: usize, +}; + pub const IndexContext = struct { env: *RuntimeEnv, userdata: []coral.io.Byte, @@ -42,23 +46,18 @@ pub const IndexContext = struct { pub const RuntimeEnv = struct { allocator: coral.io.Allocator, - err_writer: coral.io.Writer, + error_handler: ErrorHandler, + syscallers: SyscallerTable, local_refs: RefStack, frames: FrameStack, ref_values: RefSlab, - const FrameStack = coral.list.Stack(struct { - arg_count: u8, - locals_top: usize, - }); + const FrameStack = coral.list.Stack(Frame); + + const SyscallerTable = coral.map.StringTable(Syscaller); const RefStack = coral.list.Stack(?*RuntimeRef); - pub const Options = struct { - out_writer: coral.io.Writer = coral.io.null_writer, - err_writer: coral.io.Writer = coral.io.null_writer, - }; - const RefSlab = coral.map.Slab(struct { ref_count: usize, @@ -71,9 +70,9 @@ pub const RuntimeEnv = struct { }, }); - pub const ScriptSource = struct { + pub const Syscall = struct { name: []const coral.io.Byte, - data: []const coral.io.Byte, + caller: Syscaller, }; pub fn acquire(self: *RuntimeEnv, ref: ?*const RuntimeRef) ?*RuntimeRef { @@ -89,22 +88,10 @@ pub const RuntimeEnv = struct { return @ptrFromInt(key); } - pub fn call( - self: *RuntimeEnv, - caller_ref: ?*const RuntimeRef, - callable_ref: ?*const RuntimeRef, - arg_refs: []const *const RuntimeRef, - ) RuntimeError!?*RuntimeRef { - return switch (self.unbox(callable_ref)) { - .dynamic => |dynamic| dynamic.typeinfo.call(.{ - .env = self, - .caller = caller_ref, - .userdata = dynamic.userdata, - .args = arg_refs, - }), - - else => self.raise(error.TypeMismatch, "type is not callable"), - }; + pub fn bind_syscalls(self: *RuntimeEnv, syscalls: []const Syscall) RuntimeError!void { + for (syscalls) |syscall| { + _ = try self.syscallers.replace(syscall.name, syscall.caller); + } } pub fn discard(self: *RuntimeEnv, ref: ?*RuntimeRef) void { @@ -126,58 +113,29 @@ pub const RuntimeEnv = struct { } } - pub fn dynamic_get(self: *RuntimeEnv, indexable_ref: ?*const RuntimeRef, index_ref: ?*const RuntimeRef) RuntimeError!?*RuntimeRef { - return switch (self.unbox(indexable_ref)) { - .nil => self.raise(error.BadOperation, "nil has no such index"), - .boolean => self.raise(error.BadOperation, "boolean has no such index"), - .number => self.raise(error.BadOperation, "number has no such index"), - .string => self.raise(error.BadOperation, "string has no such index"), + pub fn execute_file(self: *RuntimeEnv, file_access: file.Access, file_path: file.Path) RuntimeError!?*RuntimeRef { + if ((try file.allocate_and_load(self.allocator, file_access, file_path))) |file_data| { + defer self.allocator.deallocate(file_data); - .dynamic => |dynamic| dynamic.typeinfo.get(.{ - .userdata = dynamic.userdata, - .env = self, - .index_ref = index_ref, - }), - }; + if (file_path.to_string()) |string| { + return self.execute_script(string, file_data); + } + } + + return self.raise(error.BadOperation, "failed to load file"); } - pub fn dynamic_set(self: *RuntimeEnv, indexable_ref: ?*const RuntimeRef, index_ref: ?*const RuntimeRef, value_ref: ?*const RuntimeRef) RuntimeError!void { - return switch (self.unbox(indexable_ref)) { - .nil => self.raise(error.BadOperation, "nil is immutable"), - .boolean => self.raise(error.BadOperation, "boolean is immutable"), - .number => self.raise(error.BadOperation, "number is immutable"), - .string => self.raise(error.BadOperation, "string is immutable"), - - .dynamic => |dynamic| dynamic.typeinfo.set(.{ - .userdata = dynamic.userdata, - .env = self, - .index_ref = index_ref, - }, value_ref), - }; - } - - pub fn execute_file(self: *RuntimeEnv, global_ref: ?*const RuntimeRef, file_access: file.Access, file_path: file.Path) RuntimeError!?*RuntimeRef { - const error_message = "failed to load file"; - - const file_data = (try file.allocate_and_load(self.allocator, file_access, file_path)) orelse { - return self.raise(error.SystemFailure, error_message); - }; - - defer self.allocator.deallocate(file_data); - - return self.execute_script(global_ref, .{ - .name = file_path.to_string() orelse return self.raise(error.SystemFailure, error_message), - .data = file_data, - }); - } - - pub fn execute_script(self: *RuntimeEnv, global_ref: ?*const RuntimeRef, source: ScriptSource) RuntimeError!?*RuntimeRef { + pub fn execute_script( + self: *RuntimeEnv, + name: []const coral.io.Byte, + data: []const coral.io.Byte, + ) RuntimeError!?*RuntimeRef { var ast = Ast.make(self.allocator); defer ast.free(); { - var tokenizer = tokens.Tokenizer{.source = source.data}; + var tokenizer = tokens.Tokenizer{.source = data}; ast.parse(&tokenizer) catch |parse_error| switch (parse_error) { error.BadSyntax => return self.raise(error.BadSyntax, ast.error_message), @@ -191,7 +149,7 @@ pub const RuntimeEnv = struct { try exe.compile_ast(ast); - return exe.execute(global_ref, source.name); + return try exe.execute(name); } pub fn frame_pop(self: *RuntimeEnv) void { @@ -201,13 +159,12 @@ pub const RuntimeEnv = struct { coral.debug.assert(self.local_refs.drop((self.local_refs.values.len - frame.?.locals_top) + frame.?.arg_count)); } - pub fn frame_push(self: *RuntimeEnv, arg_count: u8) RuntimeError![]const *const RuntimeRef { + pub fn frame_push(self: *RuntimeEnv, frame_name: []const coral.io.Byte, arg_count: u8) RuntimeError!void { try self.frames.push_one(.{ + .name = frame_name, .arg_count = arg_count, .locals_top = self.local_refs.values.len, }); - - return @as([]const *const RuntimeRef, @ptrCast(self.local_refs.values[(self.local_refs.values.len - arg_count) .. self.local_refs.values.len])); } pub fn free(self: *RuntimeEnv) void { @@ -215,6 +172,16 @@ pub const RuntimeEnv = struct { self.ref_values.free(); } + pub fn get_arg(self: *RuntimeEnv, index: usize) RuntimeError!?*const RuntimeRef { + const frame = self.frames.peek() orelse return self.raise(error.IllegalState, "stack underflow"); + + if (index >= frame.arg_count) { + return null; + } + + return self.local_refs.values[frame.locals_top - (1 + index)]; + } + pub fn local_get(self: *RuntimeEnv, local: u8) ?*RuntimeRef { return self.local_refs.values[local]; } @@ -243,12 +210,13 @@ pub const RuntimeEnv = struct { self.local_refs.values[local] = self.acquire(value); } - pub fn make(allocator: coral.io.Allocator, options: Options) RuntimeError!RuntimeEnv { - return .{ + pub fn make(allocator: coral.io.Allocator, error_handler: ErrorHandler) coral.io.AllocationError!RuntimeEnv { + return RuntimeEnv{ .local_refs = RefStack.make(allocator), .ref_values = RefSlab.make(allocator), .frames = FrameStack.make(allocator), - .err_writer = options.err_writer, + .syscallers = SyscallerTable.make(allocator), + .error_handler = error_handler, .allocator = allocator, }; } @@ -260,7 +228,11 @@ pub const RuntimeEnv = struct { })); } - pub fn new_dynamic(self: *RuntimeEnv, userdata: []const coral.io.Byte, typeinfo: *const Typeinfo) RuntimeError!*RuntimeRef { + pub fn new_dynamic( + self: *RuntimeEnv, + userdata: []const coral.io.Byte, + typeinfo: *const Typeinfo, + ) RuntimeError!*RuntimeRef { const userdata_copy = try coral.io.allocate_copy(self.allocator, userdata); errdefer self.allocator.deallocate(userdata_copy); @@ -296,17 +268,19 @@ pub const RuntimeEnv = struct { })); } - pub fn raise(self: *RuntimeEnv, error_value: RuntimeError, error_message: []const coral.io.Byte) RuntimeError { - // TODO: Print stack trace from state. - coral.utf8.print_formatted(self.err_writer, "{name}@{line}: {message}", .{ - .name = "???", - .line = @as(u64, 0), - .message = error_message, - }) catch {}; + pub fn raise(self: *RuntimeEnv, error_value: RuntimeError, message: []const coral.io.Byte) RuntimeError { + self.error_handler.invoke(.{ + .message = message, + .frames = self.frames.values, + }); return error_value; } + pub fn syscaller(self: *RuntimeEnv, name: []const coral.io.Byte) RuntimeError!Syscaller { + return self.syscallers.lookup(name) orelse self.raise(error.BadOperation, "attempt to call undefined syscall"); + } + pub fn unbox(self: *RuntimeEnv, ref: ?*const RuntimeRef) Unboxed { const ref_data = self.ref_values.lookup(@intFromPtr(ref orelse return .nil)); @@ -320,18 +294,48 @@ pub const RuntimeEnv = struct { .dynamic => |dynamic| .{.dynamic = dynamic}, }; } + + pub fn unbox_dynamic(self: *RuntimeEnv, ref: ?*const RuntimeRef) RuntimeError![]const coral.io.Byte { + if (ref) |live_ref| { + const ref_data = self.ref_values.lookup(@intFromPtr(live_ref)); + + coral.debug.assert(ref_data != null); + + if (ref_data.?.object == .dynamic) { + return ref_data.?.object.dynamic; + } + } + + return self.raise(error.TypeMismatch, "expected dynamic"); + } + + pub fn unbox_string(self: *RuntimeEnv, ref: ?*const RuntimeRef) RuntimeError![]const coral.io.Byte { + if (ref) |live_ref| { + const ref_data = self.ref_values.lookup(@intFromPtr(live_ref)); + + coral.debug.assert(ref_data != null); + + if (ref_data.?.object == .string) { + return ref_data.?.object.string; + } + } + + return self.raise(error.TypeMismatch, "expected string"); + } }; pub const RuntimeError = coral.io.AllocationError || error { IllegalState, - SystemFailure, TypeMismatch, BadOperation, BadSyntax, + Assertion, }; pub const RuntimeRef = opaque {}; +pub const Syscaller = coral.io.Generator(RuntimeError!?*RuntimeRef, *RuntimeEnv); + pub const TestContext = struct { env: *RuntimeEnv, userdata: []const coral.io.Byte, @@ -339,6 +343,7 @@ pub const TestContext = struct { }; pub const Typeinfo = struct { + name: []const coral.io.Byte, call: *const fn (context: CallContext) RuntimeError!?*RuntimeRef = default_call, clean: *const fn (userdata: []coral.io.Byte) void = default_clean, get: *const fn (context: IndexContext) RuntimeError!?*RuntimeRef = default_get, @@ -380,26 +385,87 @@ pub const Unboxed = union (enum) { number: Float, string: []const coral.io.Byte, dynamic: *const DynamicObject, + + pub fn expect_dynamic(self: Unboxed, env: *RuntimeEnv) RuntimeError!*const DynamicObject { + return switch (self) { + .dynamic => |dynamic| dynamic, + else => env.raise(error.TypeMismatch, "expected dynamic"), + }; + } }; -pub fn get_dynamic_field(env: *RuntimeEnv, indexable_ref: ?*const RuntimeRef, field_name: []const coral.io.Byte) RuntimeError!?*RuntimeRef { +pub fn assert(env: *RuntimeEnv, condition: bool, message: []const coral.io.Byte) RuntimeError!void { + if (!condition) { + return env.raise(error.Assertion, message); + } +} + +pub fn call( + env: *RuntimeEnv, + caller_ref: ?*const RuntimeRef, + callable_ref: ?*const RuntimeRef, + arg_refs: Args, +) RuntimeError!?*RuntimeRef { + for (arg_refs) |arg_ref| { + try env.local_push_ref(arg_ref); + } + + env.frame_push("", arg_refs.len); + + defer env.frame_pop(); + + const dynamic = try env.unbox(callable_ref).expect_dynamic(); + + return dynamic.type_info.call(.{ + .env = env, + .caller = caller_ref, + .userdata = dynamic.userdata, + }); +} + +pub fn get( + env: *RuntimeEnv, + indexable_ref: ?*const RuntimeRef, + index_ref: ?*const RuntimeRef, +) RuntimeError!?*RuntimeRef { + const dynamic = try env.unbox(indexable_ref).expect_dynamic(env); + + return dynamic.typeinfo.get(.{ + .userdata = dynamic.userdata, + .env = env, + .index_ref = index_ref, + }); +} + +pub fn get_field(env: *RuntimeEnv, indexable_ref: ?*const RuntimeRef, field_name: []const coral.io.Byte) RuntimeError!?*RuntimeRef { const field_name_ref = try env.new_string(field_name); defer env.discard(field_name_ref); - return env.dynamic_get(indexable_ref, field_name_ref); + return get(env, indexable_ref, field_name_ref); } -pub fn new_callable(env: *RuntimeEnv, generator: Callable) RuntimeError!?*RuntimeRef { - const Concrete = struct { - fn call(context: CallContext) RuntimeError!?*RuntimeRef { - return @as(*Callable, @ptrCast(@alignCast(context.userdata.ptr))).invoke(context); - } - }; +pub fn set( + env: *RuntimeEnv, + indexable_ref: ?*const RuntimeRef, + index_ref: ?*const RuntimeRef, + value_ref: ?*const RuntimeRef, +) RuntimeError!void { + const dynamic = try env.unbox(indexable_ref).expect_dynamic(env); - return env.new_dynamic(coral.io.bytes_of(&generator), &.{ - .call = Concrete.call, - }); + return dynamic.typeinfo.set(.{ + .userdata = dynamic.userdata, + .env = env, + .index_ref = index_ref, + }, value_ref); +} + +pub fn set_field(env: *RuntimeEnv, indexable_ref: ?*const RuntimeRef, field_name: []const coral.io.Byte, value_ref: ?*const RuntimeRef) RuntimeError!void { + const field_name_ref = try env.new_string(field_name); + + defer env.discard(field_name_ref); + + return set(env, indexable_ref, field_name_ref, value_ref); } pub fn new_table(env: *RuntimeEnv) RuntimeError!?*RuntimeRef { @@ -410,14 +476,6 @@ pub fn new_table(env: *RuntimeEnv) RuntimeError!?*RuntimeRef { return try env.new_dynamic(coral.io.bytes_of(&table), &Table.typeinfo); } -pub fn set_dynamic_field(env: *RuntimeEnv, indexable_ref: ?*const RuntimeRef, field_name: []const coral.io.Byte, value_ref: ?*const RuntimeRef) RuntimeError!void { - const field_name_ref = try env.new_string(field_name); - - defer env.discard(field_name_ref); - - return env.dynamic_set(indexable_ref, field_name_ref, value_ref); -} - pub fn test_difference(env: *RuntimeEnv, lhs_ref: ?*const RuntimeRef, rhs_ref: ?*const RuntimeRef) RuntimeError!Float { return switch (env.unbox(lhs_ref)) { .nil => env.raise(error.TypeMismatch, "cannot compare nil objects"), diff --git a/source/ona/kym/Ast.zig b/source/ona/kym/Ast.zig index cbf7335..c5781f3 100755 --- a/source/ona/kym/Ast.zig +++ b/source/ona/kym/Ast.zig @@ -17,7 +17,7 @@ pub const Expression = union (enum) { grouped_expression: *Expression, get_local: []const coral.io.Byte, - call_global: struct { + call_system: struct { identifier: []const coral.io.Byte, argument_expressions: List, }, @@ -85,7 +85,7 @@ pub const Statement = union (enum) { expression: Expression, }, - call_global: struct { + call_system: struct { identifier: []const coral.io.Byte, argument_expressions: Expression.List, }, @@ -249,7 +249,7 @@ pub fn parse(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!void { tokenizer.step(); try self.statements.push_one(.{ - .call_global = .{ + .call_system = .{ .argument_expressions = expressions_list, .identifier = identifier, }, @@ -344,7 +344,7 @@ fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression tokenizer.step(); return Expression{ - .call_global = .{ + .call_system = .{ .identifier = identifier, .argument_expressions = expression_list, }, @@ -361,7 +361,7 @@ fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression tokenizer.step(); return Expression{ - .call_global = .{ + .call_system = .{ .identifier = identifier, .argument_expressions = expression_list, }, diff --git a/source/ona/kym/Chunk.zig b/source/ona/kym/Chunk.zig index 5013c3c..c029e15 100644 --- a/source/ona/kym/Chunk.zig +++ b/source/ona/kym/Chunk.zig @@ -90,7 +90,7 @@ const AstCompiler = struct { }); }, - .call_global => |call| { + .call_system => |call| { if (call.argument_expressions.values.len > coral.math.max_int(@typeInfo(u8).Int)) { return self.chunk.env.raise(error.OutOfMemory, "functions may receive a maximum of 255 locals"); } @@ -100,7 +100,8 @@ const AstCompiler = struct { } try self.chunk.append_opcode(.{.push_const = try self.chunk.declare_constant_string(call.identifier)}); - try self.chunk.append_opcode(.{.call = @intCast(call.argument_expressions.values.len)}); + try self.chunk.append_opcode(.{.syscall = @intCast(call.argument_expressions.values.len)}); + try self.chunk.append_opcode(.pop); }, } } @@ -120,7 +121,7 @@ const AstCompiler = struct { } }, - .call_global => |call| { + .call_system => |call| { if (call.argument_expressions.values.len > coral.math.max_int(@typeInfo(u8).Int)) { return self.chunk.env.raise(error.OutOfMemory, "functions may receive a maximum of 255 locals"); } @@ -130,8 +131,8 @@ const AstCompiler = struct { } try self.chunk.append_opcode(.{.push_const = try self.chunk.declare_constant_string(call.identifier)}); - try self.chunk.append_opcode(.get_global); - try self.chunk.append_opcode(.{.call = @intCast(call.argument_expressions.values.len)}); + try self.chunk.append_opcode(.{.syscall = @intCast(call.argument_expressions.values.len)}); + try self.chunk.append_opcode(.pop); } } } @@ -171,6 +172,7 @@ const RefList = coral.list.Stack(?*kym.RuntimeRef); const LocalsList = coral.list.Stack([]const u8); pub const Opcode = union (enum) { + pop, push_nil, push_true, push_false, @@ -178,8 +180,8 @@ pub const Opcode = union (enum) { push_local: u8, push_table: u32, set_local: u8, - get_global, call: u8, + syscall: u8, not, neg, @@ -251,14 +253,14 @@ pub fn declare_constant_string(self: *Self, constant: []const coral.io.Byte) kym return @intCast(tail); } -pub fn execute(self: *Self, global_ref: ?*const kym.RuntimeRef, name: []const coral.io.Byte) kym.RuntimeError!?*kym.RuntimeRef { - _ = name; - _ = try self.env.frame_push(0); +pub fn execute(self: *Self, name: []const coral.io.Byte) kym.RuntimeError!?*kym.RuntimeRef { + try self.env.frame_push(name, 0); defer self.env.frame_pop(); for (self.opcodes.values) |opcode| { switch (opcode) { + .pop => self.env.discard(self.env.local_pop()), .push_nil => try self.env.local_push_ref(null), .push_true => try self.env.local_push_boolean(true), .push_false => try self.env.local_push_boolean(false), @@ -270,10 +272,18 @@ pub fn execute(self: *Self, global_ref: ?*const kym.RuntimeRef, name: []const co defer self.env.discard(table_ref); { + const dynamic = try self.env.unbox(table_ref).expect_dynamic(self.env); var popped = @as(usize, 0); while (popped < field_count) : (popped += 1) { - try self.env.dynamic_set(table_ref, self.env.local_pop(), self.env.local_pop()); + const index_ref = self.env.local_pop(); + const value_ref = self.env.local_pop(); + + try dynamic.typeinfo.set(.{ + .userdata = dynamic.userdata, + .env = self.env, + .index_ref = index_ref, + }, value_ref); } } @@ -288,18 +298,6 @@ pub fn execute(self: *Self, global_ref: ?*const kym.RuntimeRef, name: []const co try self.env.local_push_ref(ref); }, - .get_global => { - const identifier_ref = self.env.local_pop(); - - defer self.env.discard(identifier_ref); - - const ref = try self.env.dynamic_get(global_ref, identifier_ref); - - defer self.env.discard(ref); - - try self.env.local_push_ref(ref); - }, - .set_local => |local| { const ref = self.env.local_pop(); @@ -313,17 +311,43 @@ pub fn execute(self: *Self, global_ref: ?*const kym.RuntimeRef, name: []const co defer self.env.discard(callable_ref); - const args = try self.env.frame_push(arg_count); + try self.env.frame_push("", arg_count); defer self.env.frame_pop(); - const result_ref = try self.env.call(global_ref, callable_ref, args); + const dynamic = try self.env.unbox(callable_ref).expect_dynamic(self.env); + + const result_ref = try dynamic.typeinfo.call(.{ + .env = self.env, + .caller = null, + .userdata = dynamic.userdata, + }); defer self.env.discard(result_ref); try self.env.local_push_ref(result_ref); }, + .syscall => |arg_count| { + const identifier_ref = self.env.local_pop(); + + defer self.env.discard(identifier_ref); + + const identifier = try self.env.unbox_string(identifier_ref); + + try self.env.frame_push(identifier, arg_count); + + errdefer self.env.frame_pop(); + + const result_ref = try (try self.env.syscaller(identifier)).invoke(self.env); + + defer self.env.discard(result_ref); + + self.env.frame_pop(); + + try self.env.local_push_ref(result_ref); + }, + .neg => try self.env.local_push_number(switch (self.env.unbox(self.env.local_pop())) { .number => |number| -number, else => return self.env.raise(error.TypeMismatch, "object is not scalar negatable"), diff --git a/source/ona/kym/Table.zig b/source/ona/kym/Table.zig index ae6af0a..ac5819f 100644 --- a/source/ona/kym/Table.zig +++ b/source/ona/kym/Table.zig @@ -4,10 +4,10 @@ const kym = @import("../kym.zig"); fields: FieldTable, -const FieldTable = coral.map.Table([]const coral.io.Byte, struct { +const FieldTable = coral.map.StringTable(struct { key_ref: ?*kym.RuntimeRef, value_ref: ?*kym.RuntimeRef, -}, coral.map.string_table_traits); +}); const Self = @This(); @@ -22,6 +22,7 @@ pub fn make(env: *kym.RuntimeEnv) Self { } pub const typeinfo = kym.Typeinfo{ + .name = "table", .clean = typeinfo_clean, .get = typeinfo_get, .set = typeinfo_set, diff --git a/source/ona/ona.zig b/source/ona/ona.zig index 82ee995..18facc5 100644 --- a/source/ona/ona.zig +++ b/source/ona/ona.zig @@ -10,81 +10,85 @@ const heap = @import("./heap.zig"); const kym = @import("./kym.zig"); -pub const RuntimeError = error { - OutOfMemory, - InitFailure, - BadManifest, -}; +fn kym_handle_errors(info: kym.ErrorInfo) void { + var remaining_frames = info.frames.len; + + while (remaining_frames != 0) { + remaining_frames -= 1; + + app.log_fail(info.frames[remaining_frames].name); + } +} + +fn kym_log_info(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef { + app.log_info(try env.unbox_string(try env.get_arg(0))); + + return null; +} + +fn kym_log_warn(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef { + app.log_warn(try env.unbox_string(try env.get_arg(0))); + + return null; +} + +fn kym_log_fail(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef { + app.log_fail(try env.unbox_string(try env.get_arg(0))); + + return null; +} fn last_sdl_error() [:0]const u8 { return coral.io.slice_sentineled(@as(u8, 0), @as([*:0]const u8, @ptrCast(ext.SDL_GetError()))); } -pub fn run_app(file_access: file.Access) RuntimeError!void { - var info_log = app.WritableLog.make(.info, heap.allocator); - - defer info_log.free(); - - var fail_log = app.WritableLog.make(.fail, heap.allocator); - - defer fail_log.free(); - +pub fn run_app(file_access: file.Access) void { if (ext.SDL_Init(ext.SDL_INIT_EVERYTHING) != 0) { - try fail_log.write(last_sdl_error()); - - return error.InitFailure; + return app.log_fail(last_sdl_error()); } defer ext.SDL_Quit(); - var script_env = kym.RuntimeEnv.make(heap.allocator, .{ - .out_writer = info_log.as_writer(), - .err_writer = fail_log.as_writer(), - }) catch { - try fail_log.write("failed to initialize script runtime"); - - return error.InitFailure; + var script_env = kym.RuntimeEnv.make(heap.allocator, kym.ErrorHandler.from(kym_handle_errors)) catch { + return app.log_fail("failed to initialize script runtime"); }; defer script_env.free(); - var globals_ref = kym.new_table(&script_env) catch { - try fail_log.write("failed to initialize script runtime"); - - return error.InitFailure; + script_env.bind_syscalls(&.{ + .{ + .name = "log_info", + .caller = kym.Syscaller.from(kym_log_info), + }, + .{ + .name = "log_fail", + .caller = kym.Syscaller.from(kym_log_fail), + }, + }) catch { + return app.log_fail("failed to initialize script runtime"); }; - defer script_env.discard(&globals_ref); - var manifest = app.Manifest{}; - manifest.load(&script_env, globals_ref, file_access) catch { - fail_log.write("failed to load / execute app.ona manifest") catch {}; - - return error.BadManifest; - }; + manifest.load(&script_env, file_access) catch return; const window = create: { const pos = ext.SDL_WINDOWPOS_CENTERED; const flags = 0; break: create ext.SDL_CreateWindow(&manifest.title, pos, pos, manifest.width, manifest.height, flags) orelse { - fail_log.write(last_sdl_error()) catch {}; - - return error.InitFailure; + return app.log_fail(last_sdl_error()); }; }; defer ext.SDL_DestroyWindow(window); const renderer = create: { - const defaultDriverIndex = -1; + const default_driver_index = -1; const flags = ext.SDL_RENDERER_ACCELERATED; - break: create ext.SDL_CreateRenderer(window, defaultDriverIndex, flags) orelse { - fail_log.write(last_sdl_error()) catch {}; - - return error.InitFailure; + break: create ext.SDL_CreateRenderer(window, default_driver_index, flags) orelse { + return app.log_fail(last_sdl_error()); }; }; diff --git a/source/runner.zig b/source/runner.zig index e24965c..d38ba14 100644 --- a/source/runner.zig +++ b/source/runner.zig @@ -1,5 +1,5 @@ const ona = @import("ona"); -pub fn main() ona.RuntimeError!void { - try ona.run_app(.{.sandboxed_path = &ona.file.Path.cwd}); +pub fn main() anyerror!void { + ona.run_app(.{.sandboxed_path = &ona.file.Path.cwd}); }