From 25ee586acb5f1d45ab53c9d35c48b8eb009dcf2f Mon Sep 17 00:00:00 2001 From: Luna Date: Mon, 3 Jun 2019 15:10:12 -0300 Subject: [PATCH 1/4] compiler: add local scope basics --- src/compiler.zig | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/compiler.zig b/src/compiler.zig index ac6030a..cd8eb85 100644 --- a/src/compiler.zig +++ b/src/compiler.zig @@ -113,6 +113,11 @@ var rules = []ParseRule{ ParseRule{}, }; +pub const Local = struct { + name: tokens.Token, + depth: i32, +}; + pub const Compiler = struct { src: []const u8, stdout: vm.StdOut, @@ -123,6 +128,10 @@ pub const Compiler = struct { debug_flag: bool = false, vmach: *vm.VM, + locals: [256]Local, + localCount: u8 = 0, + scopeDepth: u8 = 0, + pub fn init( allocator: *Allocator, chunk: *chunks.Chunk, @@ -139,6 +148,12 @@ pub const Compiler = struct { .parser = Parser{}, .debug_flag = debug_flag, .vmach = vmach, + + // local variable resolution + .locals = []Local{Local{ + .name = Token{}, + .depth = -1, + }} ** 256, }; } From 5138410be4e89a096b22ee4e68409760f2e115f8 Mon Sep 17 00:00:00 2001 From: Luna Date: Mon, 3 Jun 2019 15:17:07 -0300 Subject: [PATCH 2/4] compiler: add scope support --- src/compiler.zig | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/compiler.zig b/src/compiler.zig index cd8eb85..f468ee8 100644 --- a/src/compiler.zig +++ b/src/compiler.zig @@ -244,6 +244,14 @@ pub const Compiler = struct { } } + fn beginScope(self: *Compiler) void { + self.scopeDepth += 1; + } + + fn endScope(self: *Compiler) void { + self.scopeDepth -= 1; + } + fn grouping(self: *Compiler, canAssign: bool) !void { try self.expression(); try self.consume(.RIGHT_PAREN, "Expect ')' after expression."); @@ -456,7 +464,7 @@ pub const Compiler = struct { try self.defineVariable(global); } - fn declaration(self: *Compiler) !void { + fn declaration(self: *Compiler) anyerror!void { if (try self.match(.VAR)) { try self.varDecl(); } else { @@ -465,9 +473,21 @@ pub const Compiler = struct { if (self.parser.panicMode) try self.synchronize(); } + fn block(self: *Compiler) anyerror!void { + while (!self.check(.RIGHT_BRACE) and !self.check(.EOF)) { + try self.declaration(); + } + + try self.consume(.RIGHT_BRACE, "Expect '}' after block."); + } + fn statement(self: *Compiler) !void { if (try self.match(.PRINT)) { try self.printStmt(); + } else if (try self.match(.LEFT_BRACE)) { + self.beginScope(); + try self.block(); + self.endScope(); } else { try self.exprStmt(); } From 26d299cd2341e1af5ad2a3bfb6eab6abe0b98086 Mon Sep 17 00:00:00 2001 From: Luna Date: Mon, 3 Jun 2019 16:10:49 -0300 Subject: [PATCH 3/4] add local/global "switch" --- src/chunk.zig | 3 ++ src/compiler.zig | 99 +++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 84 insertions(+), 18 deletions(-) diff --git a/src/chunk.zig b/src/chunk.zig index 5ce7062..bcfc23e 100644 --- a/src/chunk.zig +++ b/src/chunk.zig @@ -35,6 +35,9 @@ const AllOpcodes = struct { pub GetGlobalLong: u8 = 20, pub SetGlobal: u8 = 21, pub SetGlobalLong: u8 = 22, + + pub GetLocal: u8 = 23, + pub SetLocal: u8 = 24, }; pub const OpCode = AllOpcodes{}; diff --git a/src/compiler.zig b/src/compiler.zig index f468ee8..7b51ca4 100644 --- a/src/compiler.zig +++ b/src/compiler.zig @@ -129,8 +129,8 @@ pub const Compiler = struct { vmach: *vm.VM, locals: [256]Local, - localCount: u8 = 0, - scopeDepth: u8 = 0, + localCount: i32 = 0, + scopeDepth: i32 = 0, pub fn init( allocator: *Allocator, @@ -248,8 +248,14 @@ pub const Compiler = struct { self.scopeDepth += 1; } - fn endScope(self: *Compiler) void { + fn endScope(self: *Compiler) !void { self.scopeDepth -= 1; + + // clear the current scope in the stack + while (self.localCount > 0 and self.locals[@intCast(usize, self.localCount - 1)].depth > self.scopeDepth) { + try self.emitByte(chunks.OpCode.Pop); + self.localCount -= 1; + } } fn grouping(self: *Compiler, canAssign: bool) !void { @@ -275,27 +281,45 @@ pub const Compiler = struct { ))); } + fn resolveLocal(self: *Compiler, name: *Token) i32 { + var i = self.localCount - 1; + while (i >= 0) : (i -= 1) { + var idx = @intCast(usize, i); + var local = &self.locals[idx]; + if (std.mem.eql(u8, name.lexeme, local.name.lexeme)) { + return i; + } + } + + return -1; + } + fn namedVariable(self: *Compiler, tok: *Token, canAssign: bool) !void { // writeConstant always writes OP_CODE which may be not // what we want, so. - var idx = try self.currentChunk().writeConstantRaw(values.ObjVal(try objects.copyString( - self.vmach, - tok.lexeme, - )), tok.line); + var getOp: u8 = undefined; + var setOp: u8 = undefined; + + // we try to resolve the local. depending if it gets resolved + // or not, we select the necessary get/set op codes. + var arg: i32 = self.resolveLocal(tok); + + if (arg != -1) { + getOp = chunks.OpCode.GetLocal; + setOp = chunks.OpCode.SetLocal; + } else { + arg = (try self.identifierConstant(tok)).Small; + getOp = chunks.OpCode.GetGlobal; + setOp = chunks.OpCode.SetGlobal; + } + + var idx: u8 = @intCast(u8, arg); if (canAssign and try self.match(.EQUAL)) { try self.expression(); - try self.emitConstWithIndex( - chunks.OpCode.SetGlobal, - chunks.OpCode.SetGlobalLong, - idx, - ); + try self.emitBytes(setOp, idx); } else { - try self.emitConstWithIndex( - chunks.OpCode.GetGlobal, - chunks.OpCode.GetGlobalLong, - idx, - ); + try self.emitBytes(getOp, idx); } } @@ -420,8 +444,43 @@ pub const Compiler = struct { )), token.line); } + fn addLocal(self: *Compiler, name: Token) void { + if (self.localCount == 256) { + self.errorCurrent("Too many variables in function."); + return; + } + + self.localCount += 1; + var local: *Local = &self.locals[@intCast(usize, self.localCount)]; + local.name = name; + local.depth = self.scopeDepth; + } + + fn declareVariable(self: *Compiler) void { + if (self.scopeDepth == 0) return; + var name: *Token = &self.parser.previous; + + // check if we're redeclaring an existing variable + // in the *CURRENT* scope. + + // go from current down to global + var i = self.localCount; + while (i >= 0) : (i -= 1) { + var local = self.locals[@intCast(usize, i)]; + if (local.depth == -1 and local.depth < self.scopeDepth) break; + + if (std.mem.eql(u8, name.lexeme, local.name.lexeme)) { + self.errorCurrent("Variable with this name already declared in this scope."); + } + } + + self.addLocal(name.*); + } + fn parseVariable(self: *Compiler, msg: []const u8) !chunks.ConstantIndex { try self.consume(.IDENTIFIER, msg); + self.declareVariable(); + if (self.scopeDepth > 0) return chunks.ConstantIndex{ .Small = 0 }; return try self.identifierConstant(&self.parser.previous); } @@ -444,6 +503,8 @@ pub const Compiler = struct { } fn defineVariable(self: *Compiler, global: chunks.ConstantIndex) !void { + if (self.scopeDepth > 0) return; + try self.emitConstWithIndex( chunks.OpCode.DefineGlobal, chunks.OpCode.DefineGlobalLong, @@ -460,6 +521,8 @@ pub const Compiler = struct { try self.emitByte(chunks.OpCode.Nil); } + // check scopeDepth here + try self.consume(.SEMICOLON, "Expect ';' after variable declaration."); try self.defineVariable(global); } @@ -487,7 +550,7 @@ pub const Compiler = struct { } else if (try self.match(.LEFT_BRACE)) { self.beginScope(); try self.block(); - self.endScope(); + try self.endScope(); } else { try self.exprStmt(); } From d7b78e09e33fcf0af491a321a906f489a28cc73c Mon Sep 17 00:00:00 2001 From: Luna Date: Mon, 3 Jun 2019 16:24:54 -0300 Subject: [PATCH 4/4] finish impl for local vars --- src/chunk.zig | 15 +++++++++++++++ src/compiler.zig | 17 +++++++++++++++-- src/vm.zig | 20 +++++++++----------- 3 files changed, 39 insertions(+), 13 deletions(-) diff --git a/src/chunk.zig b/src/chunk.zig index bcfc23e..a244bd2 100644 --- a/src/chunk.zig +++ b/src/chunk.zig @@ -88,6 +88,17 @@ fn constantLongInstruction( return offset + 4; } +fn byteInstruction( + stdout: var, + name: []const u8, + chunk: *Chunk, + index: usize, +) !usize { + var slot: u8 = chunk.code[index + 1]; + try stdout.print("{} {}", name, slot); + return index + 2; +} + pub const ConstantIndexTag = enum { Small, Long, @@ -254,6 +265,10 @@ pub const Chunk = struct { return try constantInstruction(stdout, "OP_SETGLOBAL", self, index); } else if (instruction == OpCode.SetGlobalLong) { return try constantLongInstruction(stdout, "OP_SETGLOBAL_LONG", self, index); + } else if (instruction == OpCode.GetLocal) { + return try byteInstruction(stdout, "OP_GETLOCAL", self, index); + } else if (instruction == OpCode.SetLocal) { + return try byteInstruction(stdout, "OP_GETLOCAL", self, index); } else { try stdout.print("Unknown opcode: {}\n", instruction); return index + 1; diff --git a/src/compiler.zig b/src/compiler.zig index 7b51ca4..398741b 100644 --- a/src/compiler.zig +++ b/src/compiler.zig @@ -287,6 +287,9 @@ pub const Compiler = struct { var idx = @intCast(usize, i); var local = &self.locals[idx]; if (std.mem.eql(u8, name.lexeme, local.name.lexeme)) { + if (local.depth == -1) { + self.errorCurrent("Cannot read local variable in its own initializer."); + } return i; } } @@ -453,7 +456,8 @@ pub const Compiler = struct { self.localCount += 1; var local: *Local = &self.locals[@intCast(usize, self.localCount)]; local.name = name; - local.depth = self.scopeDepth; + //local.depth = self.scopeDepth; + local.depth = -1; } fn declareVariable(self: *Compiler) void { @@ -502,8 +506,17 @@ pub const Compiler = struct { } } + fn markInitialized(self: *Compiler) void { + if (self.scopeDepth == 0) return; + var idx = @intCast(usize, self.localCount); + self.locals[idx].depth = self.scopeDepth; + } + fn defineVariable(self: *Compiler, global: chunks.ConstantIndex) !void { - if (self.scopeDepth > 0) return; + if (self.scopeDepth > 0) { + self.markInitialized(); + return; + } try self.emitConstWithIndex( chunks.OpCode.DefineGlobal, diff --git a/src/vm.zig b/src/vm.zig index d2b923d..b3e3942 100644 --- a/src/vm.zig +++ b/src/vm.zig @@ -294,26 +294,24 @@ pub const VM = struct { chunk.OpCode.Pop => blk: { _ = self.pop(); - break :blk; + }, + + chunk.OpCode.GetLocal => blk: { + var slot = self.readByte(); + try self.push(self.stack[slot]); + }, + chunk.OpCode.SetLocal => blk: { + var slot = self.readByte(); + self.stack[slot] = self.peek(0); }, chunk.OpCode.GetGlobal => blk: { try self.doGetGlobal(self.readString()); - break :blk; }, - chunk.OpCode.GetGlobalLong => blk: { - try self.doGetGlobal(self.readStringLong()); - break :blk; - }, - chunk.OpCode.SetGlobal => blk: { try self.doSetGlobal(self.readString()); break :blk; }, - chunk.OpCode.SetGlobalLong => blk: { - try self.doSetGlobal(self.readStringLong()); - break :blk; - }, // extracting the name is different depending of the // op code since one just uses a single byte, the other