diff --git a/source/ona/kym.zig b/source/ona/kym.zig index 79f4af8..42758c9 100644 --- a/source/ona/kym.zig +++ b/source/ona/kym.zig @@ -14,6 +14,7 @@ pub const Any = union (enum) { float: Float, string: []const coral.io.Byte, symbol: []const coral.io.Byte, + lambda, dynamic: *const DynamicObject, pub fn expect_dynamic(self: Any) ?*const DynamicObject { @@ -76,22 +77,20 @@ pub const Method = struct { }; pub const RuntimeEnv = struct { - interned_symbols: SymbolTable, + interned_symbols: RefTable, allocator: coral.io.Allocator, error_handler: ErrorHandler, - syscallers: SyscallerTable, + syscallables: RefTable, local_refs: RefStack, frames: FrameStack, ref_values: RefSlab, const FrameStack = coral.list.Stack(Frame); - const SymbolTable = coral.map.StringTable(*RuntimeRef); - - const SyscallerTable = coral.map.StringTable(Caller); - const RefStack = coral.list.Stack(?*RuntimeRef); + const RefTable = coral.map.StringTable(*RuntimeRef); + const RefSlab = coral.map.Slab(struct { ref_count: usize, @@ -102,15 +101,11 @@ pub const RuntimeEnv = struct { fixed: Fixed, string: []coral.io.Byte, symbol: []coral.io.Byte, + lambda: Caller, dynamic: *DynamicObject, }, }); - pub const Syscall = struct { - name: []const coral.io.Byte, - caller: Caller, - }; - pub fn acquire(self: *RuntimeEnv, ref: *const RuntimeRef) *RuntimeRef { const key = @intFromPtr(ref); var ref_data = self.ref_values.remove(key); @@ -124,15 +119,9 @@ pub const RuntimeEnv = struct { return @ptrFromInt(key); } - pub fn bind_syscalls(self: *RuntimeEnv, syscalls: []const Syscall) RuntimeError!void { - for (syscalls) |syscall| { - _ = try self.syscallers.replace(syscall.name, syscall.caller); - } - } - pub fn call( self: *RuntimeEnv, - caller: Caller, + callable_ref: *const RuntimeRef, arg_count: u8, name: []const coral.io.Byte, ) RuntimeError!?*RuntimeRef { @@ -154,16 +143,26 @@ pub const RuntimeEnv = struct { } } - return caller.invoke(self); - } + return switch ((self.ref_values.lookup(@intFromPtr(callable_ref)) orelse unreachable).object) { + .lambda => |lambda| lambda.invoke(self), + + .dynamic => |dynamic| dynamic.typeinfo.call(.{ + .env = self, + .userdata = dynamic.userdata, + }), - pub fn callable(self: *RuntimeEnv, ref: *const RuntimeRef) RuntimeError!Caller { - return switch ((self.ref_values.lookup(@intFromPtr(ref)) orelse unreachable).object) { - .dynamic => |dynamic| Caller.bind(DynamicObject, dynamic, DynamicObject.call), else => self.raise(error.TypeMismatch, "object is not callable"), }; } + pub fn bind_syscaller(self: *RuntimeEnv, comptime name: []const coral.io.Byte, caller: Caller) RuntimeError!void { + const lambda_ref = try self.new_lambda(caller); + + if (try self.syscallables.replace(name, self.acquire(lambda_ref))) |replaced_entry| { + self.discard(replaced_entry.value); + } + } + pub fn discard(self: *RuntimeEnv, ref: ?*RuntimeRef) void { const key = @intFromPtr(ref orelse return); var ref_data = self.ref_values.remove(key) orelse unreachable; @@ -174,7 +173,7 @@ pub const RuntimeEnv = struct { if (ref_data.ref_count == 0) { switch (ref_data.object) { - .false, .true, .float, .fixed => {}, + .false, .true, .float, .fixed, .lambda => {}, .string => |string| self.allocator.deallocate(string), .symbol => |symbol| self.allocator.deallocate(symbol), @@ -225,7 +224,11 @@ pub const RuntimeEnv = struct { try chunk.compile_ast(ast); - return self.call(chunk.as_caller(), 0, name); + const chunk_ref = try self.new_lambda(chunk.as_caller()); + + defer self.discard(chunk_ref); + + return self.call(chunk_ref, 0, name); } pub fn free(self: *RuntimeEnv) void { @@ -241,9 +244,17 @@ pub const RuntimeEnv = struct { } } + { + var iterable = self.syscallables.as_iterable(); + + while (iterable.next()) |entry| { + self.discard(entry.value); + } + } + self.frames.free(); self.local_refs.free(); - self.syscallers.free(); + self.syscallables.free(); self.ref_values.free(); self.interned_symbols.free(); } @@ -267,6 +278,14 @@ pub const RuntimeEnv = struct { return self.acquire(self.local_refs.values[local] orelse return null); } + pub fn get_syscallable(self: *RuntimeEnv, name: []const coral.io.Byte) ?*RuntimeRef { + if (self.syscallables.lookup(name)) |system_ref| { + return self.acquire(system_ref); + } + + return null; + } + pub fn pop_local(self: *RuntimeEnv) RuntimeError!?*RuntimeRef { return self.local_refs.pop() orelse self.raise(error.IllegalState, "stack underflow"); } @@ -300,8 +319,8 @@ pub const RuntimeEnv = struct { .local_refs = RefStack.make(allocator), .ref_values = RefSlab.make(allocator), .frames = FrameStack.make(allocator), - .syscallers = SyscallerTable.make(allocator, .{}), - .interned_symbols = SymbolTable.make(allocator, .{}), + .syscallables = RefTable.make(allocator, .{}), + .interned_symbols = RefTable.make(allocator, .{}), .error_handler = error_handler, .allocator = allocator, }; @@ -350,6 +369,13 @@ pub const RuntimeEnv = struct { })); } + pub fn new_lambda(self: *RuntimeEnv, caller: Caller) RuntimeError!*RuntimeRef { + return @ptrFromInt(try self.ref_values.insert(.{ + .ref_count = 1, + .object = .{.lambda = caller}, + })); + } + pub fn new_string(self: *RuntimeEnv, string_data: []const coral.io.Byte) RuntimeError!*RuntimeRef { const string_copy = try coral.io.allocate_copy(self.allocator, string_data); @@ -405,10 +431,6 @@ pub const RuntimeEnv = struct { }, index_ref, value_ref); } - pub fn syscallable(self: *RuntimeEnv, name: []const coral.io.Byte) RuntimeError!Caller { - return self.syscallers.lookup(name) orelse self.raise(error.BadOperation, "attempt to get undefined syscall"); - } - pub fn unbox(self: *RuntimeEnv, ref: *const RuntimeRef) Any { return switch ((self.ref_values.lookup(@intFromPtr(ref)) orelse unreachable).object) { .false => .{.boolean = false}, @@ -417,6 +439,7 @@ pub const RuntimeEnv = struct { .float => |float| .{.float = float}, .string => |string| .{.string = string}, .symbol => |symbol| .{.symbol = symbol}, + .lambda => .lambda, .dynamic => |dynamic| .{.dynamic = dynamic}, }; } @@ -484,7 +507,8 @@ pub fn hash(env: *RuntimeEnv, ref: *const RuntimeRef) usize { .fixed => 0, .string => |string| coral.io.djb2_hash(@typeInfo(usize).Int, string), .symbol => 0, - .dynamic => |dynamic| @intFromPtr(dynamic), + .lambda => 0, + .dynamic => 0, }; } @@ -520,6 +544,7 @@ pub fn test_difference( else => env.raise(error.TypeMismatch, "right-hand object is not comparable with symbol objects"), }, + .lambda => env.raise(error.TypeMismatch, "lambda objects are not comparable"), .dynamic => env.raise(error.TypeMismatch, "dynamic objects are not comparable"), }; } @@ -555,10 +580,7 @@ pub fn test_equality(env: *RuntimeEnv, lhs_ref: *const RuntimeRef, rhs_ref: *con else => false, }, - .dynamic => |lhs_dynamic| switch (env.unbox(rhs_ref)) { - .dynamic => |rhs_dynamic| rhs_dynamic == lhs_dynamic, - else => false, - }, + else => lhs_ref == rhs_ref, }; } diff --git a/source/ona/kym/Ast.zig b/source/ona/kym/Ast.zig index 8faa1b2..2815216 100755 --- a/source/ona/kym/Ast.zig +++ b/source/ona/kym/Ast.zig @@ -18,6 +18,7 @@ pub const Expression = union (enum) { symbol_literal: []const coral.io.Byte, table_literal: TableLiteral, grouped_expression: *Expression, + get_system: []const coral.io.Byte, get_local: []const coral.io.Byte, set_local: []const coral.io.Byte, @@ -43,6 +44,11 @@ pub const Expression = union (enum) { expression: *Expression, }, + call: struct { + object_expression: *Expression, + argument_expressions: List, + }, + pub const BinaryOperator = enum { addition, subtraction, @@ -320,10 +326,16 @@ fn parse_factor(self: *Self) ParseError!Expression { break: parse .{.string_literal = value}; }, - .identifier => |local_identifier| { + .identifier => |identifier| { self.tokenizer.skip_newlines(); - break: parse .{.get_local = local_identifier}; + break: parse .{.get_local = identifier}; + }, + + .system_identifier => |system_identifier| { + self.tokenizer.skip_newlines(); + + break: parse .{.get_system = system_identifier}; }, .symbol_brace_left => { @@ -446,6 +458,40 @@ fn parse_factor(self: *Self) ParseError!Expression { self.tokenizer.skip_newlines(); }, + .symbol_paren_left => { + var argument_expressions = Expression.List.make(allocator); + + while (true) { + self.tokenizer.skip_newlines(); + + switch (self.tokenizer.token) { + .symbol_paren_right => break, + + else => { + try argument_expressions.push_one(try self.parse_expression()); + + switch (self.tokenizer.token) { + .symbol_comma => continue, + .symbol_paren_right => break, + else => return self.report("expected `,` or `)` after function argument expression"), + } + }, + } + } + + self.tokenizer.skip_newlines(); + + // TODO: Remove when Zig fixes miscompilation with in-place struct re-assignment. + const unnecessary_temp = try coral.io.allocate_one(allocator, expression); + + expression = .{ + .call = .{ + .argument_expressions = argument_expressions, + .object_expression = unnecessary_temp, + }, + }; + }, + else => break, } } diff --git a/source/ona/kym/Chunk.zig b/source/ona/kym/Chunk.zig index d3fd37b..899075b 100644 --- a/source/ona/kym/Chunk.zig +++ b/source/ona/kym/Chunk.zig @@ -95,10 +95,27 @@ const AstCompiler = struct { }); }, + .call => |call| { + if (call.argument_expressions.values.len > coral.math.max_int(@typeInfo(u8).Int)) { + return self.chunk.env.raise(error.BadSyntax, "lambdas may contain a maximum of 255 arguments"); + } + + for (call.argument_expressions.values) |argument_expression| { + try self.compile_expression(argument_expression); + } + + try self.compile_expression(call.object_expression.*); + try self.chunk.append_opcode(.{.call = @intCast(call.argument_expressions.values.len)}); + }, + .grouped_expression => |grouped_expression| { try self.compile_expression(grouped_expression.*); }, + .get_system => |get_system| { + try self.chunk.append_opcode(.{.push_system = try self.chunk.declare_constant_string(get_system)}); + }, + .get_local => |get_local| { try self.chunk.append_opcode(.{ .push_local = self.resolve_local(get_local) orelse { @@ -154,7 +171,7 @@ const AstCompiler = struct { fn declare_local(self: *AstCompiler, identifier: []const u8) kym.RuntimeError!void { if (self.local_identifiers_count == self.local_identifiers_buffer.len) { - return self.chunk.env.raise(error.OutOfMemory, "functions may contain a maximum of 255 locals"); + return self.chunk.env.raise(error.OutOfMemory, "lambdas may contain a maximum of 255 locals"); } self.local_identifiers_buffer[self.local_identifiers_count] = identifier; @@ -180,7 +197,7 @@ const AstCompiler = struct { } }; -const RefList = coral.list.Stack(?*kym.RuntimeRef); +const RefList = coral.list.Stack(*kym.RuntimeRef); const LocalsList = coral.list.Stack([]const u8); @@ -192,11 +209,11 @@ pub const Opcode = union (enum) { push_const: u16, push_local: u8, push_table: u32, + push_system: u16, set_local: u8, get_dynamic, set_dynamic, call: u8, - syscall: u8, not, neg, @@ -341,6 +358,17 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef try env.push_ref(table_ref); }, + .push_system => |push_system| { + const system_ref = self.env.get_syscallable(try kym.unbox_string( + self.env, + self.constant_refs.values[push_system], + )); + + defer self.env.discard(system_ref); + + try self.env.push_ref(system_ref); + }, + .push_local => |local| { const ref = try env.get_local(local); @@ -405,25 +433,7 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef defer env.discard(callable_ref); - break: call try env.call(try env.callable(callable_ref), arg_count, ""); - }; - - defer env.discard(result_ref); - - try env.push_ref(result_ref); - }, - - .syscall => |arg_count| { - const result_ref = call: { - const identifier_ref = try env.pop_local() orelse { - return env.raise(error.TypeMismatch, "nil is not syscallable"); - }; - - defer env.discard(identifier_ref); - - const identifier = try kym.unbox_string(env, identifier_ref); - - break: call try env.call(try env.syscallable(identifier), arg_count, identifier); + break: call try env.call(callable_ref, arg_count, ""); }; defer env.discard(result_ref); @@ -644,10 +654,6 @@ pub fn free(self: *Self) void { self.constant_refs.free(); } -fn is_zero(utf8: []const u8) bool { - return coral.io.equals(utf8, "0") or coral.io.equals(utf8, "0.0"); -} - pub fn make(env: *kym.RuntimeEnv) Self { return Self{ .opcodes = OpcodeList.make(env.allocator), diff --git a/source/ona/kym/tokens.zig b/source/ona/kym/tokens.zig index 7aa0e61..bc916d4 100755 --- a/source/ona/kym/tokens.zig +++ b/source/ona/kym/tokens.zig @@ -5,7 +5,7 @@ pub const Token = union(enum) { unknown: coral.io.Byte, newline, - special_identifier: []const coral.io.Byte, + system_identifier: []const coral.io.Byte, identifier: []const coral.io.Byte, symbol_plus, @@ -46,7 +46,7 @@ pub const Token = union(enum) { .unknown => |unknown| @as([*]const coral.io.Byte, @ptrCast(&unknown))[0 .. 1], .newline => "newline", - .special_identifier => |identifier| identifier, + .system_identifier => |identifier| identifier, .identifier => |identifier| identifier, .symbol_plus => "+", @@ -224,7 +224,7 @@ pub const Tokenizer = struct { else => break, }; - self.token = .{.special_identifier = self.source[begin .. cursor]}; + self.token = .{.system_identifier = self.source[begin .. cursor]}; return; }, @@ -241,7 +241,7 @@ pub const Tokenizer = struct { else => cursor += 1, }; - self.token = .{.special_identifier = self.source[begin .. cursor]}; + self.token = .{.system_identifier = self.source[begin .. cursor]}; cursor += 1; return; diff --git a/source/ona/ona.zig b/source/ona/ona.zig index 43897b1..212ef85 100644 --- a/source/ona/ona.zig +++ b/source/ona/ona.zig @@ -69,21 +69,16 @@ pub fn run_app(file_access: file.Access) void { defer script_env.free(); - script_env.bind_syscalls(&.{ - .{ - .name = "log_info", - .caller = kym.Caller.from(kym_log_info), - }, - .{ - .name = "log_warn", - .caller = kym.Caller.from(kym_log_warn), - }, - .{ - .name = "log_fail", - .caller = kym.Caller.from(kym_log_fail), - }, - }) catch { - return app.log_fail("failed to bind syscalls to script runtime"); + script_env.bind_syscaller("log_info", kym.Caller.from(kym_log_info)) catch { + return app.log_fail("failed to bind `log_info` syscall"); + }; + + script_env.bind_syscaller("log_warn", kym.Caller.from(kym_log_warn)) catch { + return app.log_fail("failed to bind `log_warn` syscall"); + }; + + script_env.bind_syscaller("log_fail", kym.Caller.from(kym_log_fail)) catch { + return app.log_fail("failed to bind `log_fail` syscall"); }; var manifest = app.Manifest{};