Add local variables to Kym
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/pr Build is passing Details

This commit is contained in:
kayomn 2023-07-12 01:51:32 +01:00
parent 48f7f97893
commit dc674ae2c3
6 changed files with 145 additions and 12 deletions

View File

@ -1,6 +1,8 @@
title = "Afterglow"
return {
title = "Afterglow",
title = title,
width = 1280,
height = 800,
tick_rate = 60,

View File

@ -172,8 +172,6 @@ pub const HexadecimalFormat = struct {
_ = self;
_ = writer;
_ = value;
unreachable;
}
};
@ -267,7 +265,8 @@ noinline fn print_value(writer: io.Writer, value: anytype) PrintError!void {
.Float => DecimalFormat.default.print(writer, value),
.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)),
},

View File

@ -30,9 +30,46 @@ const Compiler = struct {
state: *State,
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);
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| {
switch (statement) {
.return_expression => |return_expression| {
@ -42,11 +79,21 @@ const Compiler = struct {
.return_nothing => {
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 {
fn is_zero(utf8: []const u8) bool {
return coral.io.equals(utf8, "0") or coral.io.equals(utf8, "0.0");
@ -121,7 +168,13 @@ const Compiler = struct {
.grouped_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 {
return .{
.locals = .{},
.opcodes = OpcodeList.make(allocator),
.state = state,
};
@ -190,6 +244,9 @@ pub const Opcode = union (enum) {
push_table: u32,
push_object: *State.Object,
set_local: u8,
get_local: u8,
not,
neg,
@ -268,6 +325,16 @@ pub const RuntimeEnv = struct {
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 => {
try self.state.push_value(switch (try self.state.pop_value()) {
.nil => return self.raise(error.BadOperation, "cannot convert nil to true or false"),
@ -389,7 +456,11 @@ pub const RuntimeEnv = struct {
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());
}

View File

@ -15,6 +15,7 @@ pub const Expression = union (enum) {
string_literal: []const u8,
table_literal: NamedList,
grouped_expression: *Expression,
get_local: []const u8,
binary_operation: struct {
operator: BinaryOperator,
@ -74,6 +75,11 @@ pub const Statement = union (enum) {
return_expression: Expression,
return_nothing,
set_local: struct {
identifier: []const coral.io.Byte,
expression: Expression,
},
const List = coral.list.Stack(Statement);
};
@ -175,6 +181,33 @@ pub fn parse(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!void {
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"),
}
}
@ -211,17 +244,17 @@ fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression
},
.number => |value| {
_ = tokenizer.step(.{.include_newlines = false});
return Expression{.number_literal = value};
},
.string => |value| {
_ = tokenizer.step(.{.include_newlines = false});
return Expression{.string_literal = value};
},
.local => |identifier| {
return Expression{.get_local = identifier};
},
.symbol_brace_left => {
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),
});
try self.check_syntax(tokenizer.step(.{.include_newlines = false}), "unexpected end of table");
switch (tokenizer.current_token) {
.symbol_comma => _ = tokenizer.step(.{.include_newlines = false}),

View File

@ -100,6 +100,14 @@ pub fn free(self: *Self) void {
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 {
return .{
.values = DataStack.make(allocator),
@ -132,3 +140,13 @@ pub fn release(self: *Self, object: *Object) void {
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_return,
keyword_self,
keyword_const,
pub fn text(self: Token) []const u8 {
return switch (self) {
@ -71,6 +72,7 @@ pub const Token = union(enum) {
.number => |literal| literal,
.string => |literal| literal,
.keyword_const => "const",
.keyword_nil => "nil",
.keyword_false => "false",
.keyword_true => "true",
@ -160,6 +162,12 @@ pub const Tokenizer = struct {
coral.debug.assert(identifier.len != 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")) {
self.current_token = .keyword_nil;