From dc674ae2c34aef8a84398f19b725f4312d973720 Mon Sep 17 00:00:00 2001 From: kayomn Date: Wed, 12 Jul 2023 01:51:32 +0100 Subject: [PATCH] Add local variables to Kym --- debug/app.ona | 4 +- source/coral/utf8.zig | 5 +-- source/ona/kym.zig | 79 +++++++++++++++++++++++++++++++++++++-- source/ona/kym/Ast.zig | 43 +++++++++++++++++++-- source/ona/kym/State.zig | 18 +++++++++ source/ona/kym/tokens.zig | 8 ++++ 6 files changed, 145 insertions(+), 12 deletions(-) diff --git a/debug/app.ona b/debug/app.ona index 7b46045..69c8d59 100644 --- a/debug/app.ona +++ b/debug/app.ona @@ -1,6 +1,8 @@ +title = "Afterglow" + return { - title = "Afterglow", + title = title, width = 1280, height = 800, tick_rate = 60, diff --git a/source/coral/utf8.zig b/source/coral/utf8.zig index 28e3ad7..48b6199 100644 --- a/source/coral/utf8.zig +++ b/source/coral/utf8.zig @@ -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)), }, diff --git a/source/ona/kym.zig b/source/ona/kym.zig index 997a895..5e7825b 100644 --- a/source/ona/kym.zig +++ b/source/ona/kym.zig @@ -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()); } diff --git a/source/ona/kym/Ast.zig b/source/ona/kym/Ast.zig index c0dba35..82829b3 100755 --- a/source/ona/kym/Ast.zig +++ b/source/ona/kym/Ast.zig @@ -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}), diff --git a/source/ona/kym/State.zig b/source/ona/kym/State.zig index ef98d59..7f2688a 100644 --- a/source/ona/kym/State.zig +++ b/source/ona/kym/State.zig @@ -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; +} diff --git a/source/ona/kym/tokens.zig b/source/ona/kym/tokens.zig index 3e3ec3a..a04cf80 100755 --- a/source/ona/kym/tokens.zig +++ b/source/ona/kym/tokens.zig @@ -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;