From f5ed4bbcad065e8b91ae68c9337d0f405ed74d8c Mon Sep 17 00:00:00 2001 From: kayomn Date: Sat, 26 Aug 2023 16:00:17 +0100 Subject: [PATCH 1/5] Add support for basic lambda syntax --- debug/app.ona | 12 ++-- source/ona/kym.zig | 117 +++++++++++++++++++++++++------------- source/ona/kym/ast.zig | 64 ++++++++++++++++++++- source/ona/kym/tokens.zig | 8 +++ 4 files changed, 157 insertions(+), 44 deletions(-) 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; -- 2.34.1 From b9f03b34c1abd135658c14313751d1a0eab4bc81 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sat, 26 Aug 2023 17:00:55 +0100 Subject: [PATCH 2/5] Add argument resolving to functions --- debug/app.ona | 5 +++-- source/ona/kym.zig | 48 +++++++++++++++++++++++++++++++++++++--------- 2 files changed, 42 insertions(+), 11 deletions(-) diff --git a/debug/app.ona b/debug/app.ona index abf72fa..dddd91f 100644 --- a/debug/app.ona +++ b/debug/app.ona @@ -1,8 +1,9 @@ var i = 0 -let pr = lambda (): - @print("foo") +let pr = lambda (str): + @print("This is a func call") + @print(str) end while i < 5: diff --git a/source/ona/kym.zig b/source/ona/kym.zig index 30b82cc..2a865c8 100644 --- a/source/ona/kym.zig +++ b/source/ona/kym.zig @@ -43,6 +43,7 @@ pub const RuntimeEnv = struct { push_false, push_const: u16, push_local: u8, + push_arg: u8, push_table: u32, push_builtin: Builtin, local_set: u8, @@ -71,6 +72,7 @@ pub const RuntimeEnv = struct { const OpcodeList = coral.list.Stack(Opcode); const CompilationUnit = struct { + args: []const []const coral.io.Byte, locals_buffer: [255]Local = [_]Local{.{}} ** 255, locals_count: u8 = 0, @@ -147,7 +149,7 @@ pub const RuntimeEnv = struct { errdefer lambda_chunk.free(); - try lambda_chunk.compile(literal.block_statements.values); + try lambda_chunk.compile(literal.block_statements.values, literal.argument_identifiers.values); try chunk.opcodes.push_one(.{ .push_const = try chunk.declare_constant(.{ @@ -232,11 +234,15 @@ 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}); + } + + if (self.resolve_arg(local_get.identifier)) |arg| { + return chunk.opcodes.push_one(.{.push_arg = arg}); + } + + return chunk.env.raise(error.OutOfMemory, "undefined local"); }, .local_set => |local_set| { @@ -367,6 +373,18 @@ pub const RuntimeEnv = struct { } } + fn resolve_arg(self: *CompilationUnit, arg_identifier: []const coral.io.Byte) ?u8 { + var index = @as(u8, 0); + + while (index < self.args.len) { + if (coral.io.are_equal(self.args[index], arg_identifier)) { + return index; + } + } + + return null; + } + fn resolve_local(self: *CompilationUnit, local_identifier: []const coral.io.Byte) ?ResolvedLocal { if (self.locals_count == 0) { return null; @@ -391,8 +409,8 @@ pub const RuntimeEnv = struct { } }; - fn compile(self: *Chunk, statements: []const ast.Statement) RuntimeError!void { - var unit = CompilationUnit{}; + fn compile(self: *Chunk, statements: []const ast.Statement, args: []const []const coral.io.Byte) RuntimeError!void { + var unit = CompilationUnit{.args = args}; var has_returned = false; for (statements) |statement| { @@ -468,6 +486,18 @@ pub const RuntimeEnv = struct { } }, + .push_arg => |push_arg| { + const arg = try self.env.acquire_arg(push_arg); + + errdefer { + if (arg) |ref| { + self.env.discard(ref); + } + } + + try self.env.locals.push_one(arg); + }, + .push_table => |push_table| { const table = try self.env.new_table(); @@ -1203,7 +1233,7 @@ pub const RuntimeEnv = struct { try chunk.compile(ast_tree.parse(file_data) catch |parse_error| return switch (parse_error) { error.BadSyntax => self.raise(error.BadSyntax, ast_tree.error_message()), error.OutOfMemory => error.OutOfMemory, - }); + }, &.{}); try self.frames.push_one(.{ .name = file_name, -- 2.34.1 From 5c08a1b63ef326210702cf74ce0b2379d009688a Mon Sep 17 00:00:00 2001 From: kayomn Date: Sat, 26 Aug 2023 21:30:36 +0100 Subject: [PATCH 3/5] Add lambda argument arity checking to runtime --- debug/app.ona | 2 ++ source/ona/kym.zig | 39 +++++++++++++++++++++++---------------- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/debug/app.ona b/debug/app.ona index dddd91f..855cf63 100644 --- a/debug/app.ona +++ b/debug/app.ona @@ -6,6 +6,8 @@ let pr = lambda (str): @print(str) end +pr("eeee") + while i < 5: pr("hello, world") diff --git a/source/ona/kym.zig b/source/ona/kym.zig index 2a865c8..1a34fc1 100644 --- a/source/ona/kym.zig +++ b/source/ona/kym.zig @@ -18,6 +18,7 @@ pub const RuntimeEnv = struct { const Chunk = struct { env: *RuntimeEnv, name: []coral.io.Byte, + arity: u8, opcodes: OpcodeList, constants: ConstList, @@ -141,11 +142,15 @@ pub const RuntimeEnv = struct { }, .lambda_literal => |literal| { - if (literal.argument_identifiers.values.len > coral.math.max_int(@typeInfo(u32).Int)) { + if (literal.argument_identifiers.values.len > coral.math.max_int(@typeInfo(u8).Int)) { return error.OutOfMemory; } - var lambda_chunk = try Chunk.make(chunk.env, ""); + var lambda_chunk = try Chunk.make( + chunk.env, + "", + @intCast(literal.argument_identifiers.values.len), + ); errdefer lambda_chunk.free(); @@ -982,11 +987,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, }; } @@ -996,7 +1002,13 @@ pub const RuntimeEnv = struct { } fn typeinfo_call(method: Typeinfo.Method) RuntimeError!?*RuntimeRef { - return @as(*Chunk, @ptrCast(@alignCast(method.userdata))).execute(); + const chunk = @as(*Chunk, @ptrCast(@alignCast(method.userdata))); + + if ((method.env.view_args() catch unreachable).len < chunk.arity) { + return method.env.raise(error.BadOperation, "expected more arguments"); + } + + return chunk.execute(); } fn typeinfo_destruct(method: Typeinfo.Method) void { @@ -1122,7 +1134,6 @@ pub const RuntimeEnv = struct { method.env.discard(replaced.value); } } - }; pub fn acquire(self: *RuntimeEnv, value: *const RuntimeRef) RuntimeError!*RuntimeRef { @@ -1147,16 +1158,6 @@ pub const RuntimeEnv = struct { return null; } - pub fn acquire_args(self: *RuntimeEnv) RuntimeError!*RuntimeRef { - const frame = self.frames.peek() orelse return self.raise(error.IllegalState, "stack underflow"); - - _ = frame; - - const args_table = self.new_table(); - - return args_table; - } - pub fn call(self: *RuntimeEnv, callable: *RuntimeRef, args: []const *RuntimeRef) RuntimeError!?*RuntimeRef { try self.locals.push_all(args); @@ -1222,7 +1223,7 @@ pub const RuntimeEnv = struct { defer self.allocator.deallocate(file_data); const file_name = file_path.to_string() orelse "