implementing global variables (ch21)

This commit is contained in:
Patrick MARIE 2024-08-26 22:54:25 +02:00
parent 062d2e44de
commit 5fb7d361cf
10 changed files with 219 additions and 20 deletions

View File

@ -1,8 +1,8 @@
build: build:
zig build zig build
run: run *ARGS:
zig build run zig build run -- {{ARGS}}
test: test:
zig build test zig build test

View File

@ -11,7 +11,7 @@ While reading [Crafting Interpreters](https://craftinginterpreters.com/), after
- [x] 18 - Types of Values - [x] 18 - Types of Values
- [x] 19 - Strings - [x] 19 - Strings
- [x] 20 - Hash Tables - [x] 20 - Hash Tables
- [ ] 21 - Global Variables - [x] 21 - Global Variables
- [ ] 22 - Local Variables - [ ] 22 - Local Variables
- [ ] 23 - Jumping Back and Forth - [ ] 23 - Jumping Back and Forth
- [ ] 24 - Calls and Functions - [ ] 24 - Calls and Functions

View File

@ -0,0 +1,5 @@
var breakfast = "beignets";
var beverage = "cafe au lait";
breakfast = "beignets with " + beverage;
print breakfast;

View File

@ -92,6 +92,11 @@ pub const Chunk = struct {
@intFromEnum(OpCode.OP_EQUAL) => return utils.simple_instruction("OP_EQUAL", offset), @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_GREATER) => return utils.simple_instruction("OP_GREATER", offset),
@intFromEnum(OpCode.OP_LESS) => return utils.simple_instruction("OP_LESS", 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 => { else => {
debug.print("unknown opcode {d}\n", .{instruction}); debug.print("unknown opcode {d}\n", .{instruction});
return offset + 1; return offset + 1;

View File

@ -30,8 +30,8 @@ const Precedence = enum {
}; };
const ParserRule = struct { const ParserRule = struct {
prefix: ?*const fn (*Parser) ParsingError!void, prefix: ?*const fn (*Parser, bool) ParsingError!void,
infix: ?*const fn (*Parser) ParsingError!void, infix: ?*const fn (*Parser, bool) ParsingError!void,
precedence: Precedence, precedence: Precedence,
}; };
@ -134,7 +134,9 @@ const Parser = struct {
try self.emit_return(); 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 { const value = std.fmt.parseFloat(f64, self.previous.?.start[0..self.previous.?.length]) catch {
self.error_msg("Failed converting float."); self.error_msg("Failed converting float.");
return ParsingError.FloatConv; return ParsingError.FloatConv;
@ -162,12 +164,16 @@ const Parser = struct {
return @intCast(constant); return @intCast(constant);
} }
fn grouping(self: *Parser) ParsingError!void { fn grouping(self: *Parser, can_assign: bool) ParsingError!void {
_ = can_assign;
try self.expression(); try self.expression();
self.consume(TokenType.RIGHT_PAREN, "Expect ')' after 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; const operation_type = self.previous.?.token_type;
// Compile the operand // 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 operator_type = self.previous.?.token_type;
const parser_rule = Parser.get_rule(operator_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.GREATER_EQUAL => ParserRule{ .prefix = null, .infix = binary, .precedence = Precedence.Comparison },
TokenType.LESS => 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.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.STRING => ParserRule{ .prefix = string, .infix = null, .precedence = Precedence.None },
TokenType.NUMBER => ParserRule{ .prefix = number, .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 }, TokenType.AND => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
@ -268,16 +276,23 @@ const Parser = struct {
return; 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)) { while (@intFromEnum(precedence) <= @intFromEnum(Parser.get_rule(self.current.?.token_type).precedence)) {
self.advance(); self.advance();
const infix_rule = Parser.get_rule(self.previous.?.token_type).infix; 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) { try switch (self.previous.?.token_type) {
TokenType.NIL => self.emit_byte(@intFromEnum(OpCode.OP_NIL)), TokenType.NIL => self.emit_byte(@intFromEnum(OpCode.OP_NIL)),
TokenType.TRUE => self.emit_byte(@intFromEnum(OpCode.OP_TRUE)), 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]; const str = self.previous.?.start[1 .. self.previous.?.length - 1];
var string_obj = self.vm.copy_string(str); 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)); 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 { 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); var parser = Parser.new(vm, &scanner, chunk);
parser.advance(); 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(); try parser.end_parser();
return !parser.had_error; return !parser.had_error;

View File

@ -17,7 +17,11 @@ pub fn repl(allocator: Allocator, vm: *VM) !void {
const stdout = std.io.getStdOut().writer(); const stdout = std.io.getStdOut().writer();
while (true) { while (true) {
try stdout.print("> ", .{}); if (vm.has_tracing()) {
vm.globals.dump();
}
try stdout.print("zlox> ", .{});
@memset(&line, 0); @memset(&line, 0);

View File

@ -3,6 +3,10 @@ pub const OpCode = enum(u8) {
OP_NIL, OP_NIL,
OP_TRUE, OP_TRUE,
OP_FALSE, OP_FALSE,
OP_POP,
OP_GET_GLOBAL,
OP_DEFINE_GLOBAL,
OP_SET_GLOBAL,
OP_EQUAL, OP_EQUAL,
OP_GREATER, OP_GREATER,
OP_LESS, OP_LESS,
@ -12,5 +16,6 @@ pub const OpCode = enum(u8) {
OP_DIVIDE, OP_DIVIDE,
OP_NOT, OP_NOT,
OP_NEGATE, OP_NEGATE,
OP_PRINT,
OP_RETURN, OP_RETURN,
}; };

View File

@ -123,7 +123,7 @@ pub const Table = struct {
for (self.entries, 0..) |entry, idx| { for (self.entries, 0..) |entry, idx| {
if (entry.key != null) { if (entry.key != null) {
std.debug.print("{d} ({d}) - {s}: ", .{ idx, entry.key.?.hash, entry.key.?.chars }); std.debug.print("{d} ({d}) - {s}: ", .{ idx, entry.key.?.hash, entry.key.?.chars });
entry.value.print(); entry.value.type_print();
std.debug.print("\n", .{}); std.debug.print("\n", .{});
} }

View File

@ -70,7 +70,7 @@ pub const Value = struct {
} }
pub fn as_string(self: Value) *Obj.String { 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; return obj;
} }
@ -126,6 +126,18 @@ pub const Value = struct {
ValueType.Obj => self.as_obj().print(), 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 { pub const ValueArray = struct {

View File

@ -32,6 +32,7 @@ pub const VM = struct {
// In the book, a linked list between objects is used to handle this. // In the book, a linked list between objects is used to handle this.
references: std.ArrayList(*Obj), references: std.ArrayList(*Obj),
strings: Table, strings: Table,
globals: Table,
tracing: bool, tracing: bool,
pub fn new(allocator: Allocator) VM { pub fn new(allocator: Allocator) VM {
@ -42,6 +43,7 @@ pub const VM = struct {
.stack = std.ArrayList(Value).init(allocator), .stack = std.ArrayList(Value).init(allocator),
.references = std.ArrayList(*Obj).init(allocator), .references = std.ArrayList(*Obj).init(allocator),
.strings = Table.new(allocator), .strings = Table.new(allocator),
.globals = Table.new(allocator),
.tracing = false, .tracing = false,
}; };
} }
@ -54,6 +56,7 @@ pub const VM = struct {
} }
self.strings.deinit(); self.strings.deinit();
self.globals.deinit();
self.clean_references(); self.clean_references();
self.references.deinit(); self.references.deinit();
} }
@ -106,6 +109,7 @@ pub const VM = struct {
@intFromEnum(OpCode.OP_NIL) => try self.push(Value.nil_val()), @intFromEnum(OpCode.OP_NIL) => try self.push(Value.nil_val()),
@intFromEnum(OpCode.OP_FALSE) => try self.push(Value.bool_val(false)), @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_TRUE) => try self.push(Value.bool_val(true)),
@intFromEnum(OpCode.OP_POP) => _ = self.pop(),
@intFromEnum(OpCode.OP_ADD), @intFromEnum(OpCode.OP_ADD),
@intFromEnum(OpCode.OP_SUBSTRACT), @intFromEnum(OpCode.OP_SUBSTRACT),
@intFromEnum(OpCode.OP_MULTIPLY), @intFromEnum(OpCode.OP_MULTIPLY),
@ -128,14 +132,48 @@ pub const VM = struct {
} }
try self.push(Value.number_val(-self.pop().as_number())); try self.push(Value.number_val(-self.pop().as_number()));
}, },
@intFromEnum(OpCode.OP_RETURN) => { @intFromEnum(OpCode.OP_PRINT) => {
print_value(self.pop()); print_value(self.pop());
debug.print("\n", .{}); debug.print("\n", .{});
},
@intFromEnum(OpCode.OP_RETURN) => {
return InterpretResult.OK; return InterpretResult.OK;
}, },
@intFromEnum(OpCode.OP_EQUAL) => { @intFromEnum(OpCode.OP_EQUAL) => {
try self.push(Value.bool_val(self.pop().equals(self.pop()))); 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 => { else => {
debug.print("Invalid instruction: {d}\n", .{instruction}); debug.print("Invalid instruction: {d}\n", .{instruction});
return InterpretResult.RUNTIME_ERROR; return InterpretResult.RUNTIME_ERROR;