From 83680607b7e43c16e550a8ddb80d5312dc7394d4 Mon Sep 17 00:00:00 2001 From: kayomn Date: Mon, 6 Nov 2023 20:16:22 +0000 Subject: [PATCH 1/6] Fix arity check error message formatting --- source/ona/kym.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/ona/kym.zig b/source/ona/kym.zig index 2fc8f88..69c40fe 100644 --- a/source/ona/kym.zig +++ b/source/ona/kym.zig @@ -533,8 +533,8 @@ pub const RuntimeEnv = struct { if (arg_count < arity) { return self.raise(error.BadOperation, "expected `{expected}` {noun}, {provided} provided", .{ - .expected = arity, - .provided = arg_count, + .expected = arg_count, + .provided = arity, .noun = if (arity == 1) "argument" else "arguments", }); } From eb9552c390d32f0c9e2874cf5629cbc636f8bff5 Mon Sep 17 00:00:00 2001 From: kayomn Date: Mon, 6 Nov 2023 20:53:24 +0000 Subject: [PATCH 2/6] Merge compiler into Chunk.zig --- source/ona/kym/Chunk.zig | 408 ++++++++++++++++++++++++++++++++++- source/ona/kym/Compiler.zig | 413 ------------------------------------ 2 files changed, 406 insertions(+), 415 deletions(-) delete mode 100644 source/ona/kym/Compiler.zig diff --git a/source/ona/kym/Chunk.zig b/source/ona/kym/Chunk.zig index bcfd649..7ebb7fb 100644 --- a/source/ona/kym/Chunk.zig +++ b/source/ona/kym/Chunk.zig @@ -1,6 +1,8 @@ -const app = @import("../app.zig"); +const Expr = @import("./Expr.zig"); -const Compiler = @import("./Compiler.zig"); +const Stmt = @import("./Stmt.zig"); + +const app = @import("../app.zig"); const coral = @import("coral"); @@ -25,6 +27,408 @@ const Builtin = enum { vec3, }; +const Compiler = struct { + chunk: *Self, + env: *kym.RuntimeEnv, + + fn compile_argument(self: *const Compiler, environment: *const tree.Environment, initial_argument: ?*const Expr) kym.RuntimeError!u8 { + // TODO: Exceeding 255 arguments will make the VM crash. + var maybe_argument = initial_argument; + var argument_count = @as(u8, 0); + + while (maybe_argument) |argument| { + try self.compile_expression(environment, argument, null); + + maybe_argument = argument.next; + argument_count += 1; + } + + return argument_count; + } + + fn compile_expression(self: *const Compiler, 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, + }; + + switch (expression.kind) { + .nil_literal => try self.chunk.write(expression.line, .push_nil), + .true_literal => try self.chunk.write(expression.line, .push_true), + .false_literal => try self.chunk.write(expression.line, .push_false), + + .number_literal => |literal| { + for (literal) |codepoint| { + if (codepoint == '.') { + return self.chunk.write(expression.line, .{ + .push_const = try self.declare_float(number_format.parse(literal, kym.Float) orelse unreachable), + }); + } + } + + try self.chunk.write(expression.line, .{ + .push_const = try self.declare_fixed(number_format.parse(literal, kym.Fixed) orelse unreachable), + }); + }, + + .string_literal => |literal| { + try self.chunk.write(expression.line, .{.push_const = try self.declare_string(literal)}); + }, + + .symbol_literal => |literal| { + try self.chunk.write(expression.line, .{.push_const = try self.declare_symbol(literal)}); + }, + + .table_construct => |table_construct| { + var table_entry = table_construct.entry; + var field_count = @as(u32, 0); + + while (table_entry) |entry| : (table_entry = entry.next) { + try self.compile_expression(environment, entry, null); + + if (entry.kind != .key_value) { + try self.chunk.write(expression.line, .push_top); + } + + field_count += 1; + } + + try self.chunk.write(expression.line, .{.push_table = field_count}); + }, + + .key_value => |key_value| { + 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 Self.make(self.env, name orelse "", lambda_construct.environment); + + errdefer chunk.free(self.env); + + if (lambda_construct.environment.capture_count == 0) { + try self.chunk.write(expression.line, .{.push_const = try self.declare_chunk(chunk)}); + } else { + const lambda_captures = lambda_construct.environment.get_captures(); + var index = lambda_captures.len; + + while (index != 0) { + index -= 1; + + try self.chunk.write(expression.line, switch (lambda_captures[index]) { + .declaration_index => |declaration_index| .{.push_local = declaration_index}, + .capture_index => |capture_index| .{.push_binding = capture_index}, + }); + } + + try self.chunk.write(expression.line, .{.push_const = try self.declare_chunk(chunk)}); + try self.chunk.write(expression.line, .{.bind = lambda_construct.environment.capture_count}); + } + }, + + .binary_op => |binary_op| { + try self.compile_expression(environment, binary_op.lhs_operand, null); + try self.compile_expression(environment, binary_op.rhs_operand, null); + + try self.chunk.write(expression.line, switch (binary_op.operation) { + .addition => .add, + .subtraction => .sub, + .multiplication => .mul, + .divsion => .div, + .greater_equals_comparison => .cge, + .greater_than_comparison => .cgt, + .equals_comparison => .eql, + .less_than_comparison => .clt, + .less_equals_comparison => .cle, + }); + }, + + .unary_op => |unary_op| { + try self.compile_expression(environment, unary_op.operand, null); + + try self.chunk.write(expression.line, switch (unary_op.operation) { + .boolean_negation => .not, + .numeric_negation => .neg, + }); + }, + + .invoke => |invoke| { + const argument_count = try self.compile_argument(environment, invoke.argument); + + try self.compile_expression(environment, invoke.object, null); + try self.chunk.write(expression.line, .{.call = argument_count}); + }, + + .group => |group| try self.compile_expression(environment, group, null), + .import_builtin => try self.chunk.write(expression.line, .{.push_builtin = .import}), + .print_builtin => try self.chunk.write(expression.line, .{.push_builtin = .print}), + .vec2_builtin => try self.chunk.write(expression.line, .{.push_builtin = .vec2}), + .vec3_builtin => try self.chunk.write(expression.line, .{.push_builtin = .vec3}), + + .declaration_get => |declaration_get| { + if (get_local_index(environment, declaration_get.declaration)) |index| { + return self.chunk.write(expression.line, .{.push_local = index}); + } + + if (try self.get_binding_index(environment, declaration_get.declaration)) |index| { + try self.chunk.write(expression.line, .{.push_binding = index}); + + if (is_declaration_boxed(declaration_get.declaration)) { + try self.chunk.write(expression.line, .get_box); + } + + return; + } + + 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, null); + + return self.chunk.write(expression.line, .{.set_local = index}); + } + + if (try self.get_binding_index(environment, declaration_set.declaration)) |index| { + try self.chunk.write(expression.line, .{.push_binding = index}); + try self.compile_expression(environment, declaration_set.assign, null); + + if (is_declaration_boxed(declaration_set.declaration)) { + try self.chunk.write(expression.line, .set_box); + } + + return; + } + + return self.env.raise(error.IllegalState, "local out of scope", .{}); + }, + + .field_get => |field_get| { + try self.compile_expression(environment, field_get.object, null); + try self.chunk.write(expression.line, .{.push_const = try self.declare_symbol(field_get.identifier)}); + try self.chunk.write(expression.line, .get_dynamic); + }, + + .field_set => |field_set| { + try self.compile_expression(environment, field_set.object, null); + try self.chunk.write(expression.line, .{.push_const = try self.declare_symbol(field_set.identifier)}); + try self.compile_expression(environment, field_set.assign, null); + try self.chunk.write(expression.line, .set_dynamic); + }, + + .subscript_get => |subscript_get| { + try self.compile_expression(environment, subscript_get.object, null); + try self.compile_expression(environment, subscript_get.index, null); + try self.chunk.write(expression.line, .get_dynamic); + }, + + .subscript_set => |subscript_set| { + 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.write(expression.line, .set_dynamic); + }, + } + } + + pub fn compile_environment(self: *const Compiler, environment: *const tree.Environment) kym.RuntimeError!void { + if (environment.statement) |statement| { + const last_statement = try self.compile_statement(environment, statement); + + if (last_statement.kind != .@"return") { + try self.chunk.write(last_statement.line, .push_nil); + } + } + } + + fn compile_statement(self: *const Compiler, environment: *const tree.Environment, initial_statement: *const Stmt) kym.RuntimeError!*const Stmt { + var current_statement = initial_statement; + + while (true) { + switch (current_statement.kind) { + .@"return" => |@"return"| { + if (@"return".returned_expression) |expression| { + try self.compile_expression(environment, expression, null); + } else { + try self.chunk.write(current_statement.line, .push_nil); + } + + // TODO: Omit ret calls at ends of chunk. + try self.chunk.write(current_statement.line, .ret); + }, + + .@"while" => |@"while"| { + try self.compile_expression(environment, @"while".loop_expression, null); + try self.chunk.write(current_statement.line, .{.jf = 0}); + + const origin_index = @as(u32, @intCast(self.chunk.opcodes.values.len - 1)); + + _ = 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, null); + try self.chunk.write(current_statement.line, .{.jt = origin_index}); + }, + + .@"if" => |@"if"| { + try self.compile_expression(environment, @"if".then_expression, null); + try self.chunk.write(current_statement.line, .{.jf = 0}); + + const origin_index = @as(u32, @intCast(self.chunk.opcodes.values.len - 1)); + + _ = try self.compile_statement(environment, @"if".@"then"); + self.chunk.opcodes.values[origin_index].jf = @intCast(self.chunk.opcodes.values.len - 1); + + if (@"if".@"else") |@"else"| { + _ = try self.compile_statement(environment, @"else"); + } + }, + + .declare => |declare| { + try self.compile_expression(environment, declare.initial_expression, declare.declaration.identifier); + + if (is_declaration_boxed(declare.declaration)) { + try self.chunk.write(current_statement.line, .push_boxed); + } + }, + + .top_expression => |top_expression| { + try self.compile_expression(environment, top_expression, null); + + if (top_expression.kind == .invoke) { + try self.chunk.write(current_statement.line, .pop); + } + }, + } + + current_statement = current_statement.next orelse return current_statement; + } + } + + const constants_max = @as(usize, coral.math.max_int(@typeInfo(u16).Int)); + + fn declare_chunk(self: *const Compiler, chunk: Self) 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 {max} constants", .{ + .max = @as(usize, coral.math.max_int(@typeInfo(u16).Int)), + }); + } + + const constant = try self.env.new_dynamic(coral.io.bytes_of(&chunk), typeinfo); + + errdefer self.env.discard(constant); + + try self.chunk.constants.push_one(constant); + + return @intCast(self.chunk.constants.values.len - 1); + } + + fn declare_fixed(self: *const Compiler, fixed: kym.Fixed) kym.RuntimeError!u16 { + 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); + + errdefer self.env.discard(constant); + + try self.chunk.constants.push_one(constant); + + return @intCast(self.chunk.constants.values.len - 1); + } + + fn declare_float(self: *const Compiler, float: kym.Float) kym.RuntimeError!u16 { + 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); + + errdefer self.env.discard(constant); + + try self.chunk.constants.push_one(constant); + + return @intCast(self.chunk.constants.values.len - 1); + } + + fn declare_string(self: *const Compiler, string: []const coral.io.Byte) kym.RuntimeError!u16 { + 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); + + errdefer self.env.discard(constant); + + try self.chunk.constants.push_one(constant); + + return @intCast(self.chunk.constants.values.len - 1); + } + + fn declare_symbol(self: *const Compiler, symbol: []const coral.io.Byte) kym.RuntimeError!u16 { + 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); + + errdefer self.env.discard(constant); + + try self.chunk.constants.push_one(constant); + + return @intCast(self.chunk.constants.values.len - 1); + } + + fn get_binding_index(self: *const Compiler, environment: *const tree.Environment, declaration: *const tree.Declaration) kym.RuntimeError!?u8 { + var binding_index = @as(u8, 0); + + while (binding_index < environment.capture_count) : (binding_index += 1) { + var capture = &environment.captures[binding_index]; + var target_environment = environment.enclosing orelse return null; + + while (capture.* == .capture_index) { + capture = &target_environment.captures[capture.capture_index]; + target_environment = target_environment.enclosing orelse return null; + } + + try kym.assert(self.env, capture.* == .declaration_index); + + if (&target_environment.declarations[capture.declaration_index] == declaration) { + return binding_index; + } + } + + return null; + } + + fn get_local_index(environment: *const tree.Environment, declaration: *const tree.Declaration) ?u8 { + var remaining = environment.declaration_count; + + while (remaining != 0) { + remaining -= 1; + + if (&environment.declarations[remaining] == declaration) { + return remaining; + } + } + + return null; + } + + fn is_declaration_boxed(declaration: *const tree.Declaration) bool { + return declaration.is.captured and !declaration.is.readonly; + } +}; + const ConstList = coral.list.Stack(*kym.RuntimeRef); const LineList = coral.list.Stack(u32); diff --git a/source/ona/kym/Compiler.zig b/source/ona/kym/Compiler.zig deleted file mode 100644 index 771331f..0000000 --- a/source/ona/kym/Compiler.zig +++ /dev/null @@ -1,413 +0,0 @@ -const Chunk = @import("./Chunk.zig"); - -const Expr = @import("./Expr.zig"); - -const Stmt = @import("./Stmt.zig"); - -const coral = @import("coral"); - -const kym = @import("../kym.zig"); - -const tree = @import("./tree.zig"); - -chunk: *Chunk, -env: *kym.RuntimeEnv, - -const Self = @This(); - -fn compile_argument(self: Self, environment: *const tree.Environment, initial_argument: ?*const Expr) kym.RuntimeError!u8 { - // TODO: Exceeding 255 arguments will make the VM crash. - var maybe_argument = initial_argument; - var argument_count = @as(u8, 0); - - while (maybe_argument) |argument| { - try self.compile_expression(environment, argument, null); - - maybe_argument = argument.next; - argument_count += 1; - } - - return argument_count; -} - -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, - }; - - switch (expression.kind) { - .nil_literal => try self.chunk.write(expression.line, .push_nil), - .true_literal => try self.chunk.write(expression.line, .push_true), - .false_literal => try self.chunk.write(expression.line, .push_false), - - .number_literal => |literal| { - for (literal) |codepoint| { - if (codepoint == '.') { - return self.chunk.write(expression.line, .{ - .push_const = try self.declare_float(number_format.parse(literal, kym.Float) orelse unreachable), - }); - } - } - - try self.chunk.write(expression.line, .{ - .push_const = try self.declare_fixed(number_format.parse(literal, kym.Fixed) orelse unreachable), - }); - }, - - .string_literal => |literal| { - try self.chunk.write(expression.line, .{.push_const = try self.declare_string(literal)}); - }, - - .symbol_literal => |literal| { - try self.chunk.write(expression.line, .{.push_const = try self.declare_symbol(literal)}); - }, - - .table_construct => |table_construct| { - var table_entry = table_construct.entry; - var field_count = @as(u32, 0); - - while (table_entry) |entry| : (table_entry = entry.next) { - try self.compile_expression(environment, entry, null); - - if (entry.kind != .key_value) { - try self.chunk.write(expression.line, .push_top); - } - - field_count += 1; - } - - try self.chunk.write(expression.line, .{.push_table = field_count}); - }, - - .key_value => |key_value| { - 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, name orelse "", lambda_construct.environment); - - errdefer chunk.free(self.env); - - if (lambda_construct.environment.capture_count == 0) { - try self.chunk.write(expression.line, .{.push_const = try self.declare_chunk(chunk)}); - } else { - const lambda_captures = lambda_construct.environment.get_captures(); - var index = lambda_captures.len; - - while (index != 0) { - index -= 1; - - try self.chunk.write(expression.line, switch (lambda_captures[index]) { - .declaration_index => |declaration_index| .{.push_local = declaration_index}, - .capture_index => |capture_index| .{.push_binding = capture_index}, - }); - } - - try self.chunk.write(expression.line, .{.push_const = try self.declare_chunk(chunk)}); - try self.chunk.write(expression.line, .{.bind = lambda_construct.environment.capture_count}); - } - }, - - .binary_op => |binary_op| { - try self.compile_expression(environment, binary_op.lhs_operand, null); - try self.compile_expression(environment, binary_op.rhs_operand, null); - - try self.chunk.write(expression.line, switch (binary_op.operation) { - .addition => .add, - .subtraction => .sub, - .multiplication => .mul, - .divsion => .div, - .greater_equals_comparison => .cge, - .greater_than_comparison => .cgt, - .equals_comparison => .eql, - .less_than_comparison => .clt, - .less_equals_comparison => .cle, - }); - }, - - .unary_op => |unary_op| { - try self.compile_expression(environment, unary_op.operand, null); - - try self.chunk.write(expression.line, switch (unary_op.operation) { - .boolean_negation => .not, - .numeric_negation => .neg, - }); - }, - - .invoke => |invoke| { - const argument_count = try self.compile_argument(environment, invoke.argument); - - try self.compile_expression(environment, invoke.object, null); - try self.chunk.write(expression.line, .{.call = argument_count}); - }, - - .group => |group| try self.compile_expression(environment, group, null), - .import_builtin => try self.chunk.write(expression.line, .{.push_builtin = .import}), - .print_builtin => try self.chunk.write(expression.line, .{.push_builtin = .print}), - .vec2_builtin => try self.chunk.write(expression.line, .{.push_builtin = .vec2}), - .vec3_builtin => try self.chunk.write(expression.line, .{.push_builtin = .vec3}), - - .declaration_get => |declaration_get| { - if (get_local_index(environment, declaration_get.declaration)) |index| { - return self.chunk.write(expression.line, .{.push_local = index}); - } - - if (try self.get_binding_index(environment, declaration_get.declaration)) |index| { - try self.chunk.write(expression.line, .{.push_binding = index}); - - if (is_declaration_boxed(declaration_get.declaration)) { - try self.chunk.write(expression.line, .get_box); - } - - return; - } - - 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, null); - - return self.chunk.write(expression.line, .{.set_local = index}); - } - - if (try self.get_binding_index(environment, declaration_set.declaration)) |index| { - try self.chunk.write(expression.line, .{.push_binding = index}); - try self.compile_expression(environment, declaration_set.assign, null); - - if (is_declaration_boxed(declaration_set.declaration)) { - try self.chunk.write(expression.line, .set_box); - } - - return; - } - - return self.env.raise(error.IllegalState, "local out of scope", .{}); - }, - - .field_get => |field_get| { - try self.compile_expression(environment, field_get.object, null); - try self.chunk.write(expression.line, .{.push_const = try self.declare_symbol(field_get.identifier)}); - try self.chunk.write(expression.line, .get_dynamic); - }, - - .field_set => |field_set| { - try self.compile_expression(environment, field_set.object, null); - try self.chunk.write(expression.line, .{.push_const = try self.declare_symbol(field_set.identifier)}); - try self.compile_expression(environment, field_set.assign, null); - try self.chunk.write(expression.line, .set_dynamic); - }, - - .subscript_get => |subscript_get| { - try self.compile_expression(environment, subscript_get.object, null); - try self.compile_expression(environment, subscript_get.index, null); - try self.chunk.write(expression.line, .get_dynamic); - }, - - .subscript_set => |subscript_set| { - 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.write(expression.line, .set_dynamic); - }, - } -} - -pub fn compile_environment(self: Self, environment: *const tree.Environment) kym.RuntimeError!void { - if (environment.statement) |statement| { - const last_statement = try self.compile_statement(environment, statement); - - if (last_statement.kind != .@"return") { - try self.chunk.write(last_statement.line, .push_nil); - } - } -} - -fn compile_statement(self: Self, environment: *const tree.Environment, initial_statement: *const Stmt) kym.RuntimeError!*const Stmt { - var current_statement = initial_statement; - - while (true) { - switch (current_statement.kind) { - .@"return" => |@"return"| { - if (@"return".returned_expression) |expression| { - try self.compile_expression(environment, expression, null); - } else { - try self.chunk.write(current_statement.line, .push_nil); - } - - // TODO: Omit ret calls at ends of chunk. - try self.chunk.write(current_statement.line, .ret); - }, - - .@"while" => |@"while"| { - try self.compile_expression(environment, @"while".loop_expression, null); - try self.chunk.write(current_statement.line, .{.jf = 0}); - - const origin_index = @as(u32, @intCast(self.chunk.opcodes.values.len - 1)); - - _ = 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, null); - try self.chunk.write(current_statement.line, .{.jt = origin_index}); - }, - - .@"if" => |@"if"| { - try self.compile_expression(environment, @"if".then_expression, null); - try self.chunk.write(current_statement.line, .{.jf = 0}); - - const origin_index = @as(u32, @intCast(self.chunk.opcodes.values.len - 1)); - - _ = try self.compile_statement(environment, @"if".@"then"); - self.chunk.opcodes.values[origin_index].jf = @intCast(self.chunk.opcodes.values.len - 1); - - if (@"if".@"else") |@"else"| { - _ = try self.compile_statement(environment, @"else"); - } - }, - - .declare => |declare| { - try self.compile_expression(environment, declare.initial_expression, declare.declaration.identifier); - - if (is_declaration_boxed(declare.declaration)) { - try self.chunk.write(current_statement.line, .push_boxed); - } - }, - - .top_expression => |top_expression| { - try self.compile_expression(environment, top_expression, null); - - if (top_expression.kind == .invoke) { - try self.chunk.write(current_statement.line, .pop); - } - }, - } - - current_statement = current_statement.next orelse return current_statement; - } -} - -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 {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); - - errdefer self.env.discard(constant); - - try self.chunk.constants.push_one(constant); - - return @intCast(self.chunk.constants.values.len - 1); -} - -fn declare_fixed(self: Self, fixed: kym.Fixed) kym.RuntimeError!u16 { - 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); - - errdefer self.env.discard(constant); - - try self.chunk.constants.push_one(constant); - - return @intCast(self.chunk.constants.values.len - 1); -} - -fn declare_float(self: Self, float: kym.Float) kym.RuntimeError!u16 { - 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); - - errdefer self.env.discard(constant); - - try self.chunk.constants.push_one(constant); - - return @intCast(self.chunk.constants.values.len - 1); -} - -fn declare_string(self: Self, string: []const coral.io.Byte) kym.RuntimeError!u16 { - 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); - - errdefer self.env.discard(constant); - - try self.chunk.constants.push_one(constant); - - return @intCast(self.chunk.constants.values.len - 1); -} - -fn declare_symbol(self: Self, symbol: []const coral.io.Byte) kym.RuntimeError!u16 { - 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); - - errdefer self.env.discard(constant); - - try self.chunk.constants.push_one(constant); - - return @intCast(self.chunk.constants.values.len - 1); -} - -pub fn get_binding_index(self: *const Self, environment: *const tree.Environment, declaration: *const tree.Declaration) kym.RuntimeError!?u8 { - var binding_index = @as(u8, 0); - - while (binding_index < environment.capture_count) : (binding_index += 1) { - var capture = &environment.captures[binding_index]; - var target_environment = environment.enclosing orelse return null; - - while (capture.* == .capture_index) { - capture = &target_environment.captures[capture.capture_index]; - target_environment = target_environment.enclosing orelse return null; - } - - try kym.assert(self.env, capture.* == .declaration_index); - - if (&target_environment.declarations[capture.declaration_index] == declaration) { - return binding_index; - } - } - - return null; -} - -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 (&environment.declarations[remaining] == declaration) { - return remaining; - } - } - - return null; -} - -fn is_declaration_boxed(declaration: *const tree.Declaration) bool { - return declaration.is.captured and !declaration.is.readonly; -} From b28b1a86c96ba3cd920173c6a55cdbf20fbf265e Mon Sep 17 00:00:00 2001 From: kayomn Date: Mon, 6 Nov 2023 22:27:09 +0000 Subject: [PATCH 3/6] Fix double free and boxing bugs in VM --- debug/app.ona | 6 +++++- source/ona/kym.zig | 4 ++-- source/ona/kym/Chunk.zig | 23 +++++++++++++++++++---- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/debug/app.ona b/debug/app.ona index bb14b64..6920724 100644 --- a/debug/app.ona +++ b/debug/app.ona @@ -1,5 +1,9 @@ -let test_param = "monkey wrench" +let wrench = "wrench" + +var test_param = "monkey {wrench}" + +test_param = "monkey" let printer = lambda (pfx): @print(test_param) diff --git a/source/ona/kym.zig b/source/ona/kym.zig index 69c40fe..7ad0543 100644 --- a/source/ona/kym.zig +++ b/source/ona/kym.zig @@ -282,7 +282,7 @@ pub const RuntimeEnv = struct { pub fn get_boxed(self: *RuntimeEnv, boxable: *RuntimeRef) RuntimeError!?*RuntimeRef { return switch (boxable.object().payload) { .boxed => |boxed| if (boxed) |boxed_value| boxed_value.acquire() else null, - else => self.raise(error.TypeMismatch, "{typename} is not boxable", .{.typename = boxable.typename()}), + else => self.raise(error.TypeMismatch, "{typename} is not box-gettable", .{.typename = boxable.typename()}), }; } @@ -624,7 +624,7 @@ pub const RuntimeEnv = struct { boxed.* = if (value) |ref| ref.acquire() else null; }, - else => return self.raise(error.TypeMismatch, "{typename} is not boxable", .{ + else => return self.raise(error.TypeMismatch, "{typename} is not box-settable", .{ .typename = boxable.typename(), }), } diff --git a/source/ona/kym/Chunk.zig b/source/ona/kym/Chunk.zig index 7ebb7fb..20be8b5 100644 --- a/source/ona/kym/Chunk.zig +++ b/source/ona/kym/Chunk.zig @@ -167,7 +167,14 @@ const Compiler = struct { .declaration_get => |declaration_get| { if (get_local_index(environment, declaration_get.declaration)) |index| { - return self.chunk.write(expression.line, .{.push_local = index}); + if (is_declaration_boxed(declaration_get.declaration)) { + try self.chunk.write(expression.line, .{.push_local = index}); + try self.chunk.write(expression.line, .get_box); + } else { + try self.chunk.write(expression.line, .{.push_local = index}); + } + + return; } if (try self.get_binding_index(environment, declaration_get.declaration)) |index| { @@ -187,12 +194,19 @@ const Compiler = struct { if (get_local_index(environment, declaration_set.declaration)) |index| { try self.compile_expression(environment, declaration_set.assign, null); - return self.chunk.write(expression.line, .{.set_local = index}); + if (is_declaration_boxed(declaration_set.declaration)) { + try self.chunk.write(expression.line, .{.push_local = index}); + try self.chunk.write(expression.line, .set_box); + } else { + try self.chunk.write(expression.line, .{.set_local = index}); + } + + return; } if (try self.get_binding_index(environment, declaration_set.declaration)) |index| { - try self.chunk.write(expression.line, .{.push_binding = index}); try self.compile_expression(environment, declaration_set.assign, null); + try self.chunk.write(expression.line, .{.push_binding = index}); if (is_declaration_boxed(declaration_set.declaration)) { try self.chunk.write(expression.line, .set_box); @@ -730,7 +744,8 @@ pub fn execute(self: *Self, env: *kym.RuntimeEnv, frame: *const kym.Frame) kym.R const value = try env.expect(try env.pop_local()); - defer env.discard(box); + errdefer env.discard(value); + try env.set_boxed(box, value); }, From 94a13b0d3c3dde275a060d82a1efb9e08287103f Mon Sep 17 00:00:00 2001 From: kayomn Date: Tue, 7 Nov 2023 23:02:49 +0000 Subject: [PATCH 4/6] Fix compilation errors with Stack list --- source/coral/list.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/coral/list.zig b/source/coral/list.zig index a9f48ea..5fa5040 100644 --- a/source/coral/list.zig +++ b/source/coral/list.zig @@ -17,7 +17,7 @@ pub fn Stack(comptime Value: type) type { } pub fn drop(self: *Self, count: usize) bool { - if (math.checked_sub(self.values, count)) |updated_count| { + if (math.checked_sub(self.values.len, count)) |updated_count| { self.values = self.values[0 .. updated_count]; return true; From 63792ea98393ce7e175e07bd36fa41fbe6058bed Mon Sep 17 00:00:00 2001 From: kayomn Date: Tue, 7 Nov 2023 23:06:11 +0000 Subject: [PATCH 5/6] Make tokenization aware of string templates --- source/ona/kym/tokens.zig | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/source/ona/kym/tokens.zig b/source/ona/kym/tokens.zig index 4acf6e6..cbc604d 100755 --- a/source/ona/kym/tokens.zig +++ b/source/ona/kym/tokens.zig @@ -31,6 +31,7 @@ pub const Token = union(enum) { number: []const coral.io.Byte, string: []const coral.io.Byte, + template_string: []const coral.io.Byte, keyword_nil, keyword_false, @@ -81,6 +82,7 @@ pub const Token = union(enum) { .number => |literal| literal, .string => |literal| literal, + .template_string => |literal| literal, .keyword_const => "const", .keyword_nil => "nil", @@ -305,6 +307,22 @@ pub const Stream = struct { return; }, + '`' => { + cursor += 1; + + const begin = cursor; + + while (cursor < self.source.len) switch (self.source[cursor]) { + '`' => break, + else => cursor += 1, + }; + + self.token = .{.template_string = self.source[begin .. cursor]}; + cursor += 1; + + return; + }, + '"' => { cursor += 1; From c30f10db96af773bd13a7d793a347a623085ddea Mon Sep 17 00:00:00 2001 From: kayomn Date: Tue, 7 Nov 2023 23:22:25 +0000 Subject: [PATCH 6/6] Implement string interpolation --- debug/app.ona | 7 +- source/ona/kym.zig | 81 ++++++++++- source/ona/kym/Chunk.zig | 66 +++++++-- source/ona/kym/tokens.zig | 8 +- source/ona/kym/tree.zig | 14 +- source/ona/kym/{ => tree}/Expr.zig | 212 ++++++++++++++++++++++------- source/ona/kym/{ => tree}/Stmt.zig | 38 +++--- 7 files changed, 331 insertions(+), 95 deletions(-) rename source/ona/kym/{ => tree}/Expr.zig (71%) rename source/ona/kym/{ => tree}/Stmt.zig (81%) diff --git a/debug/app.ona b/debug/app.ona index 6920724..cf95daf 100644 --- a/debug/app.ona +++ b/debug/app.ona @@ -1,9 +1,6 @@ -let wrench = "wrench" - -var test_param = "monkey {wrench}" - -test_param = "monkey" +let tool = "wrench" +var test_param = `monkey {tool} {2 + 1 - 1}` let printer = lambda (pfx): @print(test_param) diff --git a/source/ona/kym.zig b/source/ona/kym.zig index 7ad0543..bbebc34 100644 --- a/source/ona/kym.zig +++ b/source/ona/kym.zig @@ -133,6 +133,80 @@ pub const RuntimeEnv = struct { }; } + pub fn concat(self: *RuntimeEnv, values: []const *const RuntimeRef) RuntimeError!*RuntimeRef { + if (values.len == 0) { + return self.new_string(""); + } + + var remaining_value_count = values.len; + + errdefer { + var poppable_value_count = values.len - remaining_value_count; + + while (poppable_value_count != 0) { + poppable_value_count -= 1; + + const popped_value = self.pop_local() catch unreachable; + + coral.debug.assert(popped_value != null); + self.discard(popped_value.?); + } + } + + var length = @as(usize, 0); + + while (remaining_value_count != 0) { + remaining_value_count -= 1; + + const value_string = try self.to_string(values[remaining_value_count]); + + errdefer self.discard(value_string); + + const string = value_string.as_string(); + + coral.debug.assert(string != null); + + try self.locals.push_one(value_string); + + length += string.?.len; + } + + const buffer = try coral.io.allocate_many(self.allocator, length, @as(coral.io.Byte, 0)); + + errdefer self.allocator.deallocate(buffer); + + var written = @as(usize, 0); + + while (remaining_value_count != values.len) : (remaining_value_count += 1) { + const value_string = try self.pop_local(); + + coral.debug.assert(value_string != null); + + defer self.discard(value_string.?); + + const string = value_string.?.as_string(); + + coral.debug.assert(string != null); + + coral.io.copy(buffer[written ..], string.?); + + written += string.?.len; + + coral.debug.assert(written <= length); + } + + return RuntimeRef.allocate(self.allocator, .{ + .ref_count = 1, + + .payload = .{ + .string = .{ + .ptr = buffer.ptr, + .len = @intCast(buffer.len), + }, + }, + }); + } + pub fn discard(self: *RuntimeEnv, value: *RuntimeRef) void { var object = value.object(); @@ -573,8 +647,9 @@ pub const RuntimeEnv = struct { if (callable.as_dynamic(Chunk.typeinfo)) |chunk_userdata| { const chunk = @as(*Chunk, @ptrCast(@alignCast(chunk_userdata))); + const line = chunk.lines.values[chunk.cursor]; - const chunk_name = try coral.utf8.alloc_formatted(self.allocator, "{name}@{line}", .{ + const chunk_name = try coral.utf8.alloc_formatted(self.allocator, "{name}@{line_number}", .{ .name = get_name: { const string = name.as_string(); @@ -583,7 +658,7 @@ pub const RuntimeEnv = struct { break: get_name string.?; }, - .line = chunk.lines.values[chunk.cursor], + .line_number = line.number, }); defer self.allocator.deallocate(chunk_name); @@ -1013,6 +1088,8 @@ pub const Typeinfo = struct { }; pub fn assert(env: *RuntimeEnv, condition: bool) RuntimeError!void { + coral.debug.assert(condition); + if (!condition) { return env.raise(error.IllegalState, "assertion", .{}); } diff --git a/source/ona/kym/Chunk.zig b/source/ona/kym/Chunk.zig index 20be8b5..6fef2c3 100644 --- a/source/ona/kym/Chunk.zig +++ b/source/ona/kym/Chunk.zig @@ -1,7 +1,3 @@ -const Expr = @import("./Expr.zig"); - -const Stmt = @import("./Stmt.zig"); - const app = @import("../app.zig"); const coral = @import("coral"); @@ -10,6 +6,8 @@ const file = @import("../file.zig"); const kym = @import("../kym.zig"); +const tokens = @import("./tokens.zig"); + const tree = @import("./tree.zig"); name: *kym.RuntimeRef, @@ -31,7 +29,7 @@ const Compiler = struct { chunk: *Self, env: *kym.RuntimeEnv, - fn compile_argument(self: *const Compiler, environment: *const tree.Environment, initial_argument: ?*const Expr) kym.RuntimeError!u8 { + fn compile_argument(self: *const Compiler, environment: *const tree.Environment, initial_argument: ?*const tree.Expr) kym.RuntimeError!u8 { // TODO: Exceeding 255 arguments will make the VM crash. var maybe_argument = initial_argument; var argument_count = @as(u8, 0); @@ -46,7 +44,7 @@ const Compiler = struct { return argument_count; } - fn compile_expression(self: *const Compiler, environment: *const tree.Environment, expression: *const Expr, name: ?[]const coral.io.Byte) kym.RuntimeError!void { + fn compile_expression(self: *const Compiler, environment: *const tree.Environment, expression: *const tree.Expr, name: ?[]const coral.io.Byte) kym.RuntimeError!void { const number_format = coral.utf8.DecimalFormat{ .delimiter = "_", .positive_prefix = .none, @@ -75,6 +73,24 @@ const Compiler = struct { try self.chunk.write(expression.line, .{.push_const = try self.declare_string(literal)}); }, + .string_template => { + var current_expression = expression.next orelse { + return self.chunk.write(expression.line, .{.push_const = try self.declare_string("")}); + }; + + var component_count = @as(u8, 0); + + while (true) { + try self.compile_expression(environment, current_expression, null); + + component_count += 1; + + current_expression = current_expression.next orelse { + return self.chunk.write(expression.line, .{.push_concat = component_count}); + }; + } + }, + .symbol_literal => |literal| { try self.chunk.write(expression.line, .{.push_const = try self.declare_symbol(literal)}); }, @@ -256,7 +272,7 @@ const Compiler = struct { } } - fn compile_statement(self: *const Compiler, environment: *const tree.Environment, initial_statement: *const Stmt) kym.RuntimeError!*const Stmt { + fn compile_statement(self: *const Compiler, environment: *const tree.Environment, initial_statement: *const tree.Stmt) kym.RuntimeError!*const tree.Stmt { var current_statement = initial_statement; while (true) { @@ -445,7 +461,7 @@ const Compiler = struct { const ConstList = coral.list.Stack(*kym.RuntimeRef); -const LineList = coral.list.Stack(u32); +const LineList = coral.list.Stack(tokens.Line); pub const Opcode = union (enum) { ret, @@ -459,6 +475,7 @@ pub const Opcode = union (enum) { push_table: u32, push_builtin: Builtin, push_binding: u8, + push_concat: u8, push_boxed, set_local: u8, get_dynamic, @@ -540,6 +557,10 @@ pub fn dump(chunk: Self, env: *kym.RuntimeEnv) kym.RuntimeError!*kym.RuntimeRef .binding = push_binding, }), + .push_concat => |push_concat| coral.utf8.print_formatted(writer, "push concat ({count})\n", .{ + .count = push_concat, + }), + .push_builtin => |push_builtin| coral.utf8.print_formatted(writer, "push builtin ({builtin})\n", .{ .builtin = switch (push_builtin) { .import => "import", @@ -670,6 +691,33 @@ pub fn execute(self: *Self, env: *kym.RuntimeEnv, frame: *const kym.Frame) kym.R try env.locals.push_one(if (self.bindings[push_binding]) |value| value.acquire() else null); }, + .push_concat => |push_concat| { + const frame_locals = env.locals.values[frame.locals_top ..]; + + try kym.assert(env, push_concat <= frame_locals.len); + + const concat_locals = frame_locals[(frame_locals.len - push_concat) .. frame_locals.len]; + + for (concat_locals) |value| { + _ = try env.expect(value); + } + + const concatenated_value = try env.concat(@ptrCast(concat_locals)); + + errdefer env.discard(concatenated_value); + + var to_pop = concat_locals.len; + + while (to_pop != 0) : (to_pop -= 1) { + const popped = env.pop_local() catch unreachable; + + coral.debug.assert(popped != null); + env.discard(popped.?); + } + + try env.locals.push_one(concatenated_value); + }, + .bind => |bind| { const callable = try env.expect(try env.pop_local()); @@ -1071,7 +1119,7 @@ fn typeinfo_to_string(env: *kym.RuntimeEnv, userdata: []coral.io.Byte) coral.io. return env.to_string(@as(*Self, @ptrCast(@alignCast(userdata))).name); } -pub fn write(self: *Self, line: u32, opcode: Opcode) coral.io.AllocationError!void { +pub fn write(self: *Self, line: tokens.Line, opcode: Opcode) coral.io.AllocationError!void { try self.opcodes.push_one(opcode); try self.lines.push_one(line); } diff --git a/source/ona/kym/tokens.zig b/source/ona/kym/tokens.zig index cbc604d..edb37fa 100755 --- a/source/ona/kym/tokens.zig +++ b/source/ona/kym/tokens.zig @@ -1,5 +1,9 @@ const coral = @import("coral"); +pub const Line = struct { + number: u32, +}; + pub const Token = union(enum) { end, unknown: coral.io.Byte, @@ -105,7 +109,7 @@ pub const Token = union(enum) { pub const Stream = struct { source: []const coral.io.Byte, - lines_stepped: u32 = 1, + line: Line = .{.number = 1}, token: Token = .newline, pub fn skip_newlines(self: *Stream) void { @@ -136,7 +140,7 @@ pub const Stream = struct { '\n' => { cursor += 1; self.token = .newline; - self.lines_stepped += 1; + self.line.number += 1; return; }, diff --git a/source/ona/kym/tree.zig b/source/ona/kym/tree.zig index 8b1c4b4..ee2ca11 100644 --- a/source/ona/kym/tree.zig +++ b/source/ona/kym/tree.zig @@ -1,6 +1,6 @@ -const Expr = @import("./Expr.zig"); +pub const Expr = @import("./tree/Expr.zig"); -const Stmt = @import("./Stmt.zig"); +pub const Stmt = @import("./tree/Stmt.zig"); const coral = @import("coral"); @@ -163,22 +163,22 @@ pub const Root = struct { const MessageList = coral.list.Stack([]coral.io.Byte); - pub fn report_error(self: *Root, stream: *tokens.Stream, comptime format: []const u8, args: anytype) ParseError { + pub fn report_error(self: *Root, line: tokens.Line, comptime format: []const u8, args: anytype) ParseError { const allocator = self.arena.as_allocator(); - try self.error_messages.push_one(try coral.utf8.alloc_formatted(allocator, "{line}: {message}", .{ + try self.error_messages.push_one(try coral.utf8.alloc_formatted(allocator, "{line_number}: {message}", .{ .message = try coral.utf8.alloc_formatted(allocator, format, args), - .line = stream.lines_stepped, + .line_number = line.number, })); return error.BadSyntax; } - pub fn report_declare_error(self: *Root, stream: *tokens.Stream, identifier: []const coral.io.Byte, @"error": Environment.DeclareError) ParseError { + pub fn report_declare_error(self: *Root, line: tokens.Line, identifier: []const coral.io.Byte, @"error": Environment.DeclareError) ParseError { return switch (@"error") { error.OutOfMemory => error.OutOfMemory, - error.DeclarationExists => self.report_error(stream, "declaration `{identifier}` already exists", .{ + error.DeclarationExists => self.report_error(line, "declaration `{identifier}` already exists", .{ .identifier = identifier, }), }; diff --git a/source/ona/kym/Expr.zig b/source/ona/kym/tree/Expr.zig similarity index 71% rename from source/ona/kym/Expr.zig rename to source/ona/kym/tree/Expr.zig index 3213191..5d32261 100644 --- a/source/ona/kym/Expr.zig +++ b/source/ona/kym/tree/Expr.zig @@ -2,12 +2,12 @@ const Stmt = @import("./Stmt.zig"); const coral = @import("coral"); -const tokens = @import("./tokens.zig"); +const tokens = @import("../tokens.zig"); -const tree = @import("./tree.zig"); +const tree = @import("../tree.zig"); next: ?*const Self = null, -line: u32, +line: tokens.Line, kind: union (enum) { nil_literal, @@ -15,6 +15,7 @@ kind: union (enum) { false_literal, number_literal: []const coral.io.Byte, string_literal: []const coral.io.Byte, + string_template, symbol_literal: []const coral.io.Byte, table_construct: TableConstruct, key_value: KeyValue, @@ -74,14 +75,14 @@ pub const BinaryOp = struct { stream.step(); if (stream.token == .end) { - return root.report_error(stream, "expected other half of expression after `" ++ comptime token.text() ++ "`", .{}); + return root.report_error(stream.line, "expected other half of expression after `" ++ comptime token.text() ++ "`", .{}); } // TODO: Remove once Zig has fixed struct self-reassignment. const unnecessary_temp = expression; expression = try root.create_expr(.{ - .line = stream.lines_stepped, + .line = stream.line, .kind = .{ .binary_op = .{ @@ -155,6 +156,68 @@ pub const TableConstruct = struct { entry: ?*const Self, }; +const TemplateToken = union (enum) { + invalid: []const coral.io.Byte, + literal: []const coral.io.Byte, + expression: []const coral.io.Byte, + + fn extract(source: *[]const coral.io.Byte) ?TemplateToken { + var cursor = @as(usize, 0); + + defer source.* = source.*[cursor ..]; + + while (cursor < source.len) { + switch (source.*[cursor]) { + '{' => { + cursor += 1; + + while (true) : (cursor += 1) { + if (cursor == source.len) { + return .{.invalid = source.*[0 .. cursor]}; + } + + if (source.*[cursor] == '}') { + const token = TemplateToken{.expression = source.*[1 .. cursor]}; + + cursor += 1; + + return token; + } + } + }, + + else => { + cursor += 1; + + while (true) : (cursor += 1) { + if (cursor == source.len) { + return .{.literal = source.*[0 .. cursor]}; + } + + if (source.*[cursor] == '{') { + const cursor_next = cursor + 1; + + if (cursor_next == source.len) { + return .{.invalid = source.*[0 .. cursor]}; + } + + if (source.*[cursor_next] == '{') { + cursor = cursor_next; + + return .{.literal = source.*[0 .. cursor]}; + } + + return .{.literal = source.*[0 .. cursor]}; + } + } + } + } + } + + return null; + } +}; + pub const UnaryOp = struct { operand: *Self, operation: Operation, @@ -172,16 +235,16 @@ pub fn parse(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Enviro stream.skip_newlines(); if (stream.token == .end) { - return root.report_error(stream, "expected assignment after `=`", .{}); + return root.report_error(stream.line, "expected assignment after `=`", .{}); } return root.create_expr(.{ - .line = stream.lines_stepped, + .line = stream.line, .kind = switch (expression.kind) { .declaration_get => |declaration_get| convert: { if (declaration_get.declaration.is.readonly) { - return root.report_error(stream, "readonly declarations cannot be re-assigned", .{}); + return root.report_error(stream.line, "readonly declarations cannot be re-assigned", .{}); } break: convert .{ @@ -208,7 +271,7 @@ pub fn parse(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Enviro }, }, - else => return root.report_error(stream, "expected local or field on left-hand side of expression", .{}), + else => return root.report_error(stream.line, "expected local or field on left-hand side of expression", .{}), }, }); } @@ -244,13 +307,13 @@ fn parse_factor(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Env const unnecessary_temp = expression; expression = try root.create_expr(.{ - .line = stream.lines_stepped, + .line = stream.line, .kind = .{ .field_get = .{ .identifier = switch (stream.token) { .identifier => |field_identifier| field_identifier, - else => return root.report_error(stream, "expected identifier after `.`", .{}), + else => return root.report_error(stream.line, "expected identifier after `.`", .{}), }, .object = unnecessary_temp, @@ -268,7 +331,7 @@ fn parse_factor(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Env const unnecessary_temp = expression; expression = try root.create_expr(.{ - .line = stream.lines_stepped, + .line = stream.line, .kind = .{ .subscript_get = .{ @@ -279,14 +342,14 @@ fn parse_factor(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Env }); if (stream.token != .symbol_bracket_right) { - return root.report_error(stream, "expected closing `]` on subscript", .{}); + return root.report_error(stream.line, "expected closing `]` on subscript", .{}); } stream.skip_newlines(); }, .symbol_paren_left => { - const lines_stepped = stream.lines_stepped; + const lines_stepped = stream.line; stream.skip_newlines(); @@ -301,7 +364,7 @@ fn parse_factor(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Env switch (stream.token) { .symbol_comma => stream.skip_newlines(), .symbol_paren_right => break, - else => return root.report_error(stream, "expected `,` or `)` after lambda argument", .{}), + else => return root.report_error(stream.line, "expected `,` or `)` after lambda argument", .{}), } const next_argument = try parse(root, stream, environment); @@ -343,13 +406,13 @@ fn parse_operand(root: *tree.Root, stream: *tokens.Stream, environment: *tree.En const expression = try parse(root, stream, environment); if (stream.token != .symbol_paren_right) { - return root.report_error(stream, "expected a closing `)` after expression", .{}); + return root.report_error(stream.line, "expected a closing `)` after expression", .{}); } stream.skip_newlines(); return root.create_expr(.{ - .line = stream.lines_stepped, + .line = stream.line, .kind = .{.group = expression}, }); }, @@ -358,7 +421,7 @@ fn parse_operand(root: *tree.Root, stream: *tokens.Stream, environment: *tree.En stream.skip_newlines(); return root.create_expr(.{ - .line = stream.lines_stepped, + .line = stream.line, .kind = .nil_literal, }); }, @@ -367,7 +430,7 @@ fn parse_operand(root: *tree.Root, stream: *tokens.Stream, environment: *tree.En stream.skip_newlines(); return root.create_expr(.{ - .line = stream.lines_stepped, + .line = stream.line, .kind = .true_literal, }); }, @@ -376,7 +439,7 @@ fn parse_operand(root: *tree.Root, stream: *tokens.Stream, environment: *tree.En stream.skip_newlines(); return root.create_expr(.{ - .line = stream.lines_stepped, + .line = stream.line, .kind = .false_literal, }); }, @@ -385,7 +448,7 @@ fn parse_operand(root: *tree.Root, stream: *tokens.Stream, environment: *tree.En stream.skip_newlines(); return root.create_expr(.{ - .line = stream.lines_stepped, + .line = stream.line, .kind = .{.number_literal = value}, }); }, @@ -394,50 +457,58 @@ fn parse_operand(root: *tree.Root, stream: *tokens.Stream, environment: *tree.En stream.skip_newlines(); return root.create_expr(.{ - .line = stream.lines_stepped, + .line = stream.line, .kind = .{.string_literal = value}, }); }, + .template_string => |value| { + const line = stream.line; + + stream.skip_newlines(); + + return parse_template(root, value, line, environment); + }, + .symbol_at => { stream.step(); const identifier = switch (stream.token) { .identifier => |identifier| identifier, - else => return root.report_error(stream, "expected identifier after `@`", .{}), + else => return root.report_error(stream.line, "expected identifier after `@`", .{}), }; stream.skip_newlines(); if (coral.io.are_equal(identifier, "import")) { return root.create_expr(.{ - .line = stream.lines_stepped, + .line = stream.line, .kind = .import_builtin, }); } if (coral.io.are_equal(identifier, "print")) { return root.create_expr(.{ - .line = stream.lines_stepped, + .line = stream.line, .kind = .print_builtin, }); } if (coral.io.are_equal(identifier, "vec2")) { return root.create_expr(.{ - .line = stream.lines_stepped, + .line = stream.line, .kind = .vec2_builtin, }); } if (coral.io.are_equal(identifier, "vec3")) { return root.create_expr(.{ - .line = stream.lines_stepped, + .line = stream.line, .kind = .vec3_builtin, }); } - return root.report_error(stream, "unexpected identifier after `@`", .{}); + return root.report_error(stream.line, "unexpected identifier after `@`", .{}); }, .symbol_period => { @@ -445,13 +516,13 @@ fn parse_operand(root: *tree.Root, stream: *tokens.Stream, environment: *tree.En const identifier = switch (stream.token) { .identifier => |identifier| identifier, - else => return root.report_error(stream, "expected identifier after `.`", .{}), + else => return root.report_error(stream.line, "expected identifier after `.`", .{}), }; stream.skip_newlines(); return root.create_expr(.{ - .line = stream.lines_stepped, + .line = stream.line, .kind = .{.symbol_literal = identifier}, }); }, @@ -460,12 +531,12 @@ fn parse_operand(root: *tree.Root, stream: *tokens.Stream, environment: *tree.En stream.skip_newlines(); return root.create_expr(.{ - .line = stream.lines_stepped, + .line = stream.line, .kind = .{ .declaration_get = .{ .declaration = (try environment.resolve_declaration(identifier)) orelse { - return root.report_error(stream, "undefined identifier `{identifier}`", .{ + return root.report_error(stream.line, "undefined identifier `{identifier}`", .{ .identifier = identifier, }); } @@ -478,7 +549,7 @@ fn parse_operand(root: *tree.Root, stream: *tokens.Stream, environment: *tree.En stream.skip_newlines(); if (stream.token != .symbol_paren_left) { - return root.report_error(stream, "expected `(` after opening lambda block", .{}); + return root.report_error(stream.line, "expected `(` after opening lambda block", .{}); } stream.skip_newlines(); @@ -488,11 +559,11 @@ fn parse_operand(root: *tree.Root, stream: *tokens.Stream, environment: *tree.En while (stream.token != .symbol_paren_right) { const identifier = switch (stream.token) { .identifier => |identifier| identifier, - else => return root.report_error(stream, "expected identifier", .{}), + else => return root.report_error(stream.line, "expected identifier", .{}), }; _ = lambda_environment.declare_argument(identifier) catch |declare_error| { - return root.report_declare_error(stream, identifier, declare_error); + return root.report_declare_error(stream.line, identifier, declare_error); }; stream.skip_newlines(); @@ -500,14 +571,14 @@ fn parse_operand(root: *tree.Root, stream: *tokens.Stream, environment: *tree.En switch (stream.token) { .symbol_comma => stream.skip_newlines(), .symbol_paren_right => break, - else => return root.report_error(stream, "expected `,` or `)` after identifier", .{}), + else => return root.report_error(stream.line, "expected `,` or `)` after identifier", .{}), } } stream.skip_newlines(); if (stream.token != .symbol_colon) { - return root.report_error(stream, "expected `:` after closing `)` of lambda identifiers", .{}); + return root.report_error(stream.line, "expected `:` after closing `)` of lambda identifiers", .{}); } stream.skip_newlines(); @@ -529,7 +600,7 @@ fn parse_operand(root: *tree.Root, stream: *tokens.Stream, environment: *tree.En stream.skip_newlines(); return root.create_expr(.{ - .line = stream.lines_stepped, + .line = stream.line, .kind = .{.lambda_construct = .{.environment = lambda_environment}}, }); }, @@ -539,7 +610,7 @@ fn parse_operand(root: *tree.Root, stream: *tokens.Stream, environment: *tree.En if (stream.token == .symbol_brace_right) { return root.create_expr(.{ - .line = stream.lines_stepped, + .line = stream.line, .kind = .{.table_construct = .{.entry = null}}, }); } @@ -561,13 +632,13 @@ fn parse_operand(root: *tree.Root, stream: *tokens.Stream, environment: *tree.En } if (stream.token != .symbol_brace_right) { - return root.report_error(stream, "expected closing `}` on table construct", .{}); + return root.report_error(stream.line, "expected closing `}` on table construct", .{}); } stream.skip_newlines(); return root.create_expr(.{ - .line = stream.lines_stepped, + .line = stream.line, .kind = .{.table_construct = .{.entry = first_entry}}, }); }, @@ -576,7 +647,7 @@ fn parse_operand(root: *tree.Root, stream: *tokens.Stream, environment: *tree.En stream.skip_newlines(); return root.create_expr(.{ - .line = stream.lines_stepped, + .line = stream.line, .kind = .{ .unary_op = .{ @@ -591,7 +662,7 @@ fn parse_operand(root: *tree.Root, stream: *tokens.Stream, environment: *tree.En stream.skip_newlines(); return root.create_expr(.{ - .line = stream.lines_stepped, + .line = stream.line, .kind = .{ .unary_op = .{ @@ -602,7 +673,7 @@ fn parse_operand(root: *tree.Root, stream: *tokens.Stream, environment: *tree.En }); }, - else => return root.report_error(stream, "unexpected token in expression", .{}), + else => return root.report_error(stream.line, "unexpected token in expression", .{}), } } @@ -613,26 +684,26 @@ fn parse_table_entry(root: *tree.Root, stream: *tokens.Stream, environment: *tre const field = switch (stream.token) { .identifier => |identifier| identifier, - else => return root.report_error(stream, "expected identifier in field symbol literal", .{}), + else => return root.report_error(stream.line, "expected identifier in field symbol literal", .{}), }; stream.skip_newlines(); if (stream.token != .symbol_equals) { - return root.report_error(stream, "expected `=` after table symbol key", .{}); + return root.report_error(stream.line, "expected `=` after table symbol key", .{}); } stream.skip_newlines(); return root.create_expr(.{ - .line = stream.lines_stepped, + .line = stream.line, .kind = .{ .key_value = .{ .value = try parse(root, stream, environment), .key = try root.create_expr(.{ - .line = stream.lines_stepped, + .line = stream.line, .kind = .{.symbol_literal = field}, }), }, @@ -646,19 +717,19 @@ fn parse_table_entry(root: *tree.Root, stream: *tokens.Stream, environment: *tre const key = try parse(root, stream, environment); if (stream.token != .symbol_bracket_right) { - return root.report_error(stream, "expected `]` after subscript index expression", .{}); + return root.report_error(stream.line, "expected `]` after subscript index expression", .{}); } stream.skip_newlines(); if (stream.token != .symbol_equals) { - return root.report_error(stream, "expected `=` after table expression key", .{}); + return root.report_error(stream.line, "expected `=` after table expression key", .{}); } stream.skip_newlines(); return root.create_expr(.{ - .line = stream.lines_stepped, + .line = stream.line, .kind = .{ .key_value = .{ @@ -673,6 +744,45 @@ fn parse_table_entry(root: *tree.Root, stream: *tokens.Stream, environment: *tre } } +fn parse_template(root: *tree.Root, template: []const coral.io.Byte, line: tokens.Line, environment: *tree.Environment) tree.ParseError!*Self { + const expression_head = try root.create_expr(.{ + .line = line, + .kind = .string_template, + }); + + var expression_tail = expression_head; + var source = template; + + while (TemplateToken.extract(&source)) |token| { + const expression = try switch (token) { + .invalid => |invalid| root.report_error(line, "invalid template format: `{invalid}`", .{ + .invalid = invalid, + }), + + .literal => |literal| root.create_expr(.{ + .line = line, + .kind = .{.string_literal = literal}, + }), + + .expression => |expression| create: { + var stream = tokens.Stream{ + .source = expression, + .line = line, + }; + + stream.step(); + + break: create try parse(root, &stream, environment); + }, + }; + + expression_tail.next = expression; + expression_tail = expression; + } + + return expression_head; +} + const parse_term = BinaryOp.parser(parse_factor, &.{ .multiplication, .divsion, diff --git a/source/ona/kym/Stmt.zig b/source/ona/kym/tree/Stmt.zig similarity index 81% rename from source/ona/kym/Stmt.zig rename to source/ona/kym/tree/Stmt.zig index df49893..f1cdff6 100644 --- a/source/ona/kym/Stmt.zig +++ b/source/ona/kym/tree/Stmt.zig @@ -2,12 +2,12 @@ const Expr = @import("./Expr.zig"); const coral = @import("coral"); -const tokens = @import("./tokens.zig"); +const tokens = @import("../tokens.zig"); -const tree = @import("./tree.zig"); +const tree = @import("../tree.zig"); next: ?*const Self = null, -line: u32, +line: tokens.Line, kind: union (enum) { top_expression: *const Expr, @@ -46,17 +46,17 @@ pub fn parse(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Enviro if (stream.token != .end and stream.token != .newline) { return root.create_stmt(.{ - .line = stream.lines_stepped, + .line = stream.line, .kind = .{.@"return" = .{.returned_expression = try Expr.parse(root, stream, environment)}}, }); } if (stream.token != .end and stream.token != .newline) { - return root.report_error(stream, "expected end or newline after return statement", .{}); + return root.report_error(stream.line, "expected end or newline after return statement", .{}); } return root.create_stmt(.{ - .line = stream.lines_stepped, + .line = stream.line, .kind = .{.@"return" = .{.returned_expression = null}}, }); }, @@ -69,7 +69,7 @@ pub fn parse(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Enviro const condition_expression = try Expr.parse(root, stream, environment); if (stream.token != .symbol_colon) { - return root.report_error(stream, "expected `:` after `while` statement", .{}); + return root.report_error(stream.line, "expected `:` after `while` statement", .{}); } stream.skip_newlines(); @@ -88,7 +88,7 @@ pub fn parse(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Enviro } return root.create_stmt(.{ - .line = stream.lines_stepped, + .line = stream.line, .kind = .{ .@"while" = .{ @@ -106,13 +106,13 @@ pub fn parse(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Enviro const identifier = switch (stream.token) { .identifier => |identifier| identifier, - else => return root.report_error(stream, "expected identifier after declaration", .{}), + else => return root.report_error(stream.line, "expected identifier after declaration", .{}), }; stream.skip_newlines(); if (stream.token != .symbol_equals) { - return root.report_error(stream, "expected `=` after declaration `{identifier}`", .{ + return root.report_error(stream.line, "expected `=` after declaration `{identifier}`", .{ .identifier = identifier, }); } @@ -120,7 +120,7 @@ pub fn parse(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Enviro stream.skip_newlines(); return root.create_stmt(.{ - .line = stream.lines_stepped, + .line = stream.line, .kind = .{ .declare = .{ @@ -129,12 +129,12 @@ pub fn parse(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Enviro .declaration = declare: { if (is_constant) { break: declare environment.declare_constant(identifier) catch |declaration_error| { - return root.report_declare_error(stream, identifier, declaration_error); + return root.report_declare_error(stream.line, identifier, declaration_error); }; } break: declare environment.declare_variable(identifier) catch |declaration_error| { - return root.report_declare_error(stream, identifier, declaration_error); + return root.report_declare_error(stream.line, identifier, declaration_error); }; }, }, @@ -145,7 +145,7 @@ pub fn parse(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Enviro .keyword_if => return parse_branch(root, stream, environment), else => return root.create_stmt(.{ - .line = stream.lines_stepped, + .line = stream.line, .kind = .{.top_expression = try Expr.parse(root, stream, environment)}, }), } @@ -157,7 +157,7 @@ fn parse_branch(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Env const expression = try Expr.parse(root, stream, environment); if (stream.token != .symbol_colon) { - return root.report_error(stream, "expected `:` after `{token}`", .{.token = stream.token.text()}); + return root.report_error(stream.line, "expected `:` after `{token}`", .{.token = stream.token.text()}); } stream.skip_newlines(); @@ -171,7 +171,7 @@ fn parse_branch(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Env stream.skip_newlines(); return root.create_stmt(.{ - .line = stream.lines_stepped, + .line = stream.line, .kind = .{ .@"if" = .{ @@ -187,7 +187,7 @@ fn parse_branch(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Env stream.step(); if (stream.token != .symbol_colon) { - return root.report_error(stream, "expected `:` after `if` statement condition", .{}); + return root.report_error(stream.line, "expected `:` after `if` statement condition", .{}); } stream.skip_newlines(); @@ -205,7 +205,7 @@ fn parse_branch(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Env stream.skip_newlines(); return root.create_stmt(.{ - .line = stream.lines_stepped, + .line = stream.line, .kind = .{ .@"if" = .{ @@ -219,7 +219,7 @@ fn parse_branch(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Env .keyword_elif => { return root.create_stmt(.{ - .line = stream.lines_stepped, + .line = stream.line, .kind = .{ .@"if" = .{