513 lines
13 KiB
Zig
513 lines
13 KiB
Zig
const Ast = @import("./Ast.zig");
|
|
|
|
const coral = @import("coral");
|
|
|
|
const kym = @import("../kym.zig");
|
|
|
|
env: *kym.RuntimeEnv,
|
|
constant_refs: RefList,
|
|
opcodes: OpcodeList,
|
|
|
|
const AstCompiler = struct {
|
|
chunk: *Self,
|
|
local_identifiers_buffer: [255][]const coral.io.Byte = [_][]const coral.io.Byte{""} ** 255,
|
|
local_identifiers_count: u8 = 0,
|
|
|
|
fn compile_expression(self: *AstCompiler, expression: Ast.Expression) kym.RuntimeError!void {
|
|
const number_format = coral.utf8.DecimalFormat{
|
|
.delimiter = "_",
|
|
.positive_prefix = .none,
|
|
};
|
|
|
|
switch (expression) {
|
|
.nil_literal => try self.chunk.append_opcode(.push_nil),
|
|
.true_literal => try self.chunk.append_opcode(.push_true),
|
|
.false_literal => try self.chunk.append_opcode(.push_false),
|
|
|
|
.number_literal => |literal| {
|
|
const parsed_number = number_format.parse(literal, kym.Float);
|
|
|
|
coral.debug.assert(parsed_number != null);
|
|
|
|
try self.chunk.append_opcode(.{
|
|
.push_const = try self.chunk.declare_constant_number(parsed_number.?),
|
|
});
|
|
},
|
|
|
|
.string_literal => |literal| {
|
|
try self.chunk.append_opcode(.{.push_const = try self.chunk.declare_constant_string(literal)});
|
|
},
|
|
|
|
.table_literal => |fields| {
|
|
if (fields.values.len > coral.math.max_int(@typeInfo(u32).Int)) {
|
|
return error.OutOfMemory;
|
|
}
|
|
|
|
for (fields.values) |field| {
|
|
try self.compile_expression(field.expression);
|
|
|
|
try self.chunk.append_opcode(.{
|
|
.push_const = try self.chunk.declare_constant_string(field.identifier),
|
|
});
|
|
}
|
|
|
|
try self.chunk.append_opcode(.{.push_table = @intCast(fields.values.len)});
|
|
},
|
|
|
|
.binary_operation => |operation| {
|
|
try self.compile_expression(operation.lhs_expression.*);
|
|
try self.compile_expression(operation.rhs_expression.*);
|
|
|
|
try self.chunk.append_opcode(switch (operation.operator) {
|
|
.addition => .add,
|
|
.subtraction => .sub,
|
|
.multiplication => .mul,
|
|
.divsion => .div,
|
|
.greater_equals_comparison => .eql,
|
|
.greater_than_comparison => .cgt,
|
|
.equals_comparison => .cge,
|
|
.less_than_comparison => .clt,
|
|
.less_equals_comparison => .cle,
|
|
});
|
|
},
|
|
|
|
.unary_operation => |operation| {
|
|
try self.compile_expression(operation.expression.*);
|
|
|
|
try self.chunk.append_opcode(switch (operation.operator) {
|
|
.boolean_negation => .not,
|
|
.numeric_negation => .neg,
|
|
});
|
|
},
|
|
|
|
.grouped_expression => |grouped_expression| {
|
|
try self.compile_expression(grouped_expression.*);
|
|
},
|
|
|
|
.get_local => |local| {
|
|
try self.chunk.append_opcode(.{
|
|
.push_local = self.resolve_local(local) orelse return self.chunk.env.raise(error.OutOfMemory, "undefined local"),
|
|
});
|
|
},
|
|
|
|
.call_system => |call| {
|
|
if (call.argument_expressions.values.len > coral.math.max_int(@typeInfo(u8).Int)) {
|
|
return self.chunk.env.raise(error.OutOfMemory, "functions may receive a maximum of 255 locals");
|
|
}
|
|
|
|
for (call.argument_expressions.values) |argument_expression| {
|
|
try self.compile_expression(argument_expression);
|
|
}
|
|
|
|
try self.chunk.append_opcode(.{.push_const = try self.chunk.declare_constant_string(call.identifier)});
|
|
try self.chunk.append_opcode(.{.syscall = @intCast(call.argument_expressions.values.len)});
|
|
try self.chunk.append_opcode(.pop);
|
|
},
|
|
}
|
|
}
|
|
|
|
fn compile_statement(self: *AstCompiler, statement: Ast.Statement) kym.RuntimeError!void {
|
|
switch (statement) {
|
|
.return_expression => |return_expression| try self.compile_expression(return_expression),
|
|
.return_nothing => try self.chunk.append_opcode(.push_nil),
|
|
|
|
.set_local => |local| {
|
|
try self.compile_expression(local.expression);
|
|
|
|
if (self.resolve_local(local.identifier)) |index| {
|
|
try self.chunk.append_opcode(.{.set_local = index});
|
|
} else {
|
|
try self.declare_local(local.identifier);
|
|
}
|
|
},
|
|
|
|
.call_system => |call| {
|
|
if (call.argument_expressions.values.len > coral.math.max_int(@typeInfo(u8).Int)) {
|
|
return self.chunk.env.raise(error.OutOfMemory, "functions may receive a maximum of 255 locals");
|
|
}
|
|
|
|
for (call.argument_expressions.values) |argument_expression| {
|
|
try self.compile_expression(argument_expression);
|
|
}
|
|
|
|
try self.chunk.append_opcode(.{.push_const = try self.chunk.declare_constant_string(call.identifier)});
|
|
try self.chunk.append_opcode(.{.syscall = @intCast(call.argument_expressions.values.len)});
|
|
try self.chunk.append_opcode(.pop);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn declare_local(self: *AstCompiler, identifier: []const u8) kym.RuntimeError!void {
|
|
if (self.local_identifiers_count == self.local_identifiers_buffer.len) {
|
|
return self.chunk.env.raise(error.OutOfMemory, "functions may contain a maximum of 255 locals");
|
|
}
|
|
|
|
self.local_identifiers_buffer[self.local_identifiers_count] = identifier;
|
|
self.local_identifiers_count += 1;
|
|
}
|
|
|
|
fn resolve_local(self: *AstCompiler, local_identifier: []const coral.io.Byte) ?u8 {
|
|
if (self.local_identifiers_count == 0) {
|
|
return null;
|
|
}
|
|
|
|
var index = @as(u8, self.local_identifiers_count - 1);
|
|
|
|
while (true) : (index -= 1) {
|
|
if (coral.io.equals(local_identifier, self.local_identifiers_buffer[index])) {
|
|
return index;
|
|
}
|
|
|
|
if (index == 0) {
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
pub const Constant = u16;
|
|
|
|
const RefList = coral.list.Stack(?*kym.RuntimeRef);
|
|
|
|
const LocalsList = coral.list.Stack([]const u8);
|
|
|
|
pub const Opcode = union (enum) {
|
|
pop,
|
|
push_nil,
|
|
push_true,
|
|
push_false,
|
|
push_const: Constant,
|
|
push_local: u8,
|
|
push_table: u32,
|
|
set_local: u8,
|
|
call: u8,
|
|
syscall: u8,
|
|
|
|
not,
|
|
neg,
|
|
|
|
add,
|
|
sub,
|
|
mul,
|
|
div,
|
|
|
|
eql,
|
|
cgt,
|
|
clt,
|
|
cge,
|
|
cle,
|
|
};
|
|
|
|
const OpcodeList = coral.list.Stack(Opcode);
|
|
|
|
const Self = @This();
|
|
|
|
pub fn append_opcode(self: *Self, opcode: Opcode) kym.RuntimeError!void {
|
|
return self.opcodes.push_one(opcode);
|
|
}
|
|
|
|
pub fn as_caller(self: *Self) kym.Caller {
|
|
return kym.Caller.bind(Self, self, execute);
|
|
}
|
|
|
|
pub fn compile_ast(self: *Self, ast: Ast) kym.RuntimeError!void {
|
|
try self.constant_refs.grow(coral.math.max_int(@typeInfo(u16).Int));
|
|
|
|
var compiler = AstCompiler{.chunk = self};
|
|
|
|
for (ast.list_statements()) |statement| {
|
|
try compiler.compile_statement(statement);
|
|
}
|
|
|
|
try self.constant_refs.pack();
|
|
try self.opcodes.pack();
|
|
}
|
|
|
|
pub fn declare_constant_number(self: *Self, constant: kym.Float) kym.RuntimeError!Constant {
|
|
const tail = self.constant_refs.values.len;
|
|
|
|
if (tail == coral.math.max_int(@typeInfo(u16).Int)) {
|
|
return self.env.raise(error.BadSyntax, "functions may contain a maximum of 65,535 constants");
|
|
}
|
|
|
|
const constant_ref = try self.env.new_number(constant);
|
|
|
|
errdefer self.env.discard(constant_ref);
|
|
|
|
try self.constant_refs.push_one(constant_ref);
|
|
|
|
return @intCast(tail);
|
|
}
|
|
|
|
pub fn declare_constant_string(self: *Self, constant: []const coral.io.Byte) kym.RuntimeError!Constant {
|
|
const tail = self.constant_refs.values.len;
|
|
|
|
if (tail == coral.math.max_int(@typeInfo(u16).Int)) {
|
|
return self.env.raise(error.BadSyntax, "functions may contain a maximum of 65,535 constants");
|
|
}
|
|
|
|
const constant_ref = try self.env.new_string(constant);
|
|
|
|
errdefer self.env.discard(constant_ref);
|
|
|
|
try self.constant_refs.push_one(constant_ref);
|
|
|
|
return @intCast(tail);
|
|
}
|
|
|
|
fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef {
|
|
for (self.opcodes.values) |opcode| {
|
|
switch (opcode) {
|
|
.pop => env.discard(try env.pop_local()),
|
|
.push_nil => try env.push_ref(null),
|
|
.push_true => try env.push_boolean(true),
|
|
.push_false => try env.push_boolean(false),
|
|
.push_const => |constant| try env.push_ref(self.constant_refs.values[constant]),
|
|
|
|
.push_table => |field_count| {
|
|
const table_ref = try kym.new_table(env);
|
|
|
|
defer env.discard(table_ref);
|
|
|
|
{
|
|
const dynamic = try kym.unbox_dynamic(env, table_ref);
|
|
var popped = @as(usize, 0);
|
|
|
|
while (popped < field_count) : (popped += 1) {
|
|
const index_ref = try env.pop_local();
|
|
|
|
defer env.discard(index_ref);
|
|
|
|
const value_ref = try env.pop_local();
|
|
|
|
defer env.discard(value_ref);
|
|
|
|
try dynamic.typeinfo.set(.{
|
|
.userdata = dynamic.userdata,
|
|
.env = env,
|
|
.index_ref = index_ref,
|
|
}, value_ref);
|
|
}
|
|
}
|
|
|
|
try env.push_ref(table_ref);
|
|
},
|
|
|
|
.push_local => |local| {
|
|
const ref = try env.get_local(local);
|
|
|
|
defer env.discard(ref);
|
|
|
|
try env.push_ref(ref);
|
|
},
|
|
|
|
.set_local => |local| {
|
|
const ref = try env.pop_local();
|
|
|
|
defer env.discard(ref);
|
|
|
|
try env.set_local(local, ref);
|
|
},
|
|
|
|
.call => |arg_count| {
|
|
const result_ref = call: {
|
|
const callable_ref = try env.pop_local();
|
|
|
|
defer env.discard(callable_ref);
|
|
|
|
break: call try env.call("", arg_count, (try kym.unbox_dynamic(env, callable_ref)).as_caller());
|
|
};
|
|
|
|
defer env.discard(result_ref);
|
|
|
|
try env.push_ref(result_ref);
|
|
},
|
|
|
|
.syscall => |arg_count| {
|
|
const result_ref = call: {
|
|
const identifier_ref = try env.pop_local();
|
|
|
|
defer env.discard(identifier_ref);
|
|
|
|
const identifier = try kym.unbox_string(env, identifier_ref);
|
|
|
|
break: call try env.call(identifier, arg_count, try env.syscaller(identifier));
|
|
};
|
|
|
|
defer env.discard(result_ref);
|
|
|
|
try env.push_ref(result_ref);
|
|
},
|
|
|
|
.neg => try env.push_number(switch (env.unbox(try env.pop_local())) {
|
|
.number => |number| -number,
|
|
else => return env.raise(error.TypeMismatch, "object is not scalar negatable"),
|
|
}),
|
|
|
|
.not => try env.push_boolean(switch (env.unbox(try env.pop_local())) {
|
|
.boolean => |boolean| !boolean,
|
|
else => return env.raise(error.TypeMismatch, "object is not boolean negatable"),
|
|
}),
|
|
|
|
.add => {
|
|
const rhs_ref = try env.pop_local();
|
|
|
|
defer env.discard(rhs_ref);
|
|
|
|
const lhs_ref = try env.pop_local();
|
|
|
|
defer env.discard(lhs_ref);
|
|
|
|
try env.push_ref(try switch (env.unbox(lhs_ref)) {
|
|
.number => |lhs_number| switch (env.unbox(rhs_ref)) {
|
|
.number => |rhs_number| env.new_number(lhs_number + rhs_number),
|
|
else => return env.raise(error.TypeMismatch, "right-hand object is not addable"),
|
|
},
|
|
|
|
else => return env.raise(error.TypeMismatch, "left-hand object is not addable"),
|
|
});
|
|
},
|
|
|
|
.sub => {
|
|
const rhs_ref = try env.pop_local();
|
|
|
|
defer env.discard(rhs_ref);
|
|
|
|
const lhs_ref = try env.pop_local();
|
|
|
|
defer env.discard(lhs_ref);
|
|
|
|
try env.push_ref(try switch (env.unbox(lhs_ref)) {
|
|
.number => |lhs_number| switch (env.unbox(rhs_ref)) {
|
|
.number => |rhs_number| env.new_number(lhs_number - rhs_number),
|
|
else => return env.raise(error.TypeMismatch, "right-hand object is not subtractable"),
|
|
},
|
|
|
|
else => return env.raise(error.TypeMismatch, "left-hand object is not subtractable"),
|
|
});
|
|
},
|
|
|
|
.mul => {
|
|
const rhs_ref = try env.pop_local();
|
|
|
|
defer env.discard(rhs_ref);
|
|
|
|
const lhs_ref = try env.pop_local();
|
|
|
|
defer env.discard(lhs_ref);
|
|
|
|
try env.push_ref(try switch (env.unbox(lhs_ref)) {
|
|
.number => |lhs_number| switch (env.unbox(rhs_ref)) {
|
|
.number => |rhs_number| env.new_number(lhs_number * rhs_number),
|
|
else => return env.raise(error.TypeMismatch, "right-hand object is not multiplyable"),
|
|
},
|
|
|
|
else => return env.raise(error.TypeMismatch, "left-hand object is not multiplyable"),
|
|
});
|
|
},
|
|
|
|
.div => {
|
|
const rhs_ref = try env.pop_local();
|
|
|
|
defer env.discard(rhs_ref);
|
|
|
|
const lhs_ref = try env.pop_local();
|
|
|
|
defer env.discard(lhs_ref);
|
|
|
|
try env.push_ref(try switch (env.unbox(lhs_ref)) {
|
|
.number => |lhs_number| switch (env.unbox(rhs_ref)) {
|
|
.number => |rhs_number| env.new_number(lhs_number / rhs_number),
|
|
else => return env.raise(error.TypeMismatch, "right-hand object is not divisable"),
|
|
},
|
|
|
|
else => return env.raise(error.TypeMismatch, "left-hand object is not divisable"),
|
|
});
|
|
},
|
|
|
|
.eql => {
|
|
const rhs_ref = try env.pop_local();
|
|
|
|
defer env.discard(rhs_ref);
|
|
|
|
const lhs_ref = try env.pop_local();
|
|
|
|
defer env.discard(lhs_ref);
|
|
|
|
try env.push_boolean(try kym.test_equality(env, lhs_ref, rhs_ref));
|
|
},
|
|
|
|
.cgt => {
|
|
const rhs_ref = try env.pop_local();
|
|
|
|
defer env.discard(rhs_ref);
|
|
|
|
const lhs_ref = try env.pop_local();
|
|
|
|
defer env.discard(lhs_ref);
|
|
|
|
try env.push_boolean(try kym.test_difference(env, lhs_ref, rhs_ref) > 0);
|
|
},
|
|
|
|
.clt => {
|
|
const rhs_ref = try env.pop_local();
|
|
|
|
defer env.discard(rhs_ref);
|
|
|
|
const lhs_ref = try env.pop_local();
|
|
|
|
defer env.discard(lhs_ref);
|
|
|
|
try env.push_boolean(try kym.test_difference(env, lhs_ref, rhs_ref) < 0);
|
|
},
|
|
|
|
.cge => {
|
|
const rhs_ref = try env.pop_local();
|
|
|
|
defer env.discard(rhs_ref);
|
|
|
|
const lhs_ref = try env.pop_local();
|
|
|
|
defer env.discard(lhs_ref);
|
|
|
|
try env.push_boolean(try kym.test_difference(env, lhs_ref, rhs_ref) >= 0);
|
|
},
|
|
|
|
.cle => {
|
|
const rhs_ref = try env.pop_local();
|
|
|
|
defer env.discard(rhs_ref);
|
|
|
|
const lhs_ref = try env.pop_local();
|
|
|
|
defer env.discard(lhs_ref);
|
|
|
|
try env.push_boolean(try kym.test_difference(env, lhs_ref, rhs_ref) <= 0);
|
|
},
|
|
}
|
|
}
|
|
|
|
return env.pop_local();
|
|
}
|
|
|
|
pub fn free(self: *Self) void {
|
|
for (self.constant_refs.values) |constant| {
|
|
self.env.discard(constant);
|
|
}
|
|
|
|
self.opcodes.free();
|
|
self.constant_refs.free();
|
|
}
|
|
|
|
fn is_zero(utf8: []const u8) bool {
|
|
return coral.io.equals(utf8, "0") or coral.io.equals(utf8, "0.0");
|
|
}
|
|
|
|
pub fn make(env: *kym.RuntimeEnv) Self {
|
|
return Self{
|
|
.opcodes = OpcodeList.make(env.allocator),
|
|
.constant_refs = RefList.make(env.allocator),
|
|
.env = env,
|
|
};
|
|
}
|