Implement string interpolation
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details

This commit is contained in:
kayomn 2023-11-07 23:22:25 +00:00
parent 63792ea983
commit c30f10db96
7 changed files with 331 additions and 95 deletions

View File

@ -1,9 +1,6 @@
let wrench = "wrench" let tool = "wrench"
var test_param = `monkey {tool} {2 + 1 - 1}`
var test_param = "monkey {wrench}"
test_param = "monkey"
let printer = lambda (pfx): let printer = lambda (pfx):
@print(test_param) @print(test_param)

View File

@ -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 { pub fn discard(self: *RuntimeEnv, value: *RuntimeRef) void {
var object = value.object(); var object = value.object();
@ -573,8 +647,9 @@ pub const RuntimeEnv = struct {
if (callable.as_dynamic(Chunk.typeinfo)) |chunk_userdata| { if (callable.as_dynamic(Chunk.typeinfo)) |chunk_userdata| {
const chunk = @as(*Chunk, @ptrCast(@alignCast(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: { .name = get_name: {
const string = name.as_string(); const string = name.as_string();
@ -583,7 +658,7 @@ pub const RuntimeEnv = struct {
break: get_name string.?; break: get_name string.?;
}, },
.line = chunk.lines.values[chunk.cursor], .line_number = line.number,
}); });
defer self.allocator.deallocate(chunk_name); defer self.allocator.deallocate(chunk_name);
@ -1013,6 +1088,8 @@ pub const Typeinfo = struct {
}; };
pub fn assert(env: *RuntimeEnv, condition: bool) RuntimeError!void { pub fn assert(env: *RuntimeEnv, condition: bool) RuntimeError!void {
coral.debug.assert(condition);
if (!condition) { if (!condition) {
return env.raise(error.IllegalState, "assertion", .{}); return env.raise(error.IllegalState, "assertion", .{});
} }

View File

@ -1,7 +1,3 @@
const Expr = @import("./Expr.zig");
const Stmt = @import("./Stmt.zig");
const app = @import("../app.zig"); const app = @import("../app.zig");
const coral = @import("coral"); const coral = @import("coral");
@ -10,6 +6,8 @@ const file = @import("../file.zig");
const kym = @import("../kym.zig"); const kym = @import("../kym.zig");
const tokens = @import("./tokens.zig");
const tree = @import("./tree.zig"); const tree = @import("./tree.zig");
name: *kym.RuntimeRef, name: *kym.RuntimeRef,
@ -31,7 +29,7 @@ const Compiler = struct {
chunk: *Self, chunk: *Self,
env: *kym.RuntimeEnv, 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. // TODO: Exceeding 255 arguments will make the VM crash.
var maybe_argument = initial_argument; var maybe_argument = initial_argument;
var argument_count = @as(u8, 0); var argument_count = @as(u8, 0);
@ -46,7 +44,7 @@ const Compiler = struct {
return argument_count; 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{ const number_format = coral.utf8.DecimalFormat{
.delimiter = "_", .delimiter = "_",
.positive_prefix = .none, .positive_prefix = .none,
@ -75,6 +73,24 @@ const Compiler = struct {
try self.chunk.write(expression.line, .{.push_const = try self.declare_string(literal)}); 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| { .symbol_literal => |literal| {
try self.chunk.write(expression.line, .{.push_const = try self.declare_symbol(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; var current_statement = initial_statement;
while (true) { while (true) {
@ -445,7 +461,7 @@ const Compiler = struct {
const ConstList = coral.list.Stack(*kym.RuntimeRef); 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) { pub const Opcode = union (enum) {
ret, ret,
@ -459,6 +475,7 @@ pub const Opcode = union (enum) {
push_table: u32, push_table: u32,
push_builtin: Builtin, push_builtin: Builtin,
push_binding: u8, push_binding: u8,
push_concat: u8,
push_boxed, push_boxed,
set_local: u8, set_local: u8,
get_dynamic, get_dynamic,
@ -540,6 +557,10 @@ pub fn dump(chunk: Self, env: *kym.RuntimeEnv) kym.RuntimeError!*kym.RuntimeRef
.binding = push_binding, .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", .{ .push_builtin => |push_builtin| coral.utf8.print_formatted(writer, "push builtin ({builtin})\n", .{
.builtin = switch (push_builtin) { .builtin = switch (push_builtin) {
.import => "import", .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); 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| { .bind => |bind| {
const callable = try env.expect(try env.pop_local()); 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); 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.opcodes.push_one(opcode);
try self.lines.push_one(line); try self.lines.push_one(line);
} }

View File

@ -1,5 +1,9 @@
const coral = @import("coral"); const coral = @import("coral");
pub const Line = struct {
number: u32,
};
pub const Token = union(enum) { pub const Token = union(enum) {
end, end,
unknown: coral.io.Byte, unknown: coral.io.Byte,
@ -105,7 +109,7 @@ pub const Token = union(enum) {
pub const Stream = struct { pub const Stream = struct {
source: []const coral.io.Byte, source: []const coral.io.Byte,
lines_stepped: u32 = 1, line: Line = .{.number = 1},
token: Token = .newline, token: Token = .newline,
pub fn skip_newlines(self: *Stream) void { pub fn skip_newlines(self: *Stream) void {
@ -136,7 +140,7 @@ pub const Stream = struct {
'\n' => { '\n' => {
cursor += 1; cursor += 1;
self.token = .newline; self.token = .newline;
self.lines_stepped += 1; self.line.number += 1;
return; return;
}, },

View File

@ -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"); const coral = @import("coral");
@ -163,22 +163,22 @@ pub const Root = struct {
const MessageList = coral.list.Stack([]coral.io.Byte); 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(); 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), .message = try coral.utf8.alloc_formatted(allocator, format, args),
.line = stream.lines_stepped, .line_number = line.number,
})); }));
return error.BadSyntax; 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") { return switch (@"error") {
error.OutOfMemory => error.OutOfMemory, 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, .identifier = identifier,
}), }),
}; };

View File

@ -2,12 +2,12 @@ const Stmt = @import("./Stmt.zig");
const coral = @import("coral"); 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, next: ?*const Self = null,
line: u32, line: tokens.Line,
kind: union (enum) { kind: union (enum) {
nil_literal, nil_literal,
@ -15,6 +15,7 @@ kind: union (enum) {
false_literal, false_literal,
number_literal: []const coral.io.Byte, number_literal: []const coral.io.Byte,
string_literal: []const coral.io.Byte, string_literal: []const coral.io.Byte,
string_template,
symbol_literal: []const coral.io.Byte, symbol_literal: []const coral.io.Byte,
table_construct: TableConstruct, table_construct: TableConstruct,
key_value: KeyValue, key_value: KeyValue,
@ -74,14 +75,14 @@ pub const BinaryOp = struct {
stream.step(); stream.step();
if (stream.token == .end) { 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. // TODO: Remove once Zig has fixed struct self-reassignment.
const unnecessary_temp = expression; const unnecessary_temp = expression;
expression = try root.create_expr(.{ expression = try root.create_expr(.{
.line = stream.lines_stepped, .line = stream.line,
.kind = .{ .kind = .{
.binary_op = .{ .binary_op = .{
@ -155,6 +156,68 @@ pub const TableConstruct = struct {
entry: ?*const Self, 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 { pub const UnaryOp = struct {
operand: *Self, operand: *Self,
operation: Operation, operation: Operation,
@ -172,16 +235,16 @@ pub fn parse(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Enviro
stream.skip_newlines(); stream.skip_newlines();
if (stream.token == .end) { 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(.{ return root.create_expr(.{
.line = stream.lines_stepped, .line = stream.line,
.kind = switch (expression.kind) { .kind = switch (expression.kind) {
.declaration_get => |declaration_get| convert: { .declaration_get => |declaration_get| convert: {
if (declaration_get.declaration.is.readonly) { 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 .{ 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; const unnecessary_temp = expression;
expression = try root.create_expr(.{ expression = try root.create_expr(.{
.line = stream.lines_stepped, .line = stream.line,
.kind = .{ .kind = .{
.field_get = .{ .field_get = .{
.identifier = switch (stream.token) { .identifier = switch (stream.token) {
.identifier => |field_identifier| field_identifier, .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, .object = unnecessary_temp,
@ -268,7 +331,7 @@ fn parse_factor(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Env
const unnecessary_temp = expression; const unnecessary_temp = expression;
expression = try root.create_expr(.{ expression = try root.create_expr(.{
.line = stream.lines_stepped, .line = stream.line,
.kind = .{ .kind = .{
.subscript_get = .{ .subscript_get = .{
@ -279,14 +342,14 @@ fn parse_factor(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Env
}); });
if (stream.token != .symbol_bracket_right) { 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(); stream.skip_newlines();
}, },
.symbol_paren_left => { .symbol_paren_left => {
const lines_stepped = stream.lines_stepped; const lines_stepped = stream.line;
stream.skip_newlines(); stream.skip_newlines();
@ -301,7 +364,7 @@ fn parse_factor(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Env
switch (stream.token) { switch (stream.token) {
.symbol_comma => stream.skip_newlines(), .symbol_comma => stream.skip_newlines(),
.symbol_paren_right => break, .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); 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); const expression = try parse(root, stream, environment);
if (stream.token != .symbol_paren_right) { 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(); stream.skip_newlines();
return root.create_expr(.{ return root.create_expr(.{
.line = stream.lines_stepped, .line = stream.line,
.kind = .{.group = expression}, .kind = .{.group = expression},
}); });
}, },
@ -358,7 +421,7 @@ fn parse_operand(root: *tree.Root, stream: *tokens.Stream, environment: *tree.En
stream.skip_newlines(); stream.skip_newlines();
return root.create_expr(.{ return root.create_expr(.{
.line = stream.lines_stepped, .line = stream.line,
.kind = .nil_literal, .kind = .nil_literal,
}); });
}, },
@ -367,7 +430,7 @@ fn parse_operand(root: *tree.Root, stream: *tokens.Stream, environment: *tree.En
stream.skip_newlines(); stream.skip_newlines();
return root.create_expr(.{ return root.create_expr(.{
.line = stream.lines_stepped, .line = stream.line,
.kind = .true_literal, .kind = .true_literal,
}); });
}, },
@ -376,7 +439,7 @@ fn parse_operand(root: *tree.Root, stream: *tokens.Stream, environment: *tree.En
stream.skip_newlines(); stream.skip_newlines();
return root.create_expr(.{ return root.create_expr(.{
.line = stream.lines_stepped, .line = stream.line,
.kind = .false_literal, .kind = .false_literal,
}); });
}, },
@ -385,7 +448,7 @@ fn parse_operand(root: *tree.Root, stream: *tokens.Stream, environment: *tree.En
stream.skip_newlines(); stream.skip_newlines();
return root.create_expr(.{ return root.create_expr(.{
.line = stream.lines_stepped, .line = stream.line,
.kind = .{.number_literal = value}, .kind = .{.number_literal = value},
}); });
}, },
@ -394,50 +457,58 @@ fn parse_operand(root: *tree.Root, stream: *tokens.Stream, environment: *tree.En
stream.skip_newlines(); stream.skip_newlines();
return root.create_expr(.{ return root.create_expr(.{
.line = stream.lines_stepped, .line = stream.line,
.kind = .{.string_literal = value}, .kind = .{.string_literal = value},
}); });
}, },
.template_string => |value| {
const line = stream.line;
stream.skip_newlines();
return parse_template(root, value, line, environment);
},
.symbol_at => { .symbol_at => {
stream.step(); stream.step();
const identifier = switch (stream.token) { const identifier = switch (stream.token) {
.identifier => |identifier| identifier, .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(); stream.skip_newlines();
if (coral.io.are_equal(identifier, "import")) { if (coral.io.are_equal(identifier, "import")) {
return root.create_expr(.{ return root.create_expr(.{
.line = stream.lines_stepped, .line = stream.line,
.kind = .import_builtin, .kind = .import_builtin,
}); });
} }
if (coral.io.are_equal(identifier, "print")) { if (coral.io.are_equal(identifier, "print")) {
return root.create_expr(.{ return root.create_expr(.{
.line = stream.lines_stepped, .line = stream.line,
.kind = .print_builtin, .kind = .print_builtin,
}); });
} }
if (coral.io.are_equal(identifier, "vec2")) { if (coral.io.are_equal(identifier, "vec2")) {
return root.create_expr(.{ return root.create_expr(.{
.line = stream.lines_stepped, .line = stream.line,
.kind = .vec2_builtin, .kind = .vec2_builtin,
}); });
} }
if (coral.io.are_equal(identifier, "vec3")) { if (coral.io.are_equal(identifier, "vec3")) {
return root.create_expr(.{ return root.create_expr(.{
.line = stream.lines_stepped, .line = stream.line,
.kind = .vec3_builtin, .kind = .vec3_builtin,
}); });
} }
return root.report_error(stream, "unexpected identifier after `@`", .{}); return root.report_error(stream.line, "unexpected identifier after `@`", .{});
}, },
.symbol_period => { .symbol_period => {
@ -445,13 +516,13 @@ fn parse_operand(root: *tree.Root, stream: *tokens.Stream, environment: *tree.En
const identifier = switch (stream.token) { const identifier = switch (stream.token) {
.identifier => |identifier| identifier, .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(); stream.skip_newlines();
return root.create_expr(.{ return root.create_expr(.{
.line = stream.lines_stepped, .line = stream.line,
.kind = .{.symbol_literal = identifier}, .kind = .{.symbol_literal = identifier},
}); });
}, },
@ -460,12 +531,12 @@ fn parse_operand(root: *tree.Root, stream: *tokens.Stream, environment: *tree.En
stream.skip_newlines(); stream.skip_newlines();
return root.create_expr(.{ return root.create_expr(.{
.line = stream.lines_stepped, .line = stream.line,
.kind = .{ .kind = .{
.declaration_get = .{ .declaration_get = .{
.declaration = (try environment.resolve_declaration(identifier)) orelse { .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, .identifier = identifier,
}); });
} }
@ -478,7 +549,7 @@ fn parse_operand(root: *tree.Root, stream: *tokens.Stream, environment: *tree.En
stream.skip_newlines(); stream.skip_newlines();
if (stream.token != .symbol_paren_left) { 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(); 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) { while (stream.token != .symbol_paren_right) {
const identifier = switch (stream.token) { const identifier = switch (stream.token) {
.identifier => |identifier| identifier, .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| { _ = 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(); stream.skip_newlines();
@ -500,14 +571,14 @@ fn parse_operand(root: *tree.Root, stream: *tokens.Stream, environment: *tree.En
switch (stream.token) { switch (stream.token) {
.symbol_comma => stream.skip_newlines(), .symbol_comma => stream.skip_newlines(),
.symbol_paren_right => break, .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(); stream.skip_newlines();
if (stream.token != .symbol_colon) { 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(); stream.skip_newlines();
@ -529,7 +600,7 @@ fn parse_operand(root: *tree.Root, stream: *tokens.Stream, environment: *tree.En
stream.skip_newlines(); stream.skip_newlines();
return root.create_expr(.{ return root.create_expr(.{
.line = stream.lines_stepped, .line = stream.line,
.kind = .{.lambda_construct = .{.environment = lambda_environment}}, .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) { if (stream.token == .symbol_brace_right) {
return root.create_expr(.{ return root.create_expr(.{
.line = stream.lines_stepped, .line = stream.line,
.kind = .{.table_construct = .{.entry = null}}, .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) { 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(); stream.skip_newlines();
return root.create_expr(.{ return root.create_expr(.{
.line = stream.lines_stepped, .line = stream.line,
.kind = .{.table_construct = .{.entry = first_entry}}, .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(); stream.skip_newlines();
return root.create_expr(.{ return root.create_expr(.{
.line = stream.lines_stepped, .line = stream.line,
.kind = .{ .kind = .{
.unary_op = .{ .unary_op = .{
@ -591,7 +662,7 @@ fn parse_operand(root: *tree.Root, stream: *tokens.Stream, environment: *tree.En
stream.skip_newlines(); stream.skip_newlines();
return root.create_expr(.{ return root.create_expr(.{
.line = stream.lines_stepped, .line = stream.line,
.kind = .{ .kind = .{
.unary_op = .{ .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) { const field = switch (stream.token) {
.identifier => |identifier| identifier, .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(); stream.skip_newlines();
if (stream.token != .symbol_equals) { 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(); stream.skip_newlines();
return root.create_expr(.{ return root.create_expr(.{
.line = stream.lines_stepped, .line = stream.line,
.kind = .{ .kind = .{
.key_value = .{ .key_value = .{
.value = try parse(root, stream, environment), .value = try parse(root, stream, environment),
.key = try root.create_expr(.{ .key = try root.create_expr(.{
.line = stream.lines_stepped, .line = stream.line,
.kind = .{.symbol_literal = field}, .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); const key = try parse(root, stream, environment);
if (stream.token != .symbol_bracket_right) { 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(); stream.skip_newlines();
if (stream.token != .symbol_equals) { 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(); stream.skip_newlines();
return root.create_expr(.{ return root.create_expr(.{
.line = stream.lines_stepped, .line = stream.line,
.kind = .{ .kind = .{
.key_value = .{ .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, &.{ const parse_term = BinaryOp.parser(parse_factor, &.{
.multiplication, .multiplication,
.divsion, .divsion,

View File

@ -2,12 +2,12 @@ const Expr = @import("./Expr.zig");
const coral = @import("coral"); 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, next: ?*const Self = null,
line: u32, line: tokens.Line,
kind: union (enum) { kind: union (enum) {
top_expression: *const Expr, 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) { if (stream.token != .end and stream.token != .newline) {
return root.create_stmt(.{ return root.create_stmt(.{
.line = stream.lines_stepped, .line = stream.line,
.kind = .{.@"return" = .{.returned_expression = try Expr.parse(root, stream, environment)}}, .kind = .{.@"return" = .{.returned_expression = try Expr.parse(root, stream, environment)}},
}); });
} }
if (stream.token != .end and stream.token != .newline) { 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(.{ return root.create_stmt(.{
.line = stream.lines_stepped, .line = stream.line,
.kind = .{.@"return" = .{.returned_expression = null}}, .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); const condition_expression = try Expr.parse(root, stream, environment);
if (stream.token != .symbol_colon) { 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(); stream.skip_newlines();
@ -88,7 +88,7 @@ pub fn parse(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Enviro
} }
return root.create_stmt(.{ return root.create_stmt(.{
.line = stream.lines_stepped, .line = stream.line,
.kind = .{ .kind = .{
.@"while" = .{ .@"while" = .{
@ -106,13 +106,13 @@ pub fn parse(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Enviro
const identifier = switch (stream.token) { const identifier = switch (stream.token) {
.identifier => |identifier| identifier, .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(); stream.skip_newlines();
if (stream.token != .symbol_equals) { 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, .identifier = identifier,
}); });
} }
@ -120,7 +120,7 @@ pub fn parse(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Enviro
stream.skip_newlines(); stream.skip_newlines();
return root.create_stmt(.{ return root.create_stmt(.{
.line = stream.lines_stepped, .line = stream.line,
.kind = .{ .kind = .{
.declare = .{ .declare = .{
@ -129,12 +129,12 @@ pub fn parse(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Enviro
.declaration = declare: { .declaration = declare: {
if (is_constant) { if (is_constant) {
break: declare environment.declare_constant(identifier) catch |declaration_error| { 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| { 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), .keyword_if => return parse_branch(root, stream, environment),
else => return root.create_stmt(.{ else => return root.create_stmt(.{
.line = stream.lines_stepped, .line = stream.line,
.kind = .{.top_expression = try Expr.parse(root, stream, environment)}, .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); const expression = try Expr.parse(root, stream, environment);
if (stream.token != .symbol_colon) { 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(); stream.skip_newlines();
@ -171,7 +171,7 @@ fn parse_branch(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Env
stream.skip_newlines(); stream.skip_newlines();
return root.create_stmt(.{ return root.create_stmt(.{
.line = stream.lines_stepped, .line = stream.line,
.kind = .{ .kind = .{
.@"if" = .{ .@"if" = .{
@ -187,7 +187,7 @@ fn parse_branch(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Env
stream.step(); stream.step();
if (stream.token != .symbol_colon) { 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(); stream.skip_newlines();
@ -205,7 +205,7 @@ fn parse_branch(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Env
stream.skip_newlines(); stream.skip_newlines();
return root.create_stmt(.{ return root.create_stmt(.{
.line = stream.lines_stepped, .line = stream.line,
.kind = .{ .kind = .{
.@"if" = .{ .@"if" = .{
@ -219,7 +219,7 @@ fn parse_branch(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Env
.keyword_elif => { .keyword_elif => {
return root.create_stmt(.{ return root.create_stmt(.{
.line = stream.lines_stepped, .line = stream.line,
.kind = .{ .kind = .{
.@"if" = .{ .@"if" = .{