Add local variables to Kym
This commit is contained in:
parent
48f7f97893
commit
dc674ae2c3
|
@ -1,6 +1,8 @@
|
|||
|
||||
title = "Afterglow"
|
||||
|
||||
return {
|
||||
title = "Afterglow",
|
||||
title = title,
|
||||
width = 1280,
|
||||
height = 800,
|
||||
tick_rate = 60,
|
||||
|
|
|
@ -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)),
|
||||
},
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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}),
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Reference in New Issue