implemeting local variables (ch22)

This commit is contained in:
Patrick MARIE 2024-08-27 10:23:07 +02:00
parent 8dcbd48e1c
commit 28f71342a9
10 changed files with 219 additions and 6 deletions

View File

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

View File

@ -0,0 +1,4 @@
{
var a = "first";
var a = "second";
}

View File

@ -0,0 +1,20 @@
// This is showing:
//
// global
// outer
// inner
// outer
// global
var a = "global";
{
print "in outer block: " + a;
var a = "outer";
{
print "in inner block: " + a;
var a = "inner";
print "in inner block: " + a;
}
print "in outer block: " + a;
}
print "global: " + a;

View File

@ -0,0 +1,8 @@
{
var a = "outer";
{
// this is reporting "Error at 'a': Can't read local variable in its own initializer."
var a = a;
print a;
}
}

View File

@ -97,6 +97,8 @@ pub const Chunk = struct {
@intFromEnum(OpCode.OP_DEFINE_GLOBAL) => return utils.constant_instruction("OP_DEFINE_GLOBAL", self, 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_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), @intFromEnum(OpCode.OP_SET_GLOBAL) => return utils.constant_instruction("OP_SET_GLOBAL", self, offset),
@intFromEnum(OpCode.OP_GET_LOCAL) => return utils.byte_instruction("OP_GET_LOCAL", self, offset),
@intFromEnum(OpCode.OP_SET_LOCAL) => return utils.byte_instruction("OP_SET_LOCAL", 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

@ -15,6 +15,8 @@ const VM = @import("./vm.zig").VM;
const ParsingError = @import("./errors.zig").ParsingError; const ParsingError = @import("./errors.zig").ParsingError;
const identifiers_equals = @import("./utils.zig").identifiers_equals;
const Precedence = enum { const Precedence = enum {
None, None,
Assignement, Assignement,
@ -36,6 +38,7 @@ const ParserRule = struct {
}; };
const Parser = struct { const Parser = struct {
compiler: *Compiler,
current: ?Token, current: ?Token,
previous: ?Token, previous: ?Token,
scanner: *Scanner, scanner: *Scanner,
@ -44,8 +47,9 @@ const Parser = struct {
chunk: *Chunk, chunk: *Chunk,
vm: *VM, vm: *VM,
fn new(vm: *VM, scanner: *Scanner, chunk: *Chunk) Parser { fn new(vm: *VM, compiler: *Compiler, scanner: *Scanner, chunk: *Chunk) Parser {
return Parser{ return Parser{
.compiler = compiler,
.current = null, .current = null,
.previous = null, .previous = null,
.scanner = scanner, .scanner = scanner,
@ -318,12 +322,26 @@ const Parser = struct {
} }
fn named_variable(self: *Parser, token: Token, can_assign: bool) ParsingError!void { fn named_variable(self: *Parser, token: Token, can_assign: bool) ParsingError!void {
const constant = try self.identifier_constant(token); var get_op: OpCode = OpCode.OP_GET_LOCAL;
var set_op: OpCode = OpCode.OP_SET_LOCAL;
var has_local = true;
var constant = self.resolve_local(token) catch blk: {
has_local = false;
break :blk 0;
};
if (!has_local) {
constant = try self.identifier_constant(token);
get_op = OpCode.OP_GET_GLOBAL;
set_op = OpCode.OP_SET_GLOBAL;
}
if (can_assign and self.match(TokenType.EQUAL)) { if (can_assign and self.match(TokenType.EQUAL)) {
try self.expression(); try self.expression();
try self.emit_bytes(@intFromEnum(OpCode.OP_SET_GLOBAL), constant); try self.emit_bytes(@intFromEnum(set_op), constant);
} else { } else {
try self.emit_bytes(@intFromEnum(OpCode.OP_GET_GLOBAL), constant); try self.emit_bytes(@intFromEnum(get_op), constant);
} }
} }
@ -342,6 +360,10 @@ const Parser = struct {
fn statement(self: *Parser) ParsingError!void { fn statement(self: *Parser) ParsingError!void {
if (self.match(TokenType.PRINT)) { if (self.match(TokenType.PRINT)) {
try self.print_statement(); try self.print_statement();
} else if (self.match(TokenType.LEFT_BRACE)) {
self.begin_scope();
try self.block();
try self.end_scope();
} else { } else {
try self.expression_statement(); try self.expression_statement();
} }
@ -410,6 +432,12 @@ const Parser = struct {
fn parse_variable(self: *Parser, err_msg: []const u8) ParsingError!u8 { fn parse_variable(self: *Parser, err_msg: []const u8) ParsingError!u8 {
self.consume(TokenType.IDENTIFIER, err_msg); self.consume(TokenType.IDENTIFIER, err_msg);
self.declare_variable();
if (self.compiler.scope_depth > 0) {
return 0;
}
return self.identifier_constant(self.previous.?); return self.identifier_constant(self.previous.?);
} }
@ -420,15 +448,138 @@ const Parser = struct {
} }
fn define_variable(self: *Parser, global: u8) ParsingError!void { fn define_variable(self: *Parser, global: u8) ParsingError!void {
if (self.compiler.scope_depth > 0) {
self.mark_initialized();
return;
}
return self.emit_bytes(@intFromEnum(OpCode.OP_DEFINE_GLOBAL), global); return self.emit_bytes(@intFromEnum(OpCode.OP_DEFINE_GLOBAL), global);
} }
fn mark_initialized(self: *Parser) void {
self.compiler.locals[self.compiler.local_count - 1].depth = self.compiler.scope_depth;
}
fn declare_variable(self: *Parser) void {
if (self.compiler.scope_depth == 0) {
return;
}
const name = self.previous.?;
if (self.compiler.local_count == 0) {
self.add_local(name);
return;
}
var idx = self.compiler.local_count - 1;
while (idx >= 0) {
const local = &self.compiler.locals[idx];
if (local.depth != null and local.depth.? < self.compiler.scope_depth) {
break;
}
if (identifiers_equals(name, local.name)) {
self.error_msg("Already a variable with this name in this scope.");
}
if (idx == 0) {
break;
}
idx -= 1;
}
self.add_local(name);
}
fn block(self: *Parser) ParsingError!void {
while (!self.check(TokenType.RIGHT_BRACE) and !self.check(TokenType.EOF)) {
try self.declaration();
}
self.consume(TokenType.RIGHT_BRACE, "Expect '}' after block.");
}
fn begin_scope(self: *Parser) void {
self.compiler.scope_depth += 1;
}
fn end_scope(self: *Parser) !void {
self.compiler.scope_depth -= 1;
while (self.compiler.local_count > 0 and self.compiler.locals[self.compiler.local_count - 1].depth.? > self.compiler.scope_depth) {
try self.emit_byte(@intFromEnum(OpCode.OP_POP));
self.compiler.local_count -= 1;
}
}
fn add_local(self: *Parser, token: Token) void {
if (self.compiler.local_count == 256) {
self.error_msg("Too many local variables in function.");
return;
}
var local = &self.compiler.locals[self.compiler.local_count];
self.compiler.local_count += 1;
local.name = token;
local.depth = null;
}
fn resolve_local(self: *Parser, name: Token) !u8 {
if (self.compiler.local_count == 0) {
return ParsingError.NotFound;
}
var idx: u8 = @intCast(self.compiler.local_count - 1);
while (idx >= 0) {
const local = &self.compiler.locals[idx];
if (identifiers_equals(local.name, name)) {
if (local.depth == null) {
self.error_msg("Can't read local variable in its own initializer.");
}
return idx;
}
if (idx == 0) {
break;
}
idx -= 1;
}
return ParsingError.NotFound;
}
};
const Compiler = struct {
locals: [256]Local,
local_count: usize,
scope_depth: usize,
fn new() Compiler {
return Compiler{
.locals = undefined,
.local_count = 0,
.scope_depth = 0,
};
}
};
const Local = struct {
name: Token,
depth: ?usize,
}; };
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 {
_ = allocator; _ = allocator;
var compiler = Compiler.new();
var scanner = Scanner.init(contents); var scanner = Scanner.init(contents);
var parser = Parser.new(vm, &scanner, chunk); var parser = Parser.new(vm, &compiler, &scanner, chunk);
parser.advance(); parser.advance();

View File

@ -1,5 +1,6 @@
pub const ParsingError = error{ pub const ParsingError = error{
FloatConv, FloatConv,
OutOfMemory, OutOfMemory,
NotFound,
Unknown, Unknown,
}; };

View File

@ -7,6 +7,8 @@ pub const OpCode = enum(u8) {
OP_GET_GLOBAL, OP_GET_GLOBAL,
OP_DEFINE_GLOBAL, OP_DEFINE_GLOBAL,
OP_SET_GLOBAL, OP_SET_GLOBAL,
OP_GET_LOCAL,
OP_SET_LOCAL,
OP_EQUAL, OP_EQUAL,
OP_GREATER, OP_GREATER,
OP_LESS, OP_LESS,

View File

@ -2,6 +2,8 @@ const std = @import("std");
const debug = std.debug; const debug = std.debug;
const Chunk = @import("./chunk.zig").Chunk; const Chunk = @import("./chunk.zig").Chunk;
const Token = @import("./scanner.zig").Token;
const print_value = @import("./values.zig").print_value; const print_value = @import("./values.zig").print_value;
pub fn grow_capacity(capacity: usize) usize { pub fn grow_capacity(capacity: usize) usize {
@ -25,6 +27,13 @@ pub fn constant_instruction(opcode_name: []const u8, chunk: Chunk, offset: usize
return offset + 2; return offset + 2;
} }
pub fn byte_instruction(opcode_name: []const u8, chunk: Chunk, offset: usize) usize {
const slot = chunk.code[offset + 1];
debug.print("{s:16} {d:4}\n", .{ opcode_name, slot });
return offset + 2;
}
pub fn compute_hash(str: []const u8) u32 { pub fn compute_hash(str: []const u8) u32 {
var res_hash: u32 = 2166136261; var res_hash: u32 = 2166136261;
@ -35,3 +44,11 @@ pub fn compute_hash(str: []const u8) u32 {
return res_hash; return res_hash;
} }
pub fn identifiers_equals(a: Token, b: Token) bool {
if (a.length != b.length) {
return false;
}
return std.mem.eql(u8, a.start[0..a.length], b.start[0..b.length]);
}

View File

@ -174,6 +174,14 @@ pub const VM = struct {
return InterpretResult.RUNTIME_ERROR; return InterpretResult.RUNTIME_ERROR;
} }
}, },
@intFromEnum(OpCode.OP_GET_LOCAL) => {
const slot = self.read_byte();
try self.push(self.stack.items[slot]);
},
@intFromEnum(OpCode.OP_SET_LOCAL) => {
const slot = self.read_byte();
self.stack.items[slot] = self.peek(0);
},
else => { else => {
debug.print("Invalid instruction: {d}\n", .{instruction}); debug.print("Invalid instruction: {d}\n", .{instruction});
return InterpretResult.RUNTIME_ERROR; return InterpretResult.RUNTIME_ERROR;