diff --git a/src/chunk.zig b/src/chunk.zig index 5ce7062..a244bd2 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{}; @@ -85,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, @@ -251,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 ac6030a..398741b 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: i32 = 0, + scopeDepth: i32 = 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, }; } @@ -229,6 +244,20 @@ pub const Compiler = struct { } } + fn beginScope(self: *Compiler) void { + self.scopeDepth += 1; + } + + 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 { try self.expression(); try self.consume(.RIGHT_PAREN, "Expect ')' after expression."); @@ -252,27 +281,48 @@ 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)) { + if (local.depth == -1) { + self.errorCurrent("Cannot read local variable in its own initializer."); + } + 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); } } @@ -397,8 +447,44 @@ 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; + local.depth = -1; + } + + 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); } @@ -420,7 +506,18 @@ 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) { + self.markInitialized(); + return; + } + try self.emitConstWithIndex( chunks.OpCode.DefineGlobal, chunks.OpCode.DefineGlobalLong, @@ -437,11 +534,13 @@ 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); } - fn declaration(self: *Compiler) !void { + fn declaration(self: *Compiler) anyerror!void { if (try self.match(.VAR)) { try self.varDecl(); } else { @@ -450,9 +549,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(); + try self.endScope(); } else { try self.exprStmt(); } 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