Implement Bytecode Executor for Kym #19

Merged
kayomn merged 9 commits from kym-bytecode-executor into main 2023-07-12 02:58:03 +02:00
6 changed files with 145 additions and 12 deletions
Showing only changes of commit dc674ae2c3 - Show all commits

View File

@ -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,

View File

@ -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)),
}, },

View File

@ -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());
} }

View File

@ -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}),

View File

@ -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;
}

View File

@ -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;