418 lines
13 KiB
Zig
418 lines
13 KiB
Zig
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.opcodes.push_one(.push_nil),
|
|
.true_literal => try self.chunk.opcodes.push_one(.push_true),
|
|
.false_literal => try self.chunk.opcodes.push_one(.push_false),
|
|
|
|
.number_literal => |literal| {
|
|
for (literal) |codepoint| {
|
|
if (codepoint == '.') {
|
|
return self.chunk.opcodes.push_one(.{
|
|
.push_const = try self.declare_float(number_format.parse(literal, kym.Float) orelse unreachable),
|
|
});
|
|
}
|
|
}
|
|
|
|
try self.chunk.opcodes.push_one(.{
|
|
.push_const = try self.declare_fixed(number_format.parse(literal, kym.Fixed) orelse unreachable),
|
|
});
|
|
},
|
|
|
|
.string_literal => |literal| {
|
|
try self.chunk.opcodes.push_one(.{.push_const = try self.declare_string(literal)});
|
|
},
|
|
|
|
.symbol_literal => |literal| {
|
|
try self.chunk.opcodes.push_one(.{.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.opcodes.push_one(.push_top);
|
|
}
|
|
|
|
field_count += 1;
|
|
}
|
|
|
|
try self.chunk.opcodes.push_one(.{.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| {
|
|
const anonymous_chunk_name = try self.env.new_symbol(name orelse "<lambda>");
|
|
|
|
defer self.env.discard(anonymous_chunk_name);
|
|
|
|
var chunk = try Chunk.make(self.env, anonymous_chunk_name, lambda_construct.environment);
|
|
|
|
errdefer chunk.free(self.env);
|
|
|
|
if (lambda_construct.environment.capture_count == 0) {
|
|
try self.chunk.opcodes.push_one(.{.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.opcodes.push_one(switch (lambda_captures[index]) {
|
|
.declaration_index => |declaration_index| .{.push_local = declaration_index},
|
|
.capture_index => |capture_index| .{.push_binding = capture_index},
|
|
});
|
|
}
|
|
|
|
try self.chunk.opcodes.push_one(.{.push_const = try self.declare_chunk(chunk)});
|
|
try self.chunk.opcodes.push_one(.{.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.opcodes.push_one(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.opcodes.push_one(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.opcodes.push_one(.{.call = argument_count});
|
|
},
|
|
|
|
.group => |group| try self.compile_expression(environment, group, null),
|
|
.import_builtin => try self.chunk.opcodes.push_one(.{.push_builtin = .import}),
|
|
.print_builtin => try self.chunk.opcodes.push_one(.{.push_builtin = .print}),
|
|
.vec2_builtin => try self.chunk.opcodes.push_one(.{.push_builtin = .vec2}),
|
|
.vec3_builtin => try self.chunk.opcodes.push_one(.{.push_builtin = .vec3}),
|
|
|
|
.declaration_get => |declaration_get| {
|
|
if (get_local_index(environment, declaration_get.declaration)) |index| {
|
|
return self.chunk.opcodes.push_one(.{.push_local = index});
|
|
}
|
|
|
|
if (try self.get_binding_index(environment, declaration_get.declaration)) |index| {
|
|
try self.chunk.opcodes.push_one(.{.push_binding = index});
|
|
|
|
if (is_declaration_boxed(declaration_get.declaration)) {
|
|
try self.chunk.opcodes.push_one(.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.opcodes.push_one(.{.set_local = index});
|
|
}
|
|
|
|
if (try self.get_binding_index(environment, declaration_set.declaration)) |index| {
|
|
try self.chunk.opcodes.push_one(.{.push_binding = index});
|
|
try self.compile_expression(environment, declaration_set.assign, null);
|
|
|
|
if (is_declaration_boxed(declaration_set.declaration)) {
|
|
try self.chunk.opcodes.push_one(.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.opcodes.push_one(.{.push_const = try self.declare_symbol(field_get.identifier)});
|
|
try self.chunk.opcodes.push_one(.get_dynamic);
|
|
},
|
|
|
|
.field_set => |field_set| {
|
|
try self.compile_expression(environment, field_set.object, null);
|
|
try self.chunk.opcodes.push_one(.{.push_const = try self.declare_symbol(field_set.identifier)});
|
|
try self.compile_expression(environment, field_set.assign, null);
|
|
try self.chunk.opcodes.push_one(.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.opcodes.push_one(.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.opcodes.push_one(.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.opcodes.push_one(.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.opcodes.push_one(.push_nil);
|
|
}
|
|
|
|
// TODO: Omit ret calls at ends of chunk.
|
|
try self.chunk.opcodes.push_one(.ret);
|
|
},
|
|
|
|
.@"while" => |@"while"| {
|
|
try self.compile_expression(environment, @"while".loop_expression, null);
|
|
try self.chunk.opcodes.push_one(.{.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.opcodes.push_one(.{.jt = origin_index});
|
|
},
|
|
|
|
.@"if" => |@"if"| {
|
|
try self.compile_expression(environment, @"if".then_expression, null);
|
|
try self.chunk.opcodes.push_one(.{.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.opcodes.push_one(.push_boxed);
|
|
}
|
|
},
|
|
|
|
.top_expression => |top_expression| {
|
|
try self.compile_expression(environment, top_expression, null);
|
|
|
|
if (top_expression.kind == .invoke) {
|
|
try self.chunk.opcodes.push_one(.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;
|
|
}
|