diff --git a/debug/app.ona b/debug/app.ona index f787fc7..48b8ee8 100644 --- a/debug/app.ona +++ b/debug/app.ona @@ -1,5 +1,5 @@ -i = 0 +var i = 0 while i < 5: @print("hello, world") diff --git a/source/ona/kym.zig b/source/ona/kym.zig index 59803b9..5415f5b 100644 --- a/source/ona/kym.zig +++ b/source/ona/kym.zig @@ -70,8 +70,18 @@ pub const RuntimeEnv = struct { const OpcodeList = coral.list.Stack(Opcode); const CompilationUnit = struct { - local_identifiers_buffer: [255][]const coral.io.Byte = [_][]const coral.io.Byte{""} ** 255, - local_identifiers_count: u8 = 0, + locals_buffer: [255]Local = [_]Local{.{}} ** 255, + locals_count: u8 = 0, + + const ResolvedLocal = struct { + index: u8, + is_readonly: bool = false, + }; + + const Local = struct { + identifier: []const coral.io.Byte = "", + is_readonly: bool = false, + }; fn compile_expression(self: *CompilationUnit, chunk: *Chunk, expression: ast.Expression) RuntimeError!void { const number_format = coral.utf8.DecimalFormat{ @@ -204,25 +214,24 @@ pub const RuntimeEnv = struct { .local_get => |local_get| { try chunk.opcodes.push_one(.{ - .push_local = self.resolve_local(local_get.identifier) orelse { + .push_local = (self.resolve_local(local_get.identifier) orelse { return chunk.env.raise(error.OutOfMemory, "undefined local"); - }, + }).index, }); }, .local_set => |local_set| { try self.compile_expression(chunk, local_set.value_expression.*); - if (self.resolve_local(local_set.identifier)) |index| { - try chunk.opcodes.push_one(.{.local_set = index}); - } else { - if (self.local_identifiers_count == self.local_identifiers_buffer.len) { - return chunk.env.raise(error.BadSyntax, "chunks may have a maximum of 255 locals"); - } + const resolved_local = self.resolve_local(local_set.identifier) orelse { + return chunk.env.raise(error.BadSyntax, "undefined local"); + }; - self.local_identifiers_buffer[self.local_identifiers_count] = local_set.identifier; - self.local_identifiers_count += 1; + if (resolved_local.is_readonly) { + return chunk.env.raise(error.BadSyntax, "cannot set a read-only declaration"); } + + try chunk.opcodes.push_one(.{.local_set = resolved_local.index}); }, .field_get => |field_get| { @@ -271,6 +280,25 @@ pub const RuntimeEnv = struct { } }, + .declare => |declare| { + if (self.resolve_local(declare.identifier) != null) { + return chunk.env.raise(error.BadSyntax, "declaration shadows existing one"); + } + + try self.compile_expression(chunk, declare.assigned_expression); + + if (self.locals_count == self.locals_buffer.len) { + return chunk.env.raise(error.BadSyntax, "chunks may have a maximum of 255 locals"); + } + + self.locals_buffer[self.locals_count] = .{ + .identifier = declare.identifier, + .is_readonly = declare.storage != .variant, + }; + + self.locals_count += 1; + }, + .block => |block| { for (block.values) |block_statement| { try self.compile_statement(chunk, block_statement); @@ -320,16 +348,21 @@ pub const RuntimeEnv = struct { } } - fn resolve_local(self: *CompilationUnit, local_identifier: []const coral.io.Byte) ?u8 { - if (self.local_identifiers_count == 0) { + fn resolve_local(self: *CompilationUnit, local_identifier: []const coral.io.Byte) ?ResolvedLocal { + if (self.locals_count == 0) { return null; } - var index = @as(u8, self.local_identifiers_count - 1); + var index = @as(u8, self.locals_count - 1); while (true) : (index -= 1) { - if (coral.io.are_equal(local_identifier, self.local_identifiers_buffer[index])) { - return index; + const local = &self.locals_buffer[index]; + + if (coral.io.are_equal(local_identifier, local.identifier)) { + return .{ + .index = index, + .is_readonly = local.is_readonly, + }; } if (index == 0) { diff --git a/source/ona/kym/ast.zig b/source/ona/kym/ast.zig index 9afb040..c95bce2 100644 --- a/source/ona/kym/ast.zig +++ b/source/ona/kym/ast.zig @@ -129,6 +129,11 @@ pub const Expression = union (enum) { }); }; +pub const DeclarationStorage = enum { + variant, + readonly, +}; + const ExpressionBuilder = fn (self: *Tree) ParseError!Expression; pub const ParseError = error { @@ -139,6 +144,12 @@ pub const ParseError = error { pub const Statement = union (enum) { @"return": ?Expression, + declare: struct { + storage: DeclarationStorage, + identifier: []const coral.io.Byte, + assigned_expression: Expression, + }, + @"if": struct { condition_expression: Expression, block_statements: List, @@ -650,6 +661,56 @@ pub const Tree = struct { }; }, + .keyword_var => { + self.tokenizer.skip_newlines(); + + const identifier = switch (self.tokenizer.token) { + .identifier => |identifier| identifier, + else => return self.report("expected identifier after `var` declaration statement"), + }; + + self.tokenizer.skip_newlines(); + + if (self.tokenizer.token != .symbol_equals) { + return self.report("expected `=` after declaration identifier"); + } + + self.tokenizer.skip_newlines(); + + return .{ + .declare = .{ + .assigned_expression = try self.parse_expression(), + .storage = .variant, + .identifier = identifier, + }, + }; + }, + + .keyword_let => { + self.tokenizer.skip_newlines(); + + const identifier = switch (self.tokenizer.token) { + .identifier => |identifier| identifier, + else => return self.report("expected identifier after `let` declaration statement"), + }; + + self.tokenizer.skip_newlines(); + + if (self.tokenizer.token != .symbol_equals) { + return self.report("expected `=` after declaration identifier"); + } + + self.tokenizer.skip_newlines(); + + return .{ + .declare = .{ + .assigned_expression = try self.parse_expression(), + .storage = .readonly, + .identifier = identifier, + }, + }; + }, + .keyword_if => return self.parse_branch(), else => return .{.expression = try self.parse_expression()}, } diff --git a/source/ona/kym/tokens.zig b/source/ona/kym/tokens.zig index 3869cf1..3f1aa53 100755 --- a/source/ona/kym/tokens.zig +++ b/source/ona/kym/tokens.zig @@ -44,6 +44,8 @@ pub const Token = union(enum) { keyword_while, keyword_else, keyword_elif, + keyword_var, + keyword_let, pub fn text(self: Token) []const coral.io.Byte { return switch (self) { @@ -91,6 +93,8 @@ pub const Token = union(enum) { .keyword_while => "while", .keyword_elif => "elif", .keyword_else => "else", + .keyword_var => "var", + .keyword_let => "let", }; } }; @@ -229,6 +233,14 @@ pub const Tokenizer = struct { } }, + 'l' => { + if (coral.io.ends_with(identifier, "et")) { + self.token = .keyword_let; + + return; + } + }, + 'n' => { if (coral.io.ends_with(identifier, "il")) { self.token = .keyword_nil; @@ -261,6 +273,14 @@ pub const Tokenizer = struct { } }, + 'v' => { + if (coral.io.ends_with(identifier, "ar")) { + self.token = .keyword_var; + + return; + } + }, + 'w' => { if (coral.io.ends_with(identifier, "hile")) { self.token = .keyword_while;