implementing global variables (ch21)
This commit is contained in:
parent
062d2e44de
commit
5fb7d361cf
@ -1,8 +1,8 @@
|
||||
build:
|
||||
zig build
|
||||
|
||||
run:
|
||||
zig build run
|
||||
run *ARGS:
|
||||
zig build run -- {{ARGS}}
|
||||
|
||||
test:
|
||||
zig build test
|
||||
|
@ -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
|
||||
|
5
samples/ch21_breakfast.lox
Normal file
5
samples/ch21_breakfast.lox
Normal file
@ -0,0 +1,5 @@
|
||||
var breakfast = "beignets";
|
||||
var beverage = "cafe au lait";
|
||||
breakfast = "beignets with " + beverage;
|
||||
|
||||
print breakfast;
|
@ -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;
|
||||
|
156
src/compile.zig
156
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;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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", .{});
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
40
src/vm.zig
40
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;
|
||||
|
Loading…
Reference in New Issue
Block a user