Merge pull request 'String Literal Interpolation' (#46) from kym-string-interpolation into main
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
Reviewed-on: #46
This commit is contained in:
commit
decc2799b0
|
@ -1,5 +1,6 @@
|
||||||
|
|
||||||
let test_param = "monkey wrench"
|
let tool = "wrench"
|
||||||
|
var test_param = `monkey {tool} {2 + 1 - 1}`
|
||||||
|
|
||||||
let printer = lambda (pfx):
|
let printer = lambda (pfx):
|
||||||
@print(test_param)
|
@print(test_param)
|
||||||
|
|
|
@ -17,7 +17,7 @@ pub fn Stack(comptime Value: type) type {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn drop(self: *Self, count: usize) bool {
|
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];
|
self.values = self.values[0 .. updated_count];
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
@ -282,7 +356,7 @@ pub const RuntimeEnv = struct {
|
||||||
pub fn get_boxed(self: *RuntimeEnv, boxable: *RuntimeRef) RuntimeError!?*RuntimeRef {
|
pub fn get_boxed(self: *RuntimeEnv, boxable: *RuntimeRef) RuntimeError!?*RuntimeRef {
|
||||||
return switch (boxable.object().payload) {
|
return switch (boxable.object().payload) {
|
||||||
.boxed => |boxed| if (boxed) |boxed_value| boxed_value.acquire() else null,
|
.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()}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -533,8 +607,8 @@ pub const RuntimeEnv = struct {
|
||||||
|
|
||||||
if (arg_count < arity) {
|
if (arg_count < arity) {
|
||||||
return self.raise(error.BadOperation, "expected `{expected}` {noun}, {provided} provided", .{
|
return self.raise(error.BadOperation, "expected `{expected}` {noun}, {provided} provided", .{
|
||||||
.expected = arity,
|
.expected = arg_count,
|
||||||
.provided = arg_count,
|
.provided = arity,
|
||||||
.noun = if (arity == 1) "argument" else "arguments",
|
.noun = if (arity == 1) "argument" else "arguments",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -624,7 +699,7 @@ pub const RuntimeEnv = struct {
|
||||||
boxed.* = if (value) |ref| ref.acquire() else null;
|
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(),
|
.typename = boxable.typename(),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
@ -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", .{});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
const app = @import("../app.zig");
|
const app = @import("../app.zig");
|
||||||
|
|
||||||
const Compiler = @import("./Compiler.zig");
|
|
||||||
|
|
||||||
const coral = @import("coral");
|
const coral = @import("coral");
|
||||||
|
|
||||||
const file = @import("../file.zig");
|
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,
|
||||||
|
@ -25,9 +25,443 @@ const Builtin = enum {
|
||||||
vec3,
|
vec3,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const Compiler = struct {
|
||||||
|
chunk: *Self,
|
||||||
|
env: *kym.RuntimeEnv,
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
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 tree.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)});
|
||||||
|
},
|
||||||
|
|
||||||
|
.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)});
|
||||||
|
},
|
||||||
|
|
||||||
|
.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>", 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| {
|
||||||
|
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| {
|
||||||
|
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);
|
||||||
|
|
||||||
|
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.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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 tree.Stmt) kym.RuntimeError!*const tree.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 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,
|
||||||
|
@ -41,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,
|
||||||
|
@ -122,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",
|
||||||
|
@ -252,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());
|
||||||
|
|
||||||
|
@ -326,7 +792,8 @@ pub fn execute(self: *Self, env: *kym.RuntimeEnv, frame: *const kym.Frame) kym.R
|
||||||
|
|
||||||
const value = try env.expect(try env.pop_local());
|
const value = try env.expect(try env.pop_local());
|
||||||
|
|
||||||
defer env.discard(box);
|
errdefer env.discard(value);
|
||||||
|
|
||||||
try env.set_boxed(box, value);
|
try env.set_boxed(box, value);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -652,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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>", 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;
|
|
||||||
}
|
|
|
@ -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,
|
||||||
|
@ -31,6 +35,7 @@ pub const Token = union(enum) {
|
||||||
|
|
||||||
number: []const coral.io.Byte,
|
number: []const coral.io.Byte,
|
||||||
string: []const coral.io.Byte,
|
string: []const coral.io.Byte,
|
||||||
|
template_string: []const coral.io.Byte,
|
||||||
|
|
||||||
keyword_nil,
|
keyword_nil,
|
||||||
keyword_false,
|
keyword_false,
|
||||||
|
@ -81,6 +86,7 @@ pub const Token = union(enum) {
|
||||||
|
|
||||||
.number => |literal| literal,
|
.number => |literal| literal,
|
||||||
.string => |literal| literal,
|
.string => |literal| literal,
|
||||||
|
.template_string => |literal| literal,
|
||||||
|
|
||||||
.keyword_const => "const",
|
.keyword_const => "const",
|
||||||
.keyword_nil => "nil",
|
.keyword_nil => "nil",
|
||||||
|
@ -103,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 {
|
||||||
|
@ -134,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;
|
||||||
},
|
},
|
||||||
|
@ -305,6 +311,22 @@ pub const Stream = struct {
|
||||||
return;
|
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;
|
cursor += 1;
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
|
@ -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,
|
|
@ -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" = .{
|
Loading…
Reference in New Issue