diff --git a/.justfile b/.justfile index 24a2c22..69346f9 100644 --- a/.justfile +++ b/.justfile @@ -1,8 +1,8 @@ build: zig build -run: - zig build run +run *ARGS: + zig build run -- {{ARGS}} test: zig build test diff --git a/README.md b/README.md index ed89a21..2cfb93b 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ While reading [Crafting Interpreters](https://craftinginterpreters.com/), after - [x] 18 - Types of Values - [x] 19 - Strings - [x] 20 - Hash Tables -- [ ] 21 - Global Variables +- [x] 21 - Global Variables - [ ] 22 - Local Variables - [ ] 23 - Jumping Back and Forth - [ ] 24 - Calls and Functions diff --git a/samples/ch21_breakfast.lox b/samples/ch21_breakfast.lox new file mode 100644 index 0000000..87eed5c --- /dev/null +++ b/samples/ch21_breakfast.lox @@ -0,0 +1,5 @@ +var breakfast = "beignets"; +var beverage = "cafe au lait"; +breakfast = "beignets with " + beverage; + +print breakfast; \ No newline at end of file diff --git a/src/chunk.zig b/src/chunk.zig index 764731d..f7499ec 100644 --- a/src/chunk.zig +++ b/src/chunk.zig @@ -92,6 +92,11 @@ pub const Chunk = struct { @intFromEnum(OpCode.OP_EQUAL) => return utils.simple_instruction("OP_EQUAL", offset), @intFromEnum(OpCode.OP_GREATER) => return utils.simple_instruction("OP_GREATER", offset), @intFromEnum(OpCode.OP_LESS) => return utils.simple_instruction("OP_LESS", offset), + @intFromEnum(OpCode.OP_PRINT) => return utils.simple_instruction("OP_PRINT", offset), + @intFromEnum(OpCode.OP_POP) => return utils.simple_instruction("OP_POP", offset), + @intFromEnum(OpCode.OP_DEFINE_GLOBAL) => return utils.constant_instruction("OP_DEFINE_GLOBAL", self, offset), + @intFromEnum(OpCode.OP_GET_GLOBAL) => return utils.constant_instruction("OP_GET_GLOBAL", self, offset), + @intFromEnum(OpCode.OP_SET_GLOBAL) => return utils.constant_instruction("OP_SET_GLOBAL", self, offset), else => { debug.print("unknown opcode {d}\n", .{instruction}); return offset + 1; diff --git a/src/compile.zig b/src/compile.zig index ca72de9..e295a3c 100644 --- a/src/compile.zig +++ b/src/compile.zig @@ -30,8 +30,8 @@ const Precedence = enum { }; const ParserRule = struct { - prefix: ?*const fn (*Parser) ParsingError!void, - infix: ?*const fn (*Parser) ParsingError!void, + prefix: ?*const fn (*Parser, bool) ParsingError!void, + infix: ?*const fn (*Parser, bool) ParsingError!void, precedence: Precedence, }; @@ -134,7 +134,9 @@ const Parser = struct { try self.emit_return(); } - fn number(self: *Parser) ParsingError!void { + fn number(self: *Parser, can_assign: bool) ParsingError!void { + _ = can_assign; + const value = std.fmt.parseFloat(f64, self.previous.?.start[0..self.previous.?.length]) catch { self.error_msg("Failed converting float."); return ParsingError.FloatConv; @@ -162,12 +164,16 @@ const Parser = struct { return @intCast(constant); } - fn grouping(self: *Parser) ParsingError!void { + fn grouping(self: *Parser, can_assign: bool) ParsingError!void { + _ = can_assign; + try self.expression(); self.consume(TokenType.RIGHT_PAREN, "Expect ')' after expression."); } - fn unary(self: *Parser) ParsingError!void { + fn unary(self: *Parser, can_assign: bool) ParsingError!void { + _ = can_assign; + const operation_type = self.previous.?.token_type; // Compile the operand @@ -193,7 +199,9 @@ const Parser = struct { } } - fn binary(self: *Parser) ParsingError!void { + fn binary(self: *Parser, can_assign: bool) ParsingError!void { + _ = can_assign; + const operator_type = self.previous.?.token_type; const parser_rule = Parser.get_rule(operator_type); @@ -235,7 +243,7 @@ const Parser = struct { TokenType.GREATER_EQUAL => ParserRule{ .prefix = null, .infix = binary, .precedence = Precedence.Comparison }, TokenType.LESS => ParserRule{ .prefix = null, .infix = binary, .precedence = Precedence.Comparison }, TokenType.LESS_EQUAL => ParserRule{ .prefix = null, .infix = binary, .precedence = Precedence.Comparison }, - TokenType.IDENTIFIER => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None }, + TokenType.IDENTIFIER => ParserRule{ .prefix = variable, .infix = null, .precedence = Precedence.None }, TokenType.STRING => ParserRule{ .prefix = string, .infix = null, .precedence = Precedence.None }, TokenType.NUMBER => ParserRule{ .prefix = number, .infix = null, .precedence = Precedence.None }, TokenType.AND => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None }, @@ -268,16 +276,23 @@ const Parser = struct { return; } - try prefix_rule.?(self); + const can_assign = @intFromEnum(precedence) <= @intFromEnum(Precedence.Assignement); + try prefix_rule.?(self, can_assign); while (@intFromEnum(precedence) <= @intFromEnum(Parser.get_rule(self.current.?.token_type).precedence)) { self.advance(); const infix_rule = Parser.get_rule(self.previous.?.token_type).infix; - try infix_rule.?(self); + try infix_rule.?(self, can_assign); + } + + if (can_assign and self.match(TokenType.EQUAL)) { + self.error_msg("Invalid assignment target."); } } - fn literal(self: *Parser) ParsingError!void { + fn literal(self: *Parser, can_assign: bool) ParsingError!void { + _ = can_assign; + try switch (self.previous.?.token_type) { TokenType.NIL => self.emit_byte(@intFromEnum(OpCode.OP_NIL)), TokenType.TRUE => self.emit_byte(@intFromEnum(OpCode.OP_TRUE)), @@ -286,7 +301,9 @@ const Parser = struct { }; } - fn string(self: *Parser) ParsingError!void { + fn string(self: *Parser, can_assign: bool) ParsingError!void { + _ = can_assign; + const str = self.previous.?.start[1 .. self.previous.?.length - 1]; var string_obj = self.vm.copy_string(str); @@ -295,6 +312,116 @@ const Parser = struct { try self.emit_constant(Value.obj_val(&string_obj.obj)); } + + fn variable(self: *Parser, can_assign: bool) ParsingError!void { + try self.named_variable(self.previous.?, can_assign); + } + + fn named_variable(self: *Parser, token: Token, can_assign: bool) ParsingError!void { + const constant = try self.identifier_constant(token); + if (can_assign and self.match(TokenType.EQUAL)) { + try self.expression(); + try self.emit_bytes(@intFromEnum(OpCode.OP_SET_GLOBAL), constant); + } else { + try self.emit_bytes(@intFromEnum(OpCode.OP_GET_GLOBAL), constant); + } + } + + fn declaration(self: *Parser) ParsingError!void { + if (self.match(TokenType.VAR)) { + try self.var_declaration(); + } else { + try self.statement(); + } + + if (self.panic_mode) { + self.synchronize(); + } + } + + fn statement(self: *Parser) ParsingError!void { + if (self.match(TokenType.PRINT)) { + try self.print_statement(); + } else { + try self.expression_statement(); + } + } + + fn match(self: *Parser, token_type: TokenType) bool { + if (!self.check(token_type)) + return false; + self.advance(); + return true; + } + + fn check(self: *Parser, token_type: TokenType) bool { + return self.current.?.token_type == token_type; + } + + fn print_statement(self: *Parser) ParsingError!void { + try self.expression(); + self.consume(TokenType.SEMICOLON, "Expect ';' after value."); + try self.emit_byte(@intFromEnum(OpCode.OP_PRINT)); + } + + fn expression_statement(self: *Parser) ParsingError!void { + try self.expression(); + self.consume(TokenType.SEMICOLON, "Expect ';' after value."); + try self.emit_byte(@intFromEnum(OpCode.OP_POP)); + } + + fn synchronize(self: *Parser) void { + self.panic_mode = false; + + while (self.current.?.token_type != TokenType.EOF) { + if (self.previous.?.token_type == TokenType.SEMICOLON) { + return; + } + + switch (self.current.?.token_type) { + TokenType.CLASS, + TokenType.FUN, + TokenType.VAR, + TokenType.FOR, + TokenType.IF, + TokenType.WHILE, + TokenType.PRINT, + TokenType.RETURN, + => return, + else => {}, + } + + self.advance(); + } + } + + fn var_declaration(self: *Parser) ParsingError!void { + const global = try self.parse_variable("Expect variable name."); + + if (self.match(TokenType.EQUAL)) { + try self.expression(); + } else { + try self.emit_byte(@intFromEnum(OpCode.OP_NIL)); + } + self.consume(TokenType.SEMICOLON, "Expect ';' after variable declaration."); + + try self.define_variable(global); + } + + fn parse_variable(self: *Parser, err_msg: []const u8) ParsingError!u8 { + self.consume(TokenType.IDENTIFIER, err_msg); + return self.identifier_constant(self.previous.?); + } + + fn identifier_constant(self: *Parser, token: Token) ParsingError!u8 { + const copy = &self.vm.copy_string(token.start[0..token.length]).obj; + self.vm.add_reference(copy); + return self.make_constant(Value.obj_val(copy)); + } + + fn define_variable(self: *Parser, global: u8) ParsingError!void { + return self.emit_bytes(@intFromEnum(OpCode.OP_DEFINE_GLOBAL), global); + } }; pub fn compile(allocator: Allocator, vm: *VM, contents: []const u8, chunk: *Chunk) !bool { @@ -304,8 +431,11 @@ pub fn compile(allocator: Allocator, vm: *VM, contents: []const u8, chunk: *Chun var parser = Parser.new(vm, &scanner, chunk); parser.advance(); - try parser.expression(); - parser.consume(TokenType.EOF, "Expect end of expression."); + + while (!parser.match(TokenType.EOF)) { + try parser.declaration(); + } + try parser.end_parser(); return !parser.had_error; diff --git a/src/main.zig b/src/main.zig index cb80543..945ec7d 100644 --- a/src/main.zig +++ b/src/main.zig @@ -17,7 +17,11 @@ pub fn repl(allocator: Allocator, vm: *VM) !void { const stdout = std.io.getStdOut().writer(); while (true) { - try stdout.print("> ", .{}); + if (vm.has_tracing()) { + vm.globals.dump(); + } + + try stdout.print("zlox> ", .{}); @memset(&line, 0); diff --git a/src/opcode.zig b/src/opcode.zig index daeec65..3119bb5 100644 --- a/src/opcode.zig +++ b/src/opcode.zig @@ -3,6 +3,10 @@ pub const OpCode = enum(u8) { OP_NIL, OP_TRUE, OP_FALSE, + OP_POP, + OP_GET_GLOBAL, + OP_DEFINE_GLOBAL, + OP_SET_GLOBAL, OP_EQUAL, OP_GREATER, OP_LESS, @@ -12,5 +16,6 @@ pub const OpCode = enum(u8) { OP_DIVIDE, OP_NOT, OP_NEGATE, + OP_PRINT, OP_RETURN, }; diff --git a/src/table.zig b/src/table.zig index e7acfbc..dcdc625 100644 --- a/src/table.zig +++ b/src/table.zig @@ -123,7 +123,7 @@ pub const Table = struct { for (self.entries, 0..) |entry, idx| { if (entry.key != null) { std.debug.print("{d} ({d}) - {s}: ", .{ idx, entry.key.?.hash, entry.key.?.chars }); - entry.value.print(); + entry.value.type_print(); std.debug.print("\n", .{}); } diff --git a/src/values.zig b/src/values.zig index 9e194bb..5f76929 100644 --- a/src/values.zig +++ b/src/values.zig @@ -70,7 +70,7 @@ pub const Value = struct { } pub fn as_string(self: Value) *Obj.String { - const obj: *Obj.String = self.as_obj(); + const obj: *Obj.String = self.as_obj().as_string(); return obj; } @@ -126,6 +126,18 @@ pub const Value = struct { ValueType.Obj => self.as_obj().print(), } } + + pub fn type_print(self: Value) void { + switch (self.value_type) { + ValueType.Nil => debug.print("(nil)", .{}), + ValueType.Bool => debug.print("(bool)", .{}), + ValueType.Number => debug.print("(number)", .{}), + ValueType.Obj => debug.print("(obj)", .{}), + } + + debug.print(" ", .{}); + self.print(); + } }; pub const ValueArray = struct { diff --git a/src/vm.zig b/src/vm.zig index 050e6ad..76e75b6 100644 --- a/src/vm.zig +++ b/src/vm.zig @@ -32,6 +32,7 @@ pub const VM = struct { // In the book, a linked list between objects is used to handle this. references: std.ArrayList(*Obj), strings: Table, + globals: Table, tracing: bool, pub fn new(allocator: Allocator) VM { @@ -42,6 +43,7 @@ pub const VM = struct { .stack = std.ArrayList(Value).init(allocator), .references = std.ArrayList(*Obj).init(allocator), .strings = Table.new(allocator), + .globals = Table.new(allocator), .tracing = false, }; } @@ -54,6 +56,7 @@ pub const VM = struct { } self.strings.deinit(); + self.globals.deinit(); self.clean_references(); self.references.deinit(); } @@ -106,6 +109,7 @@ pub const VM = struct { @intFromEnum(OpCode.OP_NIL) => try self.push(Value.nil_val()), @intFromEnum(OpCode.OP_FALSE) => try self.push(Value.bool_val(false)), @intFromEnum(OpCode.OP_TRUE) => try self.push(Value.bool_val(true)), + @intFromEnum(OpCode.OP_POP) => _ = self.pop(), @intFromEnum(OpCode.OP_ADD), @intFromEnum(OpCode.OP_SUBSTRACT), @intFromEnum(OpCode.OP_MULTIPLY), @@ -128,14 +132,48 @@ pub const VM = struct { } try self.push(Value.number_val(-self.pop().as_number())); }, - @intFromEnum(OpCode.OP_RETURN) => { + @intFromEnum(OpCode.OP_PRINT) => { print_value(self.pop()); debug.print("\n", .{}); + }, + @intFromEnum(OpCode.OP_RETURN) => { return InterpretResult.OK; }, @intFromEnum(OpCode.OP_EQUAL) => { try self.push(Value.bool_val(self.pop().equals(self.pop()))); }, + @intFromEnum(OpCode.OP_DEFINE_GLOBAL) => { + const name = self.read_constant().as_string(); + + _ = self.globals.set(name, self.peek(0)); + _ = self.pop(); + }, + @intFromEnum(OpCode.OP_GET_GLOBAL) => { + const name: *Obj.String = self.read_constant().as_string(); + var value = Value.nil_val(); + + if (!self.globals.get(name, &value)) { + const err_msg = try std.fmt.allocPrint(self.allocator, "Undefined variable '{s}'.", .{name.chars}); + defer self.allocator.free(err_msg); + self.runtime_error(err_msg); + return InterpretResult.RUNTIME_ERROR; + } + + try self.push(value); + }, + @intFromEnum(OpCode.OP_SET_GLOBAL) => { + const name: *Obj.String = self.read_constant().as_string(); + + if (self.globals.set(name, self.peek(0))) { + _ = self.globals.del(name); + + const err_msg = try std.fmt.allocPrint(self.allocator, "Undefined variable '{s}'.", .{name.chars}); + defer self.allocator.free(err_msg); + + self.runtime_error(err_msg); + return InterpretResult.RUNTIME_ERROR; + } + }, else => { debug.print("Invalid instruction: {d}\n", .{instruction}); return InterpretResult.RUNTIME_ERROR;