From c30f10db96af773bd13a7d793a347a623085ddea Mon Sep 17 00:00:00 2001 From: kayomn Date: Tue, 7 Nov 2023 23:22:25 +0000 Subject: [PATCH] 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" = .{