diff --git a/debug/app.ona b/debug/app.ona index 48b8ee8..c0a0c69 100644 --- a/debug/app.ona +++ b/debug/app.ona @@ -1,18 +1,25 @@ var i = 0 +let pr = lambda (str): + @print("This is a func call") + @print(str) +end + +pr("") + while i < 5: - @print("hello, world") + pr("hello, world") i = i + 1 end if i > 6: - @print("`i` greater than `6`") + pr("`i` greater than `6`") elif i == 4: - @print("`i` is equal to `4`") + pr("`i` is equal to `4`") else: - @print("i'unno") + pr("i'unno") end return { diff --git a/source/ona/app.zig b/source/ona/app.zig index fc3aa33..10ab8e1 100644 --- a/source/ona/app.zig +++ b/source/ona/app.zig @@ -13,7 +13,7 @@ pub const Manifest = struct { tick_rate: f32 = 60.0, pub fn load(self: *Manifest, env: *kym.RuntimeEnv) kym.RuntimeError!void { - const manifest = try env.expect(try env.import(file.Path.from(&.{"app.ona"}))); + const manifest = try env.import(file.Path.from(&.{"app.ona"})) orelse return; defer env.discard(manifest); diff --git a/source/ona/kym.zig b/source/ona/kym.zig index 5415f5b..a310dc0 100644 --- a/source/ona/kym.zig +++ b/source/ona/kym.zig @@ -4,6 +4,24 @@ const coral = @import("coral"); const file = @import("./file.zig"); +pub const Frame = struct { + name: []const coral.io.Byte = "", + arg_count: u8, + locals_top: usize, + + pub fn args(self: *const Frame, env: *RuntimeEnv) []const ?*const RuntimeRef { + 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 has_arg(self: *const Frame, env: *RuntimeEnv, arg_index: u8) ?*const RuntimeRef { + return if (arg_index >= self.arg_count) null else env.locals.values[self.locals_top + arg_index]; + } +}; + pub const Fixed = i32; pub const Float = f64; @@ -18,6 +36,7 @@ pub const RuntimeEnv = struct { const Chunk = struct { env: *RuntimeEnv, name: []coral.io.Byte, + arity: u8, opcodes: OpcodeList, constants: ConstList, @@ -33,6 +52,7 @@ pub const RuntimeEnv = struct { float: Float, string: []const coral.io.Byte, symbol: []const coral.io.Byte, + chunk: Chunk, }; const Opcode = union (enum) { @@ -124,17 +144,39 @@ pub const RuntimeEnv = struct { }); }, - .table_literal => |fields| { - if (fields.values.len > coral.math.max_int(@typeInfo(u32).Int)) { + .table_literal => |literal| { + if (literal.values.len > coral.math.max_int(@typeInfo(u32).Int)) { return error.OutOfMemory; } - for (fields.values) |field| { + for (literal.values) |field| { try self.compile_expression(chunk, field.value_expression); try self.compile_expression(chunk, field.key_expression); } - try chunk.opcodes.push_one(.{.push_table = @intCast(fields.values.len)}); + try chunk.opcodes.push_one(.{.push_table = @intCast(literal.values.len)}); + }, + + .lambda_literal => |literal| { + if (literal.argument_identifiers.values.len > coral.math.max_int(@typeInfo(u8).Int)) { + return error.OutOfMemory; + } + + var lambda_chunk = try Chunk.make( + chunk.env, + "", + @intCast(literal.argument_identifiers.values.len), + ); + + errdefer lambda_chunk.free(); + + try lambda_chunk.compile(literal.block_statements.values, literal.argument_identifiers.values); + + try chunk.opcodes.push_one(.{ + .push_const = try chunk.declare_constant(.{ + .chunk = lambda_chunk, + }), + }); }, .binary_operation => |operation| { @@ -213,11 +255,11 @@ pub const RuntimeEnv = struct { }, .local_get => |local_get| { - try chunk.opcodes.push_one(.{ - .push_local = (self.resolve_local(local_get.identifier) orelse { - return chunk.env.raise(error.OutOfMemory, "undefined local"); - }).index, - }); + if (self.resolve_local(local_get.identifier)) |local| { + return chunk.opcodes.push_one(.{.push_local = local.index}); + } + + return chunk.env.raise(error.OutOfMemory, "undefined local"); }, .local_set => |local_set| { @@ -287,16 +329,26 @@ pub const RuntimeEnv = struct { try self.compile_expression(chunk, declare.assigned_expression); - if (self.locals_count == self.locals_buffer.len) { - return chunk.env.raise(error.BadSyntax, "chunks may have a maximum of 255 locals"); + switch (declare.storage) { + .@"var" => { + if (!self.declare_local(.{ + .identifier = declare.identifier, + .is_readonly = false, + })) { + return chunk.env.raise(error.BadSyntax, "too many locals"); + } + }, + + .let => { + // TODO: investigate constant folding. + if (!self.declare_local(.{ + .identifier = declare.identifier, + .is_readonly = false, + })) { + return chunk.env.raise(error.BadSyntax, "too many locals"); + } + }, } - - self.locals_buffer[self.locals_count] = .{ - .identifier = declare.identifier, - .is_readonly = declare.storage != .variant, - }; - - self.locals_count += 1; }, .block => |block| { @@ -348,6 +400,17 @@ pub const RuntimeEnv = struct { } } + fn declare_local(self: *CompilationUnit, local: Local) bool { + if (self.locals_count == self.locals_buffer.len) { + return false; + } + + self.locals_buffer[self.locals_count] = local; + self.locals_count += 1; + + return true; + } + fn resolve_local(self: *CompilationUnit, local_identifier: []const coral.io.Byte) ?ResolvedLocal { if (self.locals_count == 0) { return null; @@ -372,11 +435,29 @@ pub const RuntimeEnv = struct { } }; - fn compile(self: *Chunk, statements: []const ast.Statement) RuntimeError!void { + fn compile(self: *Chunk, statements: []const ast.Statement, args: []const []const coral.io.Byte) RuntimeError!void { var unit = CompilationUnit{}; + var has_returned = false; + + for (args) |arg| { + if (!unit.declare_local(.{ + .is_readonly = true, + .identifier = arg, + })) { + return self.env.raise(error.BadSyntax, "too many arguments"); + } + } for (statements) |statement| { try unit.compile_statement(self, statement); + + if (statement == .@"return") { + has_returned = true; + } + } + + if (!has_returned) { + try self.opcodes.push_one(.push_nil); } } @@ -385,27 +466,27 @@ pub const RuntimeEnv = struct { return self.env.raise(error.BadSyntax, "chunks cannot contain more than 65,535 constants"); } - const constant_index = self.constants.values.len; - - try self.constants.push_one(try switch (constant) { + const constant_object = try switch (constant) { .fixed => |fixed| self.env.new_fixed(fixed), .float => |float| self.env.new_float(float), .string => |string| self.env.new_string(string), .symbol => |symbol| self.env.new_symbol(symbol), - }); - return @intCast(constant_index); + .chunk => |chunk| self.env.new_dynamic(coral.io.bytes_of(&chunk).ptr, &.{ + .size = @sizeOf(Chunk), + .name = "lambda", + .destruct = typeinfo_destruct, + .call = typeinfo_call, + }), + }; + + errdefer self.env.discard(constant_object); + try self.constants.push_one(constant_object); + + return @intCast(self.constants.values.len - 1); } - fn execute(self: *Chunk) RuntimeError!?*RuntimeRef { - try self.env.frames.push_one(.{ - .arg_count = 0, - .locals_top = self.env.locals.values.len, - .name = self.name, - }); - - defer coral.debug.assert(self.env.frames.pop() != null); - + fn execute(self: *Chunk, frame: Frame) RuntimeError!?*RuntimeRef { var opcode_cursor = @as(u32, 0); while (opcode_cursor < self.opcodes.values.len) : (opcode_cursor += 1) { @@ -433,7 +514,7 @@ pub const RuntimeEnv = struct { return self.env.raise(error.IllegalState, "invalid local"); } - if (self.env.locals.values[push_local]) |local| { + if (self.env.locals.values[frame.locals_top + push_local]) |local| { try self.env.locals.push_one(try self.env.acquire(local)); } else { try self.env.locals.push_one(null); @@ -448,25 +529,19 @@ pub const RuntimeEnv = struct { { const dynamic = table.object().payload.dynamic; const userdata = dynamic.userdata(); + const table_set = dynamic.typeinfo().set; var popped = @as(usize, 0); while (popped < push_table) : (popped += 1) { - const index = try self.env.expect(try self.pop_local()); + const index = try self.expect(try self.pop_local()); defer self.env.discard(index); - const maybe_value = try self.pop_local(); + if (try self.pop_local()) |value| { + defer self.env.discard(value); - defer { - if (maybe_value) |value| { - self.env.discard(value); - } + try table_set(self.env, userdata, index, value); } - - try dynamic.typeinfo().set(.{ - .userdata = userdata, - .env = self.env, - }, index, maybe_value); } } @@ -487,7 +562,7 @@ pub const RuntimeEnv = struct { }, .local_set => |local_set| { - const local = &self.env.locals.values[local_set]; + const local = &self.env.locals.values[frame.locals_top + local_set]; if (local.*) |previous_local| { self.env.discard(previous_local); @@ -497,11 +572,11 @@ pub const RuntimeEnv = struct { }, .object_get => { - const index = try self.env.expect(try self.pop_local()); + const index = try self.expect(try self.pop_local()); defer self.env.discard(index); - const indexable = try self.env.expect(try self.pop_local()); + const indexable = try self.expect(try self.pop_local()); defer self.env.discard(indexable); @@ -542,36 +617,21 @@ pub const RuntimeEnv = struct { .object_call => |object_call| { const result = call: { - const callable = try self.env.expect(try self.pop_local()); + const callable = try self.expect(try self.pop_local()); defer self.env.discard(callable); - try self.env.frames.push_one(.{ - .name = "", - .arg_count = object_call, - .locals_top = self.env.locals.values.len, - }); + const call_frame = try self.env.push_frame(object_call); - defer coral.debug.assert(self.env.frames.pop() != null); + defer self.env.pop_frame(); break: call try switch (callable.object().payload) { - .syscall => |syscall| syscall(self.env), - - .dynamic => |dynamic| dynamic.typeinfo().call(.{ - .userdata = dynamic.userdata(), - .env = self.env, - }), - + .syscall => |syscall| syscall(self.env, call_frame), + .dynamic => |dynamic| dynamic.typeinfo().call(self.env, dynamic.userdata(), call_frame), else => self.env.raise(error.TypeMismatch, "object is not callable"), }; }; - for (0 .. object_call) |_| { - if (try self.pop_local()) |popped_arg| { - self.env.discard(popped_arg); - } - } - errdefer { if (result) |ref| { self.env.discard(ref); @@ -592,7 +652,7 @@ pub const RuntimeEnv = struct { }, .neg => { - const value = try self.env.expect(try self.pop_local()); + const value = try self.expect(try self.pop_local()); defer self.env.discard(value); @@ -604,11 +664,11 @@ pub const RuntimeEnv = struct { }, .add => { - const rhs = try self.env.expect(try self.pop_local()); + const rhs = try self.expect(try self.pop_local()); defer self.env.discard(rhs); - const lhs = try self.env.expect(try self.pop_local()); + const lhs = try self.expect(try self.pop_local()); defer self.env.discard(lhs); @@ -644,11 +704,11 @@ pub const RuntimeEnv = struct { }, .sub => { - const rhs = try self.env.expect(try self.pop_local()); + const rhs = try self.expect(try self.pop_local()); defer self.env.discard(rhs); - const lhs = try self.env.expect(try self.pop_local()); + const lhs = try self.expect(try self.pop_local()); defer self.env.discard(lhs); @@ -684,11 +744,11 @@ pub const RuntimeEnv = struct { }, .mul => { - const rhs = try self.env.expect(try self.pop_local()); + const rhs = try self.expect(try self.pop_local()); defer self.env.discard(rhs); - const lhs = try self.env.expect(try self.pop_local()); + const lhs = try self.expect(try self.pop_local()); defer self.env.discard(lhs); @@ -724,11 +784,11 @@ pub const RuntimeEnv = struct { }, .div => { - const rhs = try self.env.expect(try self.pop_local()); + const rhs = try self.expect(try self.pop_local()); defer self.env.discard(rhs); - const lhs = try self.env.expect(try self.pop_local()); + const lhs = try self.expect(try self.pop_local()); defer self.env.discard(lhs); @@ -780,11 +840,11 @@ pub const RuntimeEnv = struct { }, .cgt => { - const rhs = try self.env.expect(try self.pop_local()); + const rhs = try self.expect(try self.pop_local()); defer self.env.discard(rhs); - const lhs = try self.env.expect(try self.pop_local()); + const lhs = try self.expect(try self.pop_local()); defer self.env.discard(lhs); @@ -806,11 +866,11 @@ pub const RuntimeEnv = struct { }, .clt => { - const rhs = try self.env.expect(try self.pop_local()); + const rhs = try self.expect(try self.pop_local()); defer self.env.discard(rhs); - const lhs = try self.env.expect(try self.pop_local()); + const lhs = try self.expect(try self.pop_local()); defer self.env.discard(lhs); @@ -832,11 +892,11 @@ pub const RuntimeEnv = struct { }, .cge => { - const rhs = try self.env.expect(try self.pop_local()); + const rhs = try self.expect(try self.pop_local()); defer self.env.discard(rhs); - const lhs = try self.env.expect(try self.pop_local()); + const lhs = try self.expect(try self.pop_local()); defer self.env.discard(lhs); @@ -858,11 +918,11 @@ pub const RuntimeEnv = struct { }, .cle => { - const rhs = try self.env.expect(try self.pop_local()); + const rhs = try self.expect(try self.pop_local()); defer self.env.discard(rhs); - const lhs = try self.env.expect(try self.pop_local()); + const lhs = try self.expect(try self.pop_local()); defer self.env.discard(lhs); @@ -912,6 +972,10 @@ pub const RuntimeEnv = struct { return self.pop_local(); } + fn expect(self: *Chunk, value: ?*RuntimeRef) RuntimeError!*RuntimeRef { + return value orelse self.env.raise(error.TypeMismatch, "nil reference"); + } + fn free(self: *Chunk) void { while (self.constants.pop()) |constant| { self.env.discard(constant); @@ -922,11 +986,12 @@ pub const RuntimeEnv = struct { self.env.allocator.deallocate(self.name); } - fn make(env: *RuntimeEnv, name: []const coral.io.Byte) coral.io.AllocationError!Chunk { + fn make(env: *RuntimeEnv, name: []const coral.io.Byte, arity: u8) coral.io.AllocationError!Chunk { return .{ .name = try coral.io.allocate_copy(env.allocator, name), .opcodes = OpcodeList.make(env.allocator), .constants = ConstList.make(env.allocator), + .arity = arity, .env = env, }; } @@ -934,15 +999,27 @@ pub const RuntimeEnv = struct { fn pop_local(self: *Chunk) RuntimeError!?*RuntimeRef { return self.env.locals.pop() orelse self.env.raise(error.IllegalState, "stack underflow"); } + + fn typeinfo_call(env: *RuntimeEnv, userdata: []coral.io.Byte, frame: Frame) RuntimeError!?*RuntimeRef { + const chunk = @as(*Chunk, @ptrCast(@alignCast(userdata))); + + if (frame.arg_count < chunk.arity) { + return env.raise(error.BadOperation, "expected more arguments"); + } + + return chunk.execute(frame); + } + + fn typeinfo_destruct(env: *RuntimeEnv, userdata: []coral.io.Byte) void { + _ = env; + + @as(*Chunk, @ptrCast(@alignCast(userdata))).free(); + } }; const ConstList = coral.list.Stack(*RuntimeRef); - const FrameStack = coral.list.Stack(struct { - name: []const coral.io.Byte, - arg_count: u8, - locals_top: usize, - }); + const FrameStack = coral.list.Stack(Frame); pub const Options = struct { import_access: file.Access = .null, @@ -966,15 +1043,15 @@ pub const RuntimeEnv = struct { associative: RefTable, contiguous: RefList, - fn typeinfo_destruct(method: Typeinfo.Method) void { - const table = @as(*Table, @ptrCast(@alignCast(method.userdata.ptr))); + fn typeinfo_destruct(env: *RuntimeEnv, userdata: []coral.io.Byte) void { + const table = @as(*Table, @ptrCast(@alignCast(userdata))); { var field_iterable = table.associative.as_iterable(); while (field_iterable.next()) |entry| { - method.env.discard(entry.key); - method.env.discard(entry.value); + env.discard(entry.key); + env.discard(entry.value); } } @@ -982,18 +1059,18 @@ pub const RuntimeEnv = struct { while (table.contiguous.pop()) |value| { if (value) |ref| { - method.env.discard(ref); + env.discard(ref); } } table.contiguous.free(); } - fn typeinfo_get(method: Typeinfo.Method, index: *const RuntimeRef) RuntimeError!?*RuntimeRef { - const table = @as(*Table, @ptrCast(@alignCast(method.userdata.ptr))); - const acquired_index = try method.env.acquire(index); + fn typeinfo_get(env: *RuntimeEnv, userdata: []coral.io.Byte, index: *const RuntimeRef) RuntimeError!?*RuntimeRef { + const table = @as(*Table, @ptrCast(@alignCast(userdata))); + const acquired_index = try env.acquire(index); - defer method.env.discard(acquired_index); + defer env.discard(acquired_index); if (acquired_index.is_fixed()) |fixed| { if (fixed < 0) { @@ -1002,22 +1079,22 @@ pub const RuntimeEnv = struct { } if (fixed < table.contiguous.values.len) { - return method.env.acquire(table.contiguous.values[@intCast(fixed)] orelse return null); + return env.acquire(table.contiguous.values[@intCast(fixed)] orelse return null); } } if (table.associative.lookup(acquired_index)) |value_ref| { - return method.env.acquire(value_ref); + return env.acquire(value_ref); } return null; } - fn typeinfo_set(method: Typeinfo.Method, index: *const RuntimeRef, value: ?*const RuntimeRef) RuntimeError!void { - const table = @as(*Table, @ptrCast(@alignCast(method.userdata.ptr))); - const acquired_index = try method.env.acquire(index); + fn typeinfo_set(env: *RuntimeEnv, userdata: []coral.io.Byte, index: *const RuntimeRef, value: ?*const RuntimeRef) RuntimeError!void { + const table = @as(*Table, @ptrCast(@alignCast(userdata))); + const acquired_index = try env.acquire(index); - errdefer method.env.discard(acquired_index); + errdefer env.discard(acquired_index); if (acquired_index.is_fixed()) |fixed| { if (fixed < 0) { @@ -1029,32 +1106,31 @@ pub const RuntimeEnv = struct { const maybe_replacing = &table.contiguous.values[@intCast(fixed)]; if (maybe_replacing.*) |replacing| { - method.env.discard(replacing); + env.discard(replacing); } - maybe_replacing.* = if (value) |ref| try method.env.acquire(ref) else null; + maybe_replacing.* = if (value) |ref| try env.acquire(ref) else null; return; } } - const acquired_value = try method.env.acquire(value orelse { + const acquired_value = try env.acquire(value orelse { if (table.associative.remove(acquired_index)) |removed| { - method.env.discard(removed.key); - method.env.discard(removed.value); + env.discard(removed.key); + env.discard(removed.value); } return; }); - errdefer method.env.discard(acquired_value); + errdefer env.discard(acquired_value); if (try table.associative.replace(acquired_index, acquired_value)) |replaced| { - method.env.discard(replaced.key); - method.env.discard(replaced.value); + env.discard(replaced.key); + env.discard(replaced.value); } } - }; pub fn acquire(self: *RuntimeEnv, value: *const RuntimeRef) RuntimeError!*RuntimeRef { @@ -1067,45 +1143,15 @@ pub const RuntimeEnv = struct { return @ptrCast(object); } - pub fn acquire_arg(self: *RuntimeEnv, index: usize) RuntimeError!?*RuntimeRef { - const frame = self.frames.peek() orelse return self.raise(error.IllegalState, "stack underflow"); - - if (index < frame.arg_count) { - if (self.locals.values[(frame.locals_top - frame.arg_count) + index]) |local| { - return self.acquire(local); - } - } - - return null; - } - - pub fn bind_system(self: *RuntimeEnv, name: []const coral.io.Byte, value: *const RuntimeRef) RuntimeError!void { - const name_symbol = try self.new_symbol(name); - - errdefer self.discard(name_symbol); - - const acquired_value = try self.acquire(value); - - errdefer self.discard(acquired_value); - - if (try self.system_bindings.replace(name_symbol, acquired_value)) |replaced| { - self.discard(replaced.key); - self.discard(replaced.value); - } - } - pub fn call(self: *RuntimeEnv, callable: *RuntimeRef, args: []const *RuntimeRef) RuntimeError!?*RuntimeRef { - try self.locals.push_all(args); + // TODO: Handle errors. + for (args) |arg| { + try self.locals.push_one(try self.acquire(arg)); + } - defer coral.io.assert(self.locals.drop(args.len)); + try self.push_frame(args.len); - try self.frames.push_one(.{ - .name = "", - .arg_count = args.len, - .locals_top = self.locals.values.len, - }); - - defer coral.io.assert(self.frames.pop() != null); + defer self.pop_frame(); return switch (callable.object().payload) { .syscall => |syscall| syscall(self.env), @@ -1137,10 +1183,7 @@ pub const RuntimeEnv = struct { .dynamic => |dynamic| { if (dynamic.typeinfo().destruct) |destruct| { - destruct(.{ - .userdata = dynamic.userdata(), - .env = self, - }); + destruct(self, dynamic.userdata()); } self.allocator.deallocate(dynamic.unpack()); @@ -1159,7 +1202,7 @@ pub const RuntimeEnv = struct { defer self.allocator.deallocate(file_data); const file_name = file_path.to_string() orelse "