diff --git a/debug/app.ona b/debug/app.ona index 48b8ee8..abf72fa 100644 --- a/debug/app.ona +++ b/debug/app.ona @@ -1,18 +1,22 @@ var i = 0 +let pr = lambda (): + @print("foo") +end + 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/kym.zig b/source/ona/kym.zig index 5415f5b..30b82cc 100644 --- a/source/ona/kym.zig +++ b/source/ona/kym.zig @@ -33,6 +33,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 +125,35 @@ 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(u32).Int)) { + return error.OutOfMemory; + } + + var lambda_chunk = try Chunk.make(chunk.env, ""); + + errdefer lambda_chunk.free(); + + try lambda_chunk.compile(literal.block_statements.values); + + try chunk.opcodes.push_one(.{ + .push_const = try chunk.declare_constant(.{ + .chunk = lambda_chunk, + }), + }); }, .binary_operation => |operation| { @@ -374,9 +393,18 @@ pub const RuntimeEnv = struct { fn compile(self: *Chunk, statements: []const ast.Statement) RuntimeError!void { var unit = CompilationUnit{}; + var has_returned = false; 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 +413,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); - var opcode_cursor = @as(u32, 0); while (opcode_cursor < self.opcodes.values.len) : (opcode_cursor += 1) { @@ -547,14 +575,16 @@ pub const RuntimeEnv = struct { defer self.env.discard(callable); try self.env.frames.push_one(.{ - .name = "", + .name = "", .arg_count = object_call, .locals_top = self.env.locals.values.len, }); defer coral.debug.assert(self.env.frames.pop() != null); - break: call try switch (callable.object().payload) { + const payload = callable.object().payload; + + break: call try switch (payload) { .syscall => |syscall| syscall(self.env), .dynamic => |dynamic| dynamic.typeinfo().call(.{ @@ -566,18 +596,18 @@ pub const RuntimeEnv = struct { }; }; - for (0 .. object_call) |_| { - if (try self.pop_local()) |popped_arg| { - self.env.discard(popped_arg); - } - } - errdefer { if (result) |ref| { self.env.discard(ref); } } + for (0 .. object_call) |_| { + if (try self.pop_local()) |popped_arg| { + self.env.discard(popped_arg); + } + } + try self.env.locals.push_one(result); }, @@ -934,6 +964,14 @@ 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(method: Typeinfo.Method) RuntimeError!?*RuntimeRef { + return @as(*Chunk, @ptrCast(@alignCast(method.userdata))).execute(); + } + + fn typeinfo_destruct(method: Typeinfo.Method) void { + @as(*Chunk, @ptrCast(@alignCast(method.userdata))).free(); + } }; const ConstList = coral.list.Stack(*RuntimeRef); @@ -967,7 +1005,7 @@ pub const RuntimeEnv = struct { contiguous: RefList, fn typeinfo_destruct(method: Typeinfo.Method) void { - const table = @as(*Table, @ptrCast(@alignCast(method.userdata.ptr))); + const table = @as(*Table, @ptrCast(@alignCast(method.userdata))); { var field_iterable = table.associative.as_iterable(); @@ -990,7 +1028,7 @@ pub const RuntimeEnv = struct { } fn typeinfo_get(method: Typeinfo.Method, index: *const RuntimeRef) RuntimeError!?*RuntimeRef { - const table = @as(*Table, @ptrCast(@alignCast(method.userdata.ptr))); + const table = @as(*Table, @ptrCast(@alignCast(method.userdata))); const acquired_index = try method.env.acquire(index); defer method.env.discard(acquired_index); @@ -1014,7 +1052,7 @@ pub const RuntimeEnv = struct { } fn typeinfo_set(method: Typeinfo.Method, index: *const RuntimeRef, value: ?*const RuntimeRef) RuntimeError!void { - const table = @as(*Table, @ptrCast(@alignCast(method.userdata.ptr))); + const table = @as(*Table, @ptrCast(@alignCast(method.userdata))); const acquired_index = try method.env.acquire(index); errdefer method.env.discard(acquired_index); @@ -1079,19 +1117,14 @@ pub const RuntimeEnv = struct { 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); + pub fn acquire_args(self: *RuntimeEnv) RuntimeError!*RuntimeRef { + const frame = self.frames.peek() orelse return self.raise(error.IllegalState, "stack underflow"); - errdefer self.discard(name_symbol); + _ = frame; - const acquired_value = try self.acquire(value); + const args_table = self.new_table(); - errdefer self.discard(acquired_value); - - if (try self.system_bindings.replace(name_symbol, acquired_value)) |replaced| { - self.discard(replaced.key); - self.discard(replaced.value); - } + return args_table; } pub fn call(self: *RuntimeEnv, callable: *RuntimeRef, args: []const *RuntimeRef) RuntimeError!?*RuntimeRef { @@ -1172,6 +1205,14 @@ pub const RuntimeEnv = struct { error.OutOfMemory => error.OutOfMemory, }); + try self.frames.push_one(.{ + .name = file_name, + .arg_count = 0, + .locals_top = self.locals.values.len, + }); + + defer coral.debug.assert(self.frames.pop() != null); + return chunk.execute(); } diff --git a/source/ona/kym/ast.zig b/source/ona/kym/ast.zig index c95bce2..e3ba5f8 100644 --- a/source/ona/kym/ast.zig +++ b/source/ona/kym/ast.zig @@ -74,6 +74,11 @@ pub const Expression = union (enum) { table_literal: TableLiteral, grouped_expression: *Expression, + lambda_literal: struct { + argument_identifiers: IdentifierList, + block_statements: Statement.List, + }, + local_get: struct { identifier: []const coral.io.Byte, }, @@ -136,6 +141,8 @@ pub const DeclarationStorage = enum { const ExpressionBuilder = fn (self: *Tree) ParseError!Expression; +const IdentifierList = coral.list.Stack([]const coral.io.Byte); + pub const ParseError = error { OutOfMemory, BadSyntax, @@ -221,8 +228,6 @@ pub const Tree = struct { fn parse_branch(self: *Tree) ParseError!Statement { const allocator = self.arena.as_allocator(); - defer self.tokenizer.skip_newlines(); - self.tokenizer.step(); const condition_expression = try self.parse_expression(); @@ -238,6 +243,8 @@ pub const Tree = struct { while (true) { switch (self.tokenizer.token) { .keyword_end => { + self.tokenizer.skip_newlines(); + return .{ .@"if" = .{ .condition_expression = condition_expression, @@ -262,6 +269,8 @@ pub const Tree = struct { try else_statements.push_one(try self.parse_statement()); } + self.tokenizer.skip_newlines(); + return .{ .@"if" = .{ .else_statement = try coral.io.allocate_one(allocator, Statement{.block = else_statements}), @@ -404,6 +413,57 @@ pub const Tree = struct { break: parse .{.builtin = builtin}; }, + .keyword_lambda => { + self.tokenizer.skip_newlines(); + + if (self.tokenizer.token != .symbol_paren_left) { + return self.report("expected `(` after opening lambda block"); + } + + var argument_identifiers = IdentifierList.make(allocator); + + while (true) { + self.tokenizer.skip_newlines(); + + switch (self.tokenizer.token) { + .identifier => |identifier| try argument_identifiers.push_one(identifier), + .symbol_paren_right => break, + else => return self.report("expected identifier or closing `)` in argument list"), + } + + self.tokenizer.skip_newlines(); + + switch (self.tokenizer.token) { + .symbol_comma => continue, + .symbol_paren_right => break, + else => return self.report("expected `,` or closing `)` after identifier in argument list"), + } + } + + self.tokenizer.skip_newlines(); + + if (self.tokenizer.token != .symbol_colon) { + return self.report("expected `:` after closing `)` of lambda block argument list"); + } + + self.tokenizer.skip_newlines(); + + var block_statements = Statement.List.make(allocator); + + while (self.tokenizer.token != .keyword_end) { + try block_statements.push_one(try self.parse_statement()); + } + + self.tokenizer.skip_newlines(); + + break: parse .{ + .lambda_literal = .{ + .argument_identifiers = argument_identifiers, + .block_statements = block_statements, + }, + }; + }, + .symbol_brace_left => { var table_literal = Expression.TableLiteral.make(allocator); diff --git a/source/ona/kym/tokens.zig b/source/ona/kym/tokens.zig index 3f1aa53..875176a 100755 --- a/source/ona/kym/tokens.zig +++ b/source/ona/kym/tokens.zig @@ -46,6 +46,7 @@ pub const Token = union(enum) { keyword_elif, keyword_var, keyword_let, + keyword_lambda, pub fn text(self: Token) []const coral.io.Byte { return switch (self) { @@ -95,6 +96,7 @@ pub const Token = union(enum) { .keyword_else => "else", .keyword_var => "var", .keyword_let => "let", + .keyword_lambda => "lambda", }; } }; @@ -234,6 +236,12 @@ pub const Tokenizer = struct { }, 'l' => { + if (coral.io.ends_with(identifier, "ambda")) { + self.token = .keyword_lambda; + + return; + } + if (coral.io.ends_with(identifier, "et")) { self.token = .keyword_let;