implemeting local variables (ch22)
This commit is contained in:
parent
8dcbd48e1c
commit
28f71342a9
@ -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
|
||||||
|
4
samples/ch22_local_vars1.lox
Normal file
4
samples/ch22_local_vars1.lox
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
var a = "first";
|
||||||
|
var a = "second";
|
||||||
|
}
|
20
samples/ch22_local_vars2.lox
Normal file
20
samples/ch22_local_vars2.lox
Normal 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;
|
8
samples/ch22_local_vars3.lox
Normal file
8
samples/ch22_local_vars3.lox
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
161
src/compile.zig
161
src/compile.zig
@ -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();
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
pub const ParsingError = error{
|
pub const ParsingError = error{
|
||||||
FloatConv,
|
FloatConv,
|
||||||
OutOfMemory,
|
OutOfMemory,
|
||||||
|
NotFound,
|
||||||
Unknown,
|
Unknown,
|
||||||
};
|
};
|
||||||
|
@ -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,
|
||||||
|
@ -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]);
|
||||||
|
}
|
||||||
|
@ -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;
|
||||||
|
Loading…
Reference in New Issue
Block a user