Implement Bytecode Executor for Kym #19
|
@ -1,6 +1,8 @@
|
||||||
|
|
||||||
|
title = "Afterglow"
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title = "Afterglow",
|
title = title,
|
||||||
width = 1280,
|
width = 1280,
|
||||||
height = 800,
|
height = 800,
|
||||||
tick_rate = 60,
|
tick_rate = 60,
|
||||||
|
|
|
@ -172,8 +172,6 @@ pub const HexadecimalFormat = struct {
|
||||||
_ = self;
|
_ = self;
|
||||||
_ = writer;
|
_ = writer;
|
||||||
_ = value;
|
_ = value;
|
||||||
|
|
||||||
unreachable;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -267,7 +265,8 @@ noinline fn print_value(writer: io.Writer, value: anytype) PrintError!void {
|
||||||
.Float => DecimalFormat.default.print(writer, value),
|
.Float => DecimalFormat.default.print(writer, value),
|
||||||
|
|
||||||
.Pointer => |pointer| switch (pointer.size) {
|
.Pointer => |pointer| switch (pointer.size) {
|
||||||
.One, .Many, .C => HexadecimalFormat.default.print(writer, @intFromPtr(value)),
|
.Many, .C => HexadecimalFormat.default.print(writer, @intFromPtr(value)),
|
||||||
|
.One => if (pointer.child == []const u8) print_string(writer, *value) else HexadecimalFormat.default.print(writer, @intFromPtr(value)),
|
||||||
.Slice => if (pointer.child == u8) print_string(writer, value) else @compileError(unformattableMessage(Value)),
|
.Slice => if (pointer.child == u8) print_string(writer, value) else @compileError(unformattableMessage(Value)),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -30,9 +30,46 @@ const Compiler = struct {
|
||||||
state: *State,
|
state: *State,
|
||||||
opcodes: OpcodeList,
|
opcodes: OpcodeList,
|
||||||
|
|
||||||
|
locals: struct {
|
||||||
|
buffer: [255][]const coral.io.Byte = [_][]const coral.io.Byte{""} ** 255,
|
||||||
|
count: u8 = 0,
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
fn declare(self: *Self, identifier: []const u8) CompileError!void {
|
||||||
|
if (self.count == self.buffer.len) {
|
||||||
|
return error.TooManyLocals;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.buffer[self.count] = identifier;
|
||||||
|
self.count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve(self: *Self, local_identifier: []const coral.io.Byte) ?u8 {
|
||||||
|
var index = @as(u8, self.count);
|
||||||
|
|
||||||
|
while (index != 0) {
|
||||||
|
index -= 1;
|
||||||
|
|
||||||
|
if (coral.io.equals(local_identifier, self.buffer[index])) {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
const CompileError = coral.io.AllocationError || error {
|
||||||
|
UndefinedLocal,
|
||||||
|
TooManyLocals,
|
||||||
|
};
|
||||||
|
|
||||||
|
const LocalsList = coral.list.Stack([]const u8);
|
||||||
|
|
||||||
const OpcodeList = coral.list.Stack(Opcode);
|
const OpcodeList = coral.list.Stack(Opcode);
|
||||||
|
|
||||||
fn compile_ast(self: *Compiler, ast: Ast) coral.io.AllocationError!void {
|
fn compile_ast(self: *Compiler, ast: Ast) CompileError!void {
|
||||||
for (ast.list_statements()) |statement| {
|
for (ast.list_statements()) |statement| {
|
||||||
switch (statement) {
|
switch (statement) {
|
||||||
.return_expression => |return_expression| {
|
.return_expression => |return_expression| {
|
||||||
|
@ -42,11 +79,21 @@ const Compiler = struct {
|
||||||
.return_nothing => {
|
.return_nothing => {
|
||||||
try self.opcodes.push_one(.push_nil);
|
try self.opcodes.push_one(.push_nil);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
.set_local => |local| {
|
||||||
|
try self.compile_expression(local.expression);
|
||||||
|
|
||||||
|
if (self.locals.resolve(local.identifier)) |index| {
|
||||||
|
try self.opcodes.push_one(.{.set_local = index});
|
||||||
|
} else {
|
||||||
|
try self.locals.declare(local.identifier);
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compile_expression(self: *Compiler, expression: Ast.Expression) coral.io.AllocationError!void {
|
fn compile_expression(self: *Compiler, expression: Ast.Expression) CompileError!void {
|
||||||
const is_zero = struct {
|
const is_zero = struct {
|
||||||
fn is_zero(utf8: []const u8) bool {
|
fn is_zero(utf8: []const u8) bool {
|
||||||
return coral.io.equals(utf8, "0") or coral.io.equals(utf8, "0.0");
|
return coral.io.equals(utf8, "0") or coral.io.equals(utf8, "0.0");
|
||||||
|
@ -121,7 +168,13 @@ const Compiler = struct {
|
||||||
|
|
||||||
.grouped_expression => |grouped_expression| {
|
.grouped_expression => |grouped_expression| {
|
||||||
try self.compile_expression(grouped_expression.*);
|
try self.compile_expression(grouped_expression.*);
|
||||||
}
|
},
|
||||||
|
|
||||||
|
.get_local => |local| {
|
||||||
|
try self.opcodes.push_one(.{
|
||||||
|
.get_local = self.locals.resolve(local) orelse return error.UndefinedLocal,
|
||||||
|
});
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,6 +194,7 @@ const Compiler = struct {
|
||||||
|
|
||||||
fn make(allocator: coral.io.Allocator, state: *State) Compiler {
|
fn make(allocator: coral.io.Allocator, state: *State) Compiler {
|
||||||
return .{
|
return .{
|
||||||
|
.locals = .{},
|
||||||
.opcodes = OpcodeList.make(allocator),
|
.opcodes = OpcodeList.make(allocator),
|
||||||
.state = state,
|
.state = state,
|
||||||
};
|
};
|
||||||
|
@ -190,6 +244,9 @@ pub const Opcode = union (enum) {
|
||||||
push_table: u32,
|
push_table: u32,
|
||||||
push_object: *State.Object,
|
push_object: *State.Object,
|
||||||
|
|
||||||
|
set_local: u8,
|
||||||
|
get_local: u8,
|
||||||
|
|
||||||
not,
|
not,
|
||||||
neg,
|
neg,
|
||||||
|
|
||||||
|
@ -268,6 +325,16 @@ pub const RuntimeEnv = struct {
|
||||||
try self.state.push_value(.{.object = acquired_object});
|
try self.state.push_value(.{.object = acquired_object});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
.set_local => |local| {
|
||||||
|
if (!self.state.set_value(local, try self.state.pop_value())) {
|
||||||
|
return self.raise(error.BadOperation, "invalid local set");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
.get_local => |local| {
|
||||||
|
try self.state.push_value(self.state.get_value(local));
|
||||||
|
},
|
||||||
|
|
||||||
.not => {
|
.not => {
|
||||||
try self.state.push_value(switch (try self.state.pop_value()) {
|
try self.state.push_value(switch (try self.state.pop_value()) {
|
||||||
.nil => return self.raise(error.BadOperation, "cannot convert nil to true or false"),
|
.nil => return self.raise(error.BadOperation, "cannot convert nil to true or false"),
|
||||||
|
@ -389,7 +456,11 @@ pub const RuntimeEnv = struct {
|
||||||
|
|
||||||
defer compiler.free();
|
defer compiler.free();
|
||||||
|
|
||||||
try compiler.compile_ast(ast);
|
compiler.compile_ast(ast) catch |compile_error| return switch (compile_error) {
|
||||||
|
error.OutOfMemory => error.OutOfMemory,
|
||||||
|
error.UndefinedLocal => self.raise(error.BadOperation, "use of undefined local"),
|
||||||
|
error.TooManyLocals => self.raise(error.OutOfMemory, "functions cannot contain more than 255 locals"),
|
||||||
|
};
|
||||||
|
|
||||||
return self.execute_chunk(source.name, compiler.list_opcodes());
|
return self.execute_chunk(source.name, compiler.list_opcodes());
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ pub const Expression = union (enum) {
|
||||||
string_literal: []const u8,
|
string_literal: []const u8,
|
||||||
table_literal: NamedList,
|
table_literal: NamedList,
|
||||||
grouped_expression: *Expression,
|
grouped_expression: *Expression,
|
||||||
|
get_local: []const u8,
|
||||||
|
|
||||||
binary_operation: struct {
|
binary_operation: struct {
|
||||||
operator: BinaryOperator,
|
operator: BinaryOperator,
|
||||||
|
@ -74,6 +75,11 @@ pub const Statement = union (enum) {
|
||||||
return_expression: Expression,
|
return_expression: Expression,
|
||||||
return_nothing,
|
return_nothing,
|
||||||
|
|
||||||
|
set_local: struct {
|
||||||
|
identifier: []const coral.io.Byte,
|
||||||
|
expression: Expression,
|
||||||
|
},
|
||||||
|
|
||||||
const List = coral.list.Stack(Statement);
|
const List = coral.list.Stack(Statement);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -175,6 +181,33 @@ pub fn parse(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!void {
|
||||||
has_returned = true;
|
has_returned = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
.local => |identifier| {
|
||||||
|
try self.check_syntax(tokenizer.step(.{.include_newlines = true}), "statement has no effect");
|
||||||
|
|
||||||
|
switch (tokenizer.current_token) {
|
||||||
|
.symbol_equals => {
|
||||||
|
try self.check_syntax(
|
||||||
|
tokenizer.step(.{.include_newlines = true}),
|
||||||
|
"expected expression after `=`");
|
||||||
|
|
||||||
|
try self.statements.push_one(.{
|
||||||
|
.set_local = .{
|
||||||
|
.identifier = identifier,
|
||||||
|
.expression = try self.parse_expression(tokenizer)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (tokenizer.step(.{.include_newlines = true})) {
|
||||||
|
try self.check_syntax(
|
||||||
|
tokenizer.current_token == .newline,
|
||||||
|
"expected end of declaration after variable assignment");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
else => return self.fail_syntax("expected `=` after local"),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
else => return self.fail_syntax("invalid statement"),
|
else => return self.fail_syntax("invalid statement"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -211,17 +244,17 @@ fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression
|
||||||
},
|
},
|
||||||
|
|
||||||
.number => |value| {
|
.number => |value| {
|
||||||
_ = tokenizer.step(.{.include_newlines = false});
|
|
||||||
|
|
||||||
return Expression{.number_literal = value};
|
return Expression{.number_literal = value};
|
||||||
},
|
},
|
||||||
|
|
||||||
.string => |value| {
|
.string => |value| {
|
||||||
_ = tokenizer.step(.{.include_newlines = false});
|
|
||||||
|
|
||||||
return Expression{.string_literal = value};
|
return Expression{.string_literal = value};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
.local => |identifier| {
|
||||||
|
return Expression{.get_local = identifier};
|
||||||
|
},
|
||||||
|
|
||||||
.symbol_brace_left => {
|
.symbol_brace_left => {
|
||||||
try self.check_syntax(tokenizer.step(.{.include_newlines = false}), "unexpected end of table literal");
|
try self.check_syntax(tokenizer.step(.{.include_newlines = false}), "unexpected end of table literal");
|
||||||
|
|
||||||
|
@ -249,6 +282,8 @@ fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression
|
||||||
.expression = try self.parse_expression(tokenizer),
|
.expression = try self.parse_expression(tokenizer),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
try self.check_syntax(tokenizer.step(.{.include_newlines = false}), "unexpected end of table");
|
||||||
|
|
||||||
switch (tokenizer.current_token) {
|
switch (tokenizer.current_token) {
|
||||||
.symbol_comma => _ = tokenizer.step(.{.include_newlines = false}),
|
.symbol_comma => _ = tokenizer.step(.{.include_newlines = false}),
|
||||||
|
|
||||||
|
|
|
@ -100,6 +100,14 @@ pub fn free(self: *Self) void {
|
||||||
self.interned.free();
|
self.interned.free();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_value(self: *Self, tail_index: usize) Variant {
|
||||||
|
if (tail_index >= self.values.values.len) {
|
||||||
|
return .nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.values.values[self.values.values.len - (1 + tail_index)];
|
||||||
|
}
|
||||||
|
|
||||||
pub fn make(allocator: coral.io.Allocator) Self {
|
pub fn make(allocator: coral.io.Allocator) Self {
|
||||||
return .{
|
return .{
|
||||||
.values = DataStack.make(allocator),
|
.values = DataStack.make(allocator),
|
||||||
|
@ -132,3 +140,13 @@ pub fn release(self: *Self, object: *Object) void {
|
||||||
self.allocator.deallocate(object);
|
self.allocator.deallocate(object);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_value(self: *Self, tail_index: usize, value: Variant) bool {
|
||||||
|
if (tail_index >= self.values.values.len) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.values.values[self.values.values.len - (1 + tail_index)] = value;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
|
@ -37,6 +37,7 @@ pub const Token = union(enum) {
|
||||||
keyword_true,
|
keyword_true,
|
||||||
keyword_return,
|
keyword_return,
|
||||||
keyword_self,
|
keyword_self,
|
||||||
|
keyword_const,
|
||||||
|
|
||||||
pub fn text(self: Token) []const u8 {
|
pub fn text(self: Token) []const u8 {
|
||||||
return switch (self) {
|
return switch (self) {
|
||||||
|
@ -71,6 +72,7 @@ pub const Token = union(enum) {
|
||||||
.number => |literal| literal,
|
.number => |literal| literal,
|
||||||
.string => |literal| literal,
|
.string => |literal| literal,
|
||||||
|
|
||||||
|
.keyword_const => "const",
|
||||||
.keyword_nil => "nil",
|
.keyword_nil => "nil",
|
||||||
.keyword_false => "false",
|
.keyword_false => "false",
|
||||||
.keyword_true => "true",
|
.keyword_true => "true",
|
||||||
|
@ -160,6 +162,12 @@ pub const Tokenizer = struct {
|
||||||
coral.debug.assert(identifier.len != 0);
|
coral.debug.assert(identifier.len != 0);
|
||||||
|
|
||||||
switch (identifier[0]) {
|
switch (identifier[0]) {
|
||||||
|
'c' => if (coral.io.ends_with(identifier, "onst")) {
|
||||||
|
self.current_token = .keyword_const;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
'n' => if (coral.io.ends_with(identifier, "il")) {
|
'n' => if (coral.io.ends_with(identifier, "il")) {
|
||||||
self.current_token = .keyword_nil;
|
self.current_token = .keyword_nil;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue