diff --git a/debug/app.ona b/debug/app.ona index 1cea8e2..175db2a 100644 --- a/debug/app.ona +++ b/debug/app.ona @@ -1,33 +1,18 @@ -let printer = lambda (pfx): +let test_param = "monkey wrench" + +let printer = lambda (pfx, arbitrary): + @print(test_param) + return lambda (msg): @print(pfx) @print(msg) end end -let pr = printer("This is a func call") -var i = 0 +let pr = printer("this is a final closure") -pr("test") - -while i < 5: - pr("hello, world") - - i = i + 1 -end - -if i > 6: - pr("`i` greater than `6`") -elif i == 4: - pr("`i` is equal to `4`") -else: - pr("i'unno") -end - -let pr2 = printer("this is a final closure") - -pr2("goodbye") +pr("goodbye") return { .title = "Game", diff --git a/source/coral/utf8.zig b/source/coral/utf8.zig index dee3f8b..7ca7d52 100644 --- a/source/coral/utf8.zig +++ b/source/coral/utf8.zig @@ -196,6 +196,8 @@ pub const HexadecimalFormat = struct { _ = self; _ = writer; _ = value; + + return null; } }; diff --git a/source/ona/app.zig b/source/ona/app.zig index 10ab8e1..0ef5952 100644 --- a/source/ona/app.zig +++ b/source/ona/app.zig @@ -21,7 +21,7 @@ pub const Manifest = struct { if (try kym.get_field(env, manifest, "width")) |ref| { defer env.discard(ref); - const fixed = try env.unbox_fixed(ref); + const fixed = try env.unwrap_fixed(ref); if (fixed > 0 and fixed < coral.math.max_int(@typeInfo(@TypeOf(self.width)).Int)) { break: get @intCast(fixed); @@ -35,7 +35,7 @@ pub const Manifest = struct { if (try kym.get_field(env, manifest, "height")) |ref| { defer env.discard(ref); - const fixed = try env.unbox_fixed(ref); + const fixed = try env.unwrap_fixed(ref); if (fixed > 0 and fixed < coral.math.max_int(@typeInfo(@TypeOf(self.height)).Int)) { break: get @intCast(fixed); @@ -49,7 +49,7 @@ pub const Manifest = struct { if (try kym.get_field(env, manifest, "tick_rate")) |ref| { defer env.discard(ref); - break: get @floatCast(try env.unbox_float(ref)); + break: get @floatCast(try env.unwrap_float(ref)); } break: get self.tick_rate; @@ -58,7 +58,7 @@ pub const Manifest = struct { if (try kym.get_field(env, manifest, "title")) |ref| { defer env.discard(ref); - const title_string = try env.unbox_string(ref); + const title_string = try env.unwrap_string(ref); const limited_title_len = @min(title_string.len, self.title.len); coral.io.copy(&self.title, title_string[0 .. limited_title_len]); diff --git a/source/ona/kym.zig b/source/ona/kym.zig index 12fbf32..631a234 100644 --- a/source/ona/kym.zig +++ b/source/ona/kym.zig @@ -19,8 +19,8 @@ pub const Frame = struct { return env.locals.values[self.locals_top .. (self.locals_top + self.arg_count)]; } - pub fn get_arg(self: *const Frame, env: *RuntimeEnv, arg_index: u8) RuntimeError!*const RuntimeRef { - return self.has_arg(env, arg_index) orelse env.raise(error.BadOperation, "nil reference"); + pub fn expect_arg(self: *const Frame, env: *RuntimeEnv, arg_index: u8) RuntimeError!*const RuntimeRef { + return self.has_arg(env, arg_index) orelse env.raise(error.TypeMismatch, "nil reference", .{}); } pub fn has_arg(self: *const Frame, env: *RuntimeEnv, arg_index: u8) ?*const RuntimeRef { @@ -65,16 +65,24 @@ pub const RuntimeEnv = struct { }, .float => |rhs_float| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) + rhs_float), - else => self.raise(error.TypeMismatch, "right-hand object is not addable"), + + else => self.raise(error.TypeMismatch, "right-hand {typename} is not addable", .{ + .typename = rhs.typename(), + }), }, .float => |lhs_float| switch (rhs.object().payload) { .float => |rhs_float| self.new_float(lhs_float + rhs_float), .fixed => |rhs_fixed| self.new_float(lhs_float + @as(Float, @floatFromInt(rhs_fixed))), - else => self.raise(error.TypeMismatch, "right-hand object is not addable"), + + else => self.raise(error.TypeMismatch, "right-hand {typename} is not addable", .{ + .typename = rhs.typename(), + }), }, - else => self.raise(error.TypeMismatch, "left-hand object is not addable"), + else => self.raise(error.TypeMismatch, "left-hand {typename} is not addable", .{ + .typename = lhs.typename(), + }), }; } @@ -95,7 +103,7 @@ pub const RuntimeEnv = struct { return switch (callable.object().payload) { .syscall => |syscall| syscall(self, frame), .dynamic => |dynamic| dynamic.typeinfo().call(self, dynamic.userdata(), frame), - else => self.raise(error.TypeMismatch, "object is not callable"), + else => self.raise(error.TypeMismatch, "{typename} is not callable", .{.typename = callable.typename()}), }; } @@ -104,16 +112,24 @@ pub const RuntimeEnv = struct { .fixed => |lhs_fixed| switch (rhs.object().payload) { .fixed => |rhs_fixed| @as(Float, @floatFromInt(lhs_fixed)) - @as(Float, @floatFromInt(rhs_fixed)), .float => |rhs_float| @as(Float, @floatFromInt(lhs_fixed)) - rhs_float, - else => return self.raise(error.TypeMismatch, "right-hand object is not comparable"), + + else => return self.raise(error.TypeMismatch, "right-hand {typename} is not comparable", .{ + .typename = rhs.typename(), + }), }, .float => |lhs_float| switch (rhs.object().payload) { .float => |rhs_float| lhs_float - rhs_float, .fixed => |rhs_fixed| lhs_float - @as(Float, @floatFromInt(rhs_fixed)), - else => return self.raise(error.TypeMismatch, "right-hand object is not comparable"), + + else => return self.raise(error.TypeMismatch, "right-hand {typename} is not comparable", .{ + .typename = rhs.typename(), + }), }, - else => return self.raise(error.TypeMismatch, "left-hand object is not comparable"), + else => return self.raise(error.TypeMismatch, "left-hand {typename} is not comparable", .{ + .typename = lhs.typename(), + }), }; } @@ -157,21 +173,29 @@ pub const RuntimeEnv = struct { .fixed => |lhs_fixed| switch (rhs.object().payload) { .fixed => |rhs_fixed| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) / @as(Float, @floatFromInt(rhs_fixed))), .float => |rhs_float| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) / rhs_float), - else => self.raise(error.TypeMismatch, "right-hand object is not divisible"), + + else => self.raise(error.TypeMismatch, "right-hand {typename} is not divisible", .{ + .typename = rhs.typename(), + }), }, .float => |lhs_float| switch (rhs.object().payload) { .float => |rhs_float| self.new_float(lhs_float / rhs_float), .fixed => |rhs_fixed| self.new_float(lhs_float / @as(Float, @floatFromInt(rhs_fixed))), - else => self.raise(error.TypeMismatch, "right-hand object is not divisible"), + + else => self.raise(error.TypeMismatch, "right-hand {typename} is not divisible", .{ + .typename = rhs.typename(), + }), }, - else => self.raise(error.TypeMismatch, "left-hand object is not divisible"), + else => self.raise(error.TypeMismatch, "left-hand {typename} is not divisible", .{ + .typename = lhs.typename(), + }), }; } pub fn expect(self: *RuntimeEnv, value: ?*RuntimeRef) RuntimeError!*RuntimeRef { - return value orelse self.raise(error.TypeMismatch, "nil reference"); + return value orelse self.raise(error.TypeMismatch, "nil reference", .{}); } pub fn free(self: *RuntimeEnv) void { @@ -196,17 +220,8 @@ pub const RuntimeEnv = struct { pub fn get(self: *RuntimeEnv, indexable: *RuntimeRef, index: *const RuntimeRef) RuntimeError!?*RuntimeRef { return switch (indexable.object().payload) { - .false => self.raise(error.TypeMismatch, "false is not get-indexable"), - .true => self.raise(error.TypeMismatch, "true is not get-indexable"), - .fixed => self.raise(error.TypeMismatch, "fixed is not get-indexable"), - .float => self.raise(error.TypeMismatch, "float is not get-indexable"), - .string => self.raise(error.TypeMismatch, "string is not get-indexable"), - .symbol => self.raise(error.TypeMismatch, "symbol is not get-indexable"), - .syscall => self.raise(error.TypeMismatch, "syscall is not get-indexable"), - .boxed => self.raise(error.TypeMismatch, "boxed is not get-indexable"), - .vector2 => |vector2| swizzle: { - const swizzle_symbol = try self.unbox_symbol(index); + const swizzle_symbol = try self.unwrap_symbol(index); var swizzle_buffer = [_]f32{0} ** 3; var swizzle_count = @as(usize, 0); @@ -231,7 +246,7 @@ pub const RuntimeEnv = struct { }, .vector3 => |vector3| swizzle: { - const swizzle_symbol = try self.unbox_symbol(index); + const swizzle_symbol = try self.unwrap_symbol(index); var swizzle_buffer = [_]f32{0} ** 3; var swizzle_count = @as(usize, 0); @@ -257,6 +272,10 @@ pub const RuntimeEnv = struct { }, .dynamic => |dynamic| dynamic.typeinfo().get(self, dynamic.userdata(), index), + + else => self.raise(error.TypeMismatch, "{typename} is not get-indexable", .{ + .typename = indexable.typename(), + }), }; } @@ -266,7 +285,9 @@ pub const RuntimeEnv = struct { var chunk = make_chunk: { const file_data = (try file.allocate_and_load(self.allocator, self.options.import_access, file_path)) orelse { - return self.raise(error.BadOperation, "failed to open or read file specified"); + return self.raise(error.BadOperation, "failed to open or read `{name}`", .{ + .name = file_name, + }); }; defer self.allocator.deallocate(file_data); @@ -287,7 +308,11 @@ pub const RuntimeEnv = struct { }; } - break: make_chunk try Chunk.make(self, file_name, &root.environment); + const chunk_name = try self.new_string(file_name); + + defer self.discard(chunk_name); + + break: make_chunk try Chunk.make(self, chunk_name, &root.environment); }; defer chunk.free(self); @@ -327,16 +352,24 @@ pub const RuntimeEnv = struct { }, .float => |rhs_float| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) * rhs_float), - else => self.raise(error.TypeMismatch, "right-hand object is not multiplicable"), + + else => self.raise(error.TypeMismatch, "right-hand {typename} is not multiplicable", .{ + .typename = rhs.typename(), + }), }, .float => |lhs_float| switch (rhs.object().payload) { .float => |rhs_float| self.new_float(lhs_float * rhs_float), .fixed => |rhs_fixed| self.new_float(lhs_float * @as(Float, @floatFromInt(rhs_fixed))), - else => self.raise(error.TypeMismatch, "right-hand object is not multiplicable"), + + else => self.raise(error.TypeMismatch, "right-hand {typename} is not multiplicable", .{ + .typename = rhs.typename(), + }), }, - else => self.raise(error.TypeMismatch, "left-hand object is not multiplicable"), + else => self.raise(error.TypeMismatch, "left-hand {typename} is not multiplicable", .{ + .typename = lhs.typename(), + }), }; } @@ -344,7 +377,7 @@ pub const RuntimeEnv = struct { return switch (value.object().payload) { .fixed => |fixed| self.new_fixed(-fixed), .float => |float| self.new_float(-float), - else => self.raise(error.TypeMismatch, "object is not negatable"), + else => self.raise(error.TypeMismatch, "{typename} is not negatable", .{.typename = value.typename()}), }; } @@ -498,7 +531,7 @@ pub const RuntimeEnv = struct { } pub fn pop_local(self: *RuntimeEnv) RuntimeError!?*RuntimeRef { - return self.locals.pop() orelse self.raise(error.IllegalState, "stack underflow"); + return self.locals.pop() orelse self.raise(error.IllegalState, "stack underflow", .{}); } pub fn push_frame(self: *RuntimeEnv, name_stringable: *RuntimeRef, arg_count: u8) RuntimeError!Frame { @@ -513,8 +546,14 @@ pub const RuntimeEnv = struct { return frame; } - pub fn raise(self: *RuntimeEnv, error_value: RuntimeError, message: []const coral.io.Byte) RuntimeError { - self.print_error(message); + pub fn raise(self: *RuntimeEnv, error_value: RuntimeError, comptime format: []const coral.io.Byte, args: anytype) RuntimeError { + { + const formatted_message = try coral.utf8.alloc_formatted(self.allocator, format, args); + + defer self.allocator.deallocate(formatted_message); + + self.print_error(formatted_message); + } if (!self.frames.is_empty()) { self.print_error("stack trace:"); @@ -544,7 +583,10 @@ pub const RuntimeEnv = struct { pub fn set(self: *RuntimeEnv, indexable: *RuntimeRef, index: *const RuntimeRef, value: ?*const RuntimeRef) RuntimeError!void { return switch (indexable.object().payload) { .dynamic => |dynamic| dynamic.typeinfo().set(self, dynamic.userdata(), index, value), - else => self.raise(error.TypeMismatch, "object is not set-indexable"), + + else => self.raise(error.TypeMismatch, "{typename} is not set-indexable", .{ + .typename = indexable.typename(), + }), }; } @@ -560,16 +602,24 @@ pub const RuntimeEnv = struct { }, .float => |rhs_float| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) - rhs_float), - else => self.raise(error.TypeMismatch, "right-hand object is not subtractable"), + + else => self.raise(error.TypeMismatch, "right-hand {typename} is not subtractable", .{ + .typename = rhs.typename(), + }), }, .float => |lhs_float| switch (rhs.object().payload) { .float => |rhs_float| self.new_float(lhs_float - rhs_float), .fixed => |rhs_fixed| self.new_float(lhs_float - @as(Float, @floatFromInt(rhs_fixed))), - else => self.raise(error.TypeMismatch, "right-hand object is not subtractable"), + + else => self.raise(error.TypeMismatch, "right-hand {typename} is not subtractable", .{ + .typename = rhs.typename(), + }), }, - else => self.raise(error.TypeMismatch, "left-hand object is not subtractable"), + else => self.raise(error.TypeMismatch, "left-hand {typename} is not subtractable", .{ + .typename = lhs.typename(), + }), }; } @@ -638,34 +688,37 @@ pub const RuntimeEnv = struct { }; } - pub fn unbox_dynamic(self: *RuntimeEnv, value: *const RuntimeRef) RuntimeError![]coral.io.Byte { + pub fn unwrap_dynamic(self: *RuntimeEnv, value: *const RuntimeRef) RuntimeError![]coral.io.Byte { return switch (value.object().payload) { .dynamic => |dynamic| dynamic.userdata(), else => self.raise(error.TypeMismatch, "expected fixed object") }; } - pub fn unbox_float(self: *RuntimeEnv, value: *const RuntimeRef) RuntimeError!Float { + pub fn unwrap_float(self: *RuntimeEnv, value: *const RuntimeRef) RuntimeError!Float { return switch (value.object().payload) { .fixed => |fixed| @floatFromInt(fixed), .float => |float| float, - else => self.raise(error.TypeMismatch, "expected float object") + else => self.raise(error.TypeMismatch, "expected float, not {typename}", .{.typename = value.typename()}), }; } - pub fn unbox_fixed(self: *RuntimeEnv, value: *const RuntimeRef) RuntimeError!Fixed { - return value.as_fixed() orelse self.raise(error.TypeMismatch, "expected fixed object"); + pub fn unwrap_fixed(self: *RuntimeEnv, value: *const RuntimeRef) RuntimeError!Fixed { + return value.as_fixed() orelse self.raise(error.TypeMismatch, "expected fixed, not {typename}", .{ + .typename = value.typename() + }); } - pub fn unbox_string(self: *RuntimeEnv, value: *const RuntimeRef) RuntimeError![]const coral.io.Byte { - return value.as_string() orelse self.raise(error.TypeMismatch, "expected string object"); + pub fn unwrap_string(self: *RuntimeEnv, value: *const RuntimeRef) RuntimeError![]const coral.io.Byte { + return value.as_string() orelse self.raise(error.TypeMismatch, "expected string, not {typename}", .{ + .typename = value.typename(), + }); } - pub fn unbox_symbol(self: *RuntimeEnv, value: *const RuntimeRef) RuntimeError![*:0]const coral.io.Byte { - return switch (value.object().payload) { - .symbol => |symbol| symbol, - else => self.raise(error.TypeMismatch, "expected symbol object") - }; + pub fn unwrap_symbol(self: *RuntimeEnv, value: *const RuntimeRef) RuntimeError![*:0]const coral.io.Byte { + return value.as_symbol() orelse self.raise(error.TypeMismatch, "expected symbol, not {typename}", .{ + .typename = value.typename(), + }); } }; @@ -775,6 +828,13 @@ pub const RuntimeRef = opaque { }; } + pub fn as_symbol(self: *const RuntimeRef) ?[*:0]const coral.io.Byte { + return switch (self.object().payload) { + .symbol => |symbol| symbol, + else => null, + }; + } + fn object(self: *const RuntimeRef) *Object { return @constCast(@ptrCast(@alignCast(self))); } @@ -875,6 +935,7 @@ pub const RuntimeRef = opaque { .vector2 => "vector2", .vector3 => "vector3", .syscall => "syscall", + .boxed => "boxed", .string => "string", .dynamic => "dynamic", }; @@ -892,15 +953,15 @@ pub const Typeinfo = struct { set: *const fn (env: *RuntimeEnv, userdata: []coral.io.Byte, value: *const RuntimeRef, value: ?*const RuntimeRef) RuntimeError!void = default_set, fn default_call(env: *RuntimeEnv, _: []coral.io.Byte, _: Frame) RuntimeError!?*RuntimeRef { - return env.raise(error.BadOperation, "object is not callable"); + return env.raise(error.BadOperation, "this dynamic object is not callable", .{}); } fn default_get(env: *RuntimeEnv, _: []coral.io.Byte, _: *const RuntimeRef) RuntimeError!?*RuntimeRef { - return env.raise(error.BadOperation, "object is not get-indexable"); + return env.raise(error.BadOperation, "this dynamic object is not get-indexable", .{}); } fn default_set(env: *RuntimeEnv, _: []coral.io.Byte, _: *const RuntimeRef, _: ?*const RuntimeRef) RuntimeError!void { - return env.raise(error.BadOperation, "object is not set-indexable"); + return env.raise(error.BadOperation, "this dynamic object is not set-indexable", .{}); } }; diff --git a/source/ona/kym/Chunk.zig b/source/ona/kym/Chunk.zig index a127f2e..0051c4c 100644 --- a/source/ona/kym/Chunk.zig +++ b/source/ona/kym/Chunk.zig @@ -32,6 +32,7 @@ const Builtin = enum { const ConstList = coral.list.Stack(*kym.RuntimeRef); const OpcodeList = coral.list.Stack(union (enum) { + ret, pop, push_nil, push_true, @@ -85,6 +86,7 @@ pub fn dump(chunk: Self, env: *kym.RuntimeEnv) kym.RuntimeError!*kym.RuntimeRef _ = coral.utf8.print_formatted(writer, "[{instruction}]: ", .{.instruction = opcode_cursor}); _ = switch (chunk.opcodes.values[opcode_cursor]) { + .ret => coral.utf8.print_string(writer, "ret\n"), .pop => coral.utf8.print_string(writer, "pop\n"), .push_nil => coral.utf8.print_string(writer, "push nil\n"), .push_true => coral.utf8.print_string(writer, "push true\n"), @@ -92,7 +94,7 @@ pub fn dump(chunk: Self, env: *kym.RuntimeEnv) kym.RuntimeError!*kym.RuntimeRef .push_const => |push_const| print: { if (push_const >= chunk.constants.values.len) { - return env.raise(error.IllegalState, "invalid constant"); + return env.raise(error.IllegalState, "invalid constant", .{}); } const string_ref = try env.to_string(chunk.constants.values[push_const]); @@ -168,6 +170,8 @@ pub fn execute(self: Self, env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeEr while (opcode_cursor < self.opcodes.values.len) : (opcode_cursor += 1) { switch (self.opcodes.values[opcode_cursor]) { + .ret => break, + .pop => { if (try env.pop_local()) |ref| { env.discard(ref); @@ -180,15 +184,15 @@ pub fn execute(self: Self, env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeEr .push_const => |push_const| { if (push_const >= self.constants.values.len) { - return env.raise(error.IllegalState, "invalid constant"); + return env.raise(error.IllegalState, "invalid constant", .{}); } try env.locals.push_one(self.constants.values[push_const].acquire()); }, .push_local => |push_local| { - if (push_local >= env.locals.values.len) { - return env.raise(error.IllegalState, "invalid local"); + if (push_local >= (env.locals.values.len - frame.locals_top)) { + return env.raise(error.IllegalState, "invalid local", .{}); } if (env.locals.values[frame.locals_top + push_local]) |local| { @@ -202,7 +206,7 @@ pub fn execute(self: Self, env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeEr const frame_locals = env.locals.values[frame.locals_top ..]; if (frame_locals.len == 0) { - return env.raise(error.IllegalState, "stack overflow"); + return env.raise(error.IllegalState, "stack overflow", .{}); } if (frame_locals[frame_locals.len - 1]) |local| { @@ -254,7 +258,7 @@ pub fn execute(self: Self, env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeEr .push_binding => |push_binding| { if (push_binding > self.bindings.len) { - return env.raise(error.IllegalState, "binding out of range"); + return env.raise(error.IllegalState, "binding out of range", .{}); } try env.locals.push_one(if (self.bindings[push_binding]) |value| value.acquire() else null); @@ -266,7 +270,9 @@ pub fn execute(self: Self, env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeEr errdefer env.discard(lambda); const chunk = @as(*Self, @ptrCast(@alignCast(lambda.as_dynamic(typeinfo) orelse { - return env.raise(error.IllegalState, "cannot bind to non-chunk"); + return env.raise(error.IllegalState, "cannot bind objects to a {typename}", .{ + .typename = lambda.typename(), + }); }))); chunk.bindings = try coral.io.allocate_many(env.allocator, bind, @as(?*kym.RuntimeRef, null)); @@ -321,7 +327,7 @@ pub fn execute(self: Self, env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeEr defer env.discard(box); const boxed = box.as_boxed() orelse { - return env.raise(error.TypeMismatch, "type is not unboxable"); + return env.raise(error.TypeMismatch, "{typename} is not unboxable", .{.typename = box.typename()}); }; try env.locals.push_one(if (boxed.*) |value| value.acquire() else null); @@ -333,7 +339,7 @@ pub fn execute(self: Self, env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeEr errdefer env.discard(box); const boxed = box.as_boxed() orelse { - return env.raise(error.TypeMismatch, "type is not unboxable"); + return env.raise(error.TypeMismatch, "{typename} is not unboxable", .{.typename = box.typename()}); }; if (boxed.*) |value| { @@ -372,15 +378,11 @@ pub fn execute(self: Self, env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeEr } } - const index = try env.pop_local() orelse { - return env.raise(error.TypeMismatch, "nil is not a valid index"); - }; + const index = try env.expect(try env.pop_local()); defer env.discard(index); - const indexable = try env.pop_local() orelse { - return env.raise(error.TypeMismatch, "nil is not a valid indexable"); - }; + const indexable = try env.expect(try env.pop_local()); defer env.discard(indexable); @@ -594,13 +596,13 @@ pub fn free(self: *Self, env: *kym.RuntimeEnv) void { self.bindings = &.{}; } -pub fn make(env: *kym.RuntimeEnv, name: []const coral.io.Byte, environment: *const tree.Environment) kym.RuntimeError!Self { +pub fn make(env: *kym.RuntimeEnv, name: *const kym.RuntimeRef, environment: *const tree.Environment) kym.RuntimeError!Self { var chunk = Self{ - .name = try env.new_symbol(name), + .name = name.acquire(), .opcodes = OpcodeList.make(env.allocator), .constants = ConstList.make(env.allocator), .bindings = &.{}, - .arity = 0, + .arity = environment.argument_count, }; var compiler = Compiler{ @@ -611,7 +613,15 @@ pub fn make(env: *kym.RuntimeEnv, name: []const coral.io.Byte, environment: *con try compiler.compile_environment(environment); if (builtin.mode == .Debug) { - const allocation = try coral.utf8.alloc_formatted(env.allocator, "compiled {name}...", .{.name = name}); + const name_string = try env.to_string(name); + + defer env.discard(name_string); + + const name_bytes = name_string.as_string(); + + coral.debug.assert(name_bytes != null); + + const allocation = try coral.utf8.alloc_formatted(env.allocator, "compiled {name}...", .{.name = name_bytes.?}); defer env.allocator.deallocate(allocation); @@ -631,33 +641,33 @@ pub fn make(env: *kym.RuntimeEnv, name: []const coral.io.Byte, environment: *con } fn syscall_import(env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeError!?*kym.RuntimeRef { - return env.import(file.Path.from(&.{try env.unbox_string(try frame.get_arg(env, 0))})); + return env.import(file.Path.from(&.{try env.unwrap_string(try frame.expect_arg(env, 0))})); } fn syscall_print(env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeError!?*kym.RuntimeRef { - env.print(try env.unbox_string(try frame.get_arg(env, 0))); + env.print(try env.unwrap_string(try frame.expect_arg(env, 0))); return null; } fn syscall_vec2(env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeError!?*kym.RuntimeRef { - const x = @as(f32, @floatCast(try env.unbox_float(try frame.get_arg(env, 0)))); + const x = @as(f32, @floatCast(try env.unwrap_float(try frame.expect_arg(env, 0)))); if (frame.has_arg(env, 1)) |y| { - return env.new_vector2(x, @floatCast(try env.unbox_float(y))); + return env.new_vector2(x, @floatCast(try env.unwrap_float(y))); } return env.new_vector2(x, x); } fn syscall_vec3(env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeError!?*kym.RuntimeRef { - const x = @as(f32, @floatCast(try env.unbox_float(try frame.get_arg(env, 0)))); + const x = @as(f32, @floatCast(try env.unwrap_float(try frame.expect_arg(env, 0)))); if (frame.has_arg(env, 1)) |y| { return env.new_vector3( x, - @floatCast(try env.unbox_float(y)), - @floatCast(try env.unbox_float(try frame.get_arg(env, 2))), + @floatCast(try env.unwrap_float(y)), + @floatCast(try env.unwrap_float(try frame.expect_arg(env, 2))), ); } @@ -675,7 +685,11 @@ fn typeinfo_call(env: *kym.RuntimeEnv, userdata: []coral.io.Byte, frame: kym.Fra const chunk = @as(*Self, @ptrCast(@alignCast(userdata))); if (frame.arg_count < chunk.arity) { - return env.raise(error.BadOperation, "expected more arguments"); + return env.raise(error.BadOperation, "expected `{expected_count}` {noun}, {provided_count} provided", .{ + .expected_count = frame.arg_count, + .provided_count = chunk.arity, + .noun = if (frame.arg_count == 1) "argument" else "arguments", + }); } return chunk.execute(env, frame); diff --git a/source/ona/kym/Compiler.zig b/source/ona/kym/Compiler.zig index ed2dcfb..d41736a 100644 --- a/source/ona/kym/Compiler.zig +++ b/source/ona/kym/Compiler.zig @@ -21,7 +21,7 @@ fn compile_argument(self: Self, environment: *const tree.Environment, initial_ar var argument_count = @as(u8, 0); while (maybe_argument) |argument| { - try self.compile_expression(environment, argument); + try self.compile_expression(environment, argument, null); maybe_argument = argument.next; argument_count += 1; @@ -30,7 +30,7 @@ fn compile_argument(self: Self, environment: *const tree.Environment, initial_ar return argument_count; } -fn compile_expression(self: Self, environment: *const tree.Environment, expression: *const Expr) kym.RuntimeError!void { +fn compile_expression(self: Self, environment: *const tree.Environment, expression: *const Expr, name: ?[]const coral.io.Byte) kym.RuntimeError!void { const number_format = coral.utf8.DecimalFormat{ .delimiter = "_", .positive_prefix = .none, @@ -68,7 +68,7 @@ fn compile_expression(self: Self, environment: *const tree.Environment, expressi var field_count = @as(u32, 0); while (table_entry) |entry| : (table_entry = entry.next) { - try self.compile_expression(environment, entry); + try self.compile_expression(environment, entry, null); if (entry.kind != .key_value) { try self.chunk.opcodes.push_one(.push_top); @@ -81,33 +81,42 @@ fn compile_expression(self: Self, environment: *const tree.Environment, expressi }, .key_value => |key_value| { - try self.compile_expression(environment, key_value.value); - try self.compile_expression(environment, key_value.key); + try self.compile_expression(environment, key_value.value, null); + try self.compile_expression(environment, key_value.key, null); }, .lambda_construct => |lambda_construct| { - var chunk = try Chunk.make(self.env, "", lambda_construct.environment); + const anonymous_chunk_name = try self.env.new_symbol(name orelse ""); + + defer self.env.discard(anonymous_chunk_name); + + var chunk = try Chunk.make(self.env, anonymous_chunk_name, lambda_construct.environment); errdefer chunk.free(self.env); - if (lambda_construct.environment.capture_count == 0 and environment.capture_count == 0) { + if (lambda_construct.environment.capture_count == 0) { try self.chunk.opcodes.push_one(.{.push_const = try self.declare_chunk(chunk)}); } else { - for (environment.captures[0 .. environment.capture_count]) |capture| { - try self.chunk.opcodes.push_one(.{.push_local = environment.captures[capture]}); + const lambda_captures = lambda_construct.environment.get_captures(); + var index = lambda_captures.len; + + while (index != 0) { + index -= 1; + + try self.chunk.opcodes.push_one(switch (lambda_captures[index]) { + .declaration_index => |declaration_index| .{.push_local = declaration_index}, + .capture_index => |capture_index| .{.push_binding = capture_index}, + }); } try self.chunk.opcodes.push_one(.{.push_const = try self.declare_chunk(chunk)}); - - try self.chunk.opcodes.push_one(.{ - .bind = lambda_construct.environment.capture_count + environment.capture_count, - }); + try self.chunk.opcodes.push_one(.{.bind = lambda_construct.environment.capture_count}); } }, .binary_op => |binary_op| { - try self.compile_expression(environment, binary_op.lhs_operand); - try self.compile_expression(environment, binary_op.rhs_operand); + try self.compile_expression(environment, binary_op.lhs_operand, null); + try self.compile_expression(environment, binary_op.rhs_operand, null); try self.chunk.opcodes.push_one(switch (binary_op.operation) { .addition => .add, @@ -123,7 +132,7 @@ fn compile_expression(self: Self, environment: *const tree.Environment, expressi }, .unary_op => |unary_op| { - try self.compile_expression(environment, unary_op.operand); + try self.compile_expression(environment, unary_op.operand, null); try self.chunk.opcodes.push_one(switch (unary_op.operation) { .boolean_negation => .not, @@ -134,11 +143,11 @@ fn compile_expression(self: Self, environment: *const tree.Environment, expressi .invoke => |invoke| { const argument_count = try self.compile_argument(environment, invoke.argument); - try self.compile_expression(environment, invoke.object); + try self.compile_expression(environment, invoke.object, null); try self.chunk.opcodes.push_one(.{.call = argument_count}); }, - .group => |group| try self.compile_expression(environment, group), + .group => |group| try self.compile_expression(environment, group, null), .import_builtin => try self.chunk.opcodes.push_one(.{.push_builtin = .import}), .print_builtin => try self.chunk.opcodes.push_one(.{.push_builtin = .print}), .vec2_builtin => try self.chunk.opcodes.push_one(.{.push_builtin = .vec2}), @@ -149,7 +158,7 @@ fn compile_expression(self: Self, environment: *const tree.Environment, expressi return self.chunk.opcodes.push_one(.{.push_local = index}); } - if (get_capture_index(environment, declaration_get.declaration)) |index| { + if (get_binding_index(environment, declaration_get.declaration)) |index| { try self.chunk.opcodes.push_one(.{.push_binding = index}); if (is_declaration_boxed(declaration_get.declaration)) { @@ -159,19 +168,19 @@ fn compile_expression(self: Self, environment: *const tree.Environment, expressi return; } - return self.env.raise(error.IllegalState, "local out of scope"); + return self.env.raise(error.IllegalState, "local out of scope", .{}); }, .declaration_set => |declaration_set| { if (get_local_index(environment, declaration_set.declaration)) |index| { - try self.compile_expression(environment, declaration_set.assign); + try self.compile_expression(environment, declaration_set.assign, null); return self.chunk.opcodes.push_one(.{.set_local = index}); } - if (get_capture_index(environment, declaration_set.declaration)) |index| { + if (get_binding_index(environment, declaration_set.declaration)) |index| { try self.chunk.opcodes.push_one(.{.push_binding = index}); - try self.compile_expression(environment, declaration_set.assign); + try self.compile_expression(environment, declaration_set.assign, null); if (is_declaration_boxed(declaration_set.declaration)) { try self.chunk.opcodes.push_one(.set_box); @@ -180,32 +189,32 @@ fn compile_expression(self: Self, environment: *const tree.Environment, expressi return; } - return self.env.raise(error.IllegalState, "local out of scope"); + return self.env.raise(error.IllegalState, "local out of scope", .{}); }, .field_get => |field_get| { - try self.compile_expression(environment, field_get.object); + try self.compile_expression(environment, field_get.object, null); try self.chunk.opcodes.push_one(.{.push_const = try self.declare_symbol(field_get.identifier)}); try self.chunk.opcodes.push_one(.get_dynamic); }, .field_set => |field_set| { - try self.compile_expression(environment, field_set.object); + try self.compile_expression(environment, field_set.object, null); try self.chunk.opcodes.push_one(.{.push_const = try self.declare_symbol(field_set.identifier)}); - try self.compile_expression(environment, field_set.assign); + try self.compile_expression(environment, field_set.assign, null); try self.chunk.opcodes.push_one(.set_dynamic); }, .subscript_get => |subscript_get| { - try self.compile_expression(environment, subscript_get.object); - try self.compile_expression(environment, subscript_get.index); + try self.compile_expression(environment, subscript_get.object, null); + try self.compile_expression(environment, subscript_get.index, null); try self.chunk.opcodes.push_one(.get_dynamic); }, .subscript_set => |subscript_set| { - try self.compile_expression(environment, subscript_set.object); - try self.compile_expression(environment, subscript_set.index); - try self.compile_expression(environment, subscript_set.assign); + try self.compile_expression(environment, subscript_set.object, null); + try self.compile_expression(environment, subscript_set.index, null); + try self.compile_expression(environment, subscript_set.assign, null); try self.chunk.opcodes.push_one(.set_dynamic); }, } @@ -228,14 +237,17 @@ fn compile_statement(self: Self, environment: *const tree.Environment, initial_s switch (current_statement.kind) { .@"return" => |@"return"| { if (@"return".returned_expression) |expression| { - try self.compile_expression(environment, expression); + try self.compile_expression(environment, expression, null); } else { try self.chunk.opcodes.push_one(.push_nil); } + + // TODO: Omit ret calls at ends of chunk. + try self.chunk.opcodes.push_one(.ret); }, .@"while" => |@"while"| { - try self.compile_expression(environment, @"while".loop_expression); + try self.compile_expression(environment, @"while".loop_expression, null); try self.chunk.opcodes.push_one(.{.jf = 0}); const origin_index = @as(u32, @intCast(self.chunk.opcodes.values.len - 1)); @@ -243,12 +255,12 @@ fn compile_statement(self: Self, environment: *const tree.Environment, initial_s _ = try self.compile_statement(environment, @"while".loop); self.chunk.opcodes.values[origin_index].jf = @intCast(self.chunk.opcodes.values.len - 1); - try self.compile_expression(environment, @"while".loop_expression); + try self.compile_expression(environment, @"while".loop_expression, null); try self.chunk.opcodes.push_one(.{.jt = origin_index}); }, .@"if" => |@"if"| { - try self.compile_expression(environment, @"if".then_expression); + try self.compile_expression(environment, @"if".then_expression, null); try self.chunk.opcodes.push_one(.{.jf = 0}); const origin_index = @as(u32, @intCast(self.chunk.opcodes.values.len - 1)); @@ -262,15 +274,15 @@ fn compile_statement(self: Self, environment: *const tree.Environment, initial_s }, .declare => |declare| { - try self.compile_expression(environment, declare.initial_expression); + try self.compile_expression(environment, declare.initial_expression, declare.declaration.identifier); - if (declare.declaration.is.captured and !declare.declaration.is.readonly) { + if (is_declaration_boxed(declare.declaration)) { try self.chunk.opcodes.push_one(.push_boxed); } }, .top_expression => |top_expression| { - try self.compile_expression(environment, top_expression); + try self.compile_expression(environment, top_expression, null); if (top_expression.kind == .invoke) { try self.chunk.opcodes.push_one(.pop); @@ -282,9 +294,13 @@ fn compile_statement(self: Self, environment: *const tree.Environment, initial_s } } +const constants_max = @as(usize, coral.math.max_int(@typeInfo(u16).Int)); + fn declare_chunk(self: Self, chunk: Chunk) kym.RuntimeError!u16 { if (self.chunk.constants.values.len == coral.math.max_int(@typeInfo(u16).Int)) { - return self.env.raise(error.BadSyntax, "chunks cannot contain more than 65,535 constants"); + return self.env.raise(error.BadSyntax, "chunks cannot contain more than {max} constants", .{ + .max = @as(usize, coral.math.max_int(@typeInfo(u16).Int)), + }); } const constant = try self.env.new_dynamic(coral.io.bytes_of(&chunk), Chunk.typeinfo); @@ -297,8 +313,10 @@ fn declare_chunk(self: Self, chunk: Chunk) kym.RuntimeError!u16 { } fn declare_fixed(self: Self, fixed: kym.Fixed) kym.RuntimeError!u16 { - if (self.chunk.constants.values.len == coral.math.max_int(@typeInfo(u16).Int)) { - return self.env.raise(error.BadSyntax, "chunks cannot contain more than 65,535 constants"); + if (self.chunk.constants.values.len == constants_max) { + return self.env.raise(error.BadSyntax, "chunks cannot contain more than {max} constants", .{ + .max = constants_max, + }); } const constant = try self.env.new_fixed(fixed); @@ -311,8 +329,10 @@ fn declare_fixed(self: Self, fixed: kym.Fixed) kym.RuntimeError!u16 { } fn declare_float(self: Self, float: kym.Float) kym.RuntimeError!u16 { - if (self.chunk.constants.values.len == coral.math.max_int(@typeInfo(u16).Int)) { - return self.env.raise(error.BadSyntax, "chunks cannot contain more than 65,535 constants"); + if (self.chunk.constants.values.len == constants_max) { + return self.env.raise(error.BadSyntax, "chunks cannot contain more than {max} constants", .{ + .max = constants_max, + }); } const constant = try self.env.new_float(float); @@ -325,8 +345,10 @@ fn declare_float(self: Self, float: kym.Float) kym.RuntimeError!u16 { } fn declare_string(self: Self, string: []const coral.io.Byte) kym.RuntimeError!u16 { - if (self.chunk.constants.values.len == coral.math.max_int(@typeInfo(u16).Int)) { - return self.env.raise(error.BadSyntax, "chunks cannot contain more than 65,535 constants"); + if (self.chunk.constants.values.len == constants_max) { + return self.env.raise(error.BadSyntax, "chunks cannot contain more than {max} constants", .{ + .max = constants_max, + }); } const constant = try self.env.new_string(string); @@ -339,8 +361,10 @@ fn declare_string(self: Self, string: []const coral.io.Byte) kym.RuntimeError!u1 } fn declare_symbol(self: Self, symbol: []const coral.io.Byte) kym.RuntimeError!u16 { - if (self.chunk.constants.values.len == coral.math.max_int(@typeInfo(u16).Int)) { - return self.env.raise(error.BadSyntax, "chunks cannot contain more than 65,535 constants"); + if (self.chunk.constants.values.len == constants_max) { + return self.env.raise(error.BadSyntax, "chunks cannot contain more than {max} constants", .{ + .max = constants_max, + }); } const constant = try self.env.new_symbol(symbol); @@ -352,31 +376,35 @@ fn declare_symbol(self: Self, symbol: []const coral.io.Byte) kym.RuntimeError!u1 return @intCast(self.chunk.constants.values.len - 1); } -pub fn get_capture_index(self: *const tree.Environment, declaration: *const tree.Declaration) ?u8 { - if (self.enclosing) |enclosing_environment| { - var capture_index = @as(u8, 0); +pub fn get_binding_index(environment: *const tree.Environment, declaration: *const tree.Declaration) ?u8 { + var binding_index = @as(u8, 0); - while (capture_index < self.capture_count) : (capture_index += 1) { - const captured_declaration_index = self.captures[capture_index]; + while (binding_index < environment.capture_count) : (binding_index += 1) { + var capture = &environment.captures[binding_index]; + var target_environment = environment.enclosing orelse return null; - coral.debug.assert(captured_declaration_index < enclosing_environment.declaration_count); + while (capture.* == .capture_index) { + capture = &target_environment.captures[capture.capture_index]; + target_environment = target_environment.enclosing orelse return null; + } - if (&enclosing_environment.declarations[captured_declaration_index] == declaration) { - return capture_index; - } + coral.debug.assert(capture.* == .declaration_index); + + if (&target_environment.declarations[capture.declaration_index] == declaration) { + return binding_index; } } return null; } -pub fn get_local_index(self: *const tree.Environment, declaration: *const tree.Declaration) ?u8 { - var remaining = self.declaration_count; +pub fn get_local_index(environment: *const tree.Environment, declaration: *const tree.Declaration) ?u8 { + var remaining = environment.declaration_count; while (remaining != 0) { remaining -= 1; - if (&self.declarations[remaining] == declaration) { + if (&environment.declarations[remaining] == declaration) { return remaining; } } diff --git a/source/ona/kym/tree.zig b/source/ona/kym/tree.zig index 1221899..814bd85 100644 --- a/source/ona/kym/tree.zig +++ b/source/ona/kym/tree.zig @@ -16,7 +16,7 @@ pub const Declaration = struct { }; pub const Environment = struct { - captures: [capture_max]u8 = [_]u8{0} ** capture_max, + captures: [capture_max]Capture = [_]Capture{.{.declaration_index = 0}} ** capture_max, capture_count: u8 = 0, declarations: [declaration_max]Declaration = [_]Declaration{.{.identifier = ""}} ** declaration_max, declaration_count: u8 = 0, @@ -24,6 +24,11 @@ pub const Environment = struct { statement: ?*const Stmt = null, enclosing: ?*Environment = null, + pub const Capture = union (enum) { + declaration_index: u8, + capture_index: u8, + }; + const DeclareError = coral.io.AllocationError || error { DeclarationExists, }; @@ -93,7 +98,7 @@ pub const Environment = struct { pub fn resolve_declaration(self: *Environment, identifier: []const coral.io.Byte) coral.io.AllocationError!?*const Declaration { var environment = self; - var ancestry = @as(usize, 0); + var ancestry = @as(u32, 0); while (true) : (ancestry += 1) { var remaining_count = environment.declaration_count; @@ -107,19 +112,27 @@ pub const Environment = struct { if (ancestry != 0) { declaration.is.captured = true; environment = self; + ancestry -= 1; while (ancestry != 0) : (ancestry -= 1) { if (environment.capture_count == environment.captures.len) { return error.OutOfMemory; } - environment.captures[environment.capture_count] = remaining_count; - environment.capture_count += 1; - coral.debug.assert(environment.enclosing != null); - environment = environment.enclosing.?; + const enclosing_environment = environment.enclosing.?; + + environment.captures[environment.capture_count] = .{ + .capture_index = enclosing_environment.capture_count + }; + + environment.capture_count += 1; + environment = enclosing_environment; } + + environment.captures[environment.capture_count] = .{.declaration_index = remaining_count}; + environment.capture_count += 1; } return declaration; @@ -129,6 +142,14 @@ pub const Environment = struct { environment = environment.enclosing orelse return null; } } + + pub fn get_captures(self: *const Environment) []const Capture { + return self.captures[0 .. self.capture_count]; + } + + pub fn get_declarations(self: *const Environment) []const Declaration { + return self.declarations[0 .. self.declaration_count]; + } }; pub const ParseError = coral.io.AllocationError || error {