DzLox

:)
git clone https://git.sr.ht/~ashymad/DzLox
Log | Files | Refs | Submodules | LICENSE

commit 8ccf4e893ff6704a047a0810818ca4f80f0d3552
parent 19e17da6516be732014392872f3ae07ab5d781f4
Author: Szymon Mikulicz <szymon.mikulicz@aptiv.com>
Date:   Sat, 27 Jul 2024 00:25:58 +0200

Local variables finished

Diffstat:
Mzlox/src/chunk.zig | 2++
Mzlox/src/compiler.zig | 794++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
Mzlox/src/debug.zig | 7+++++++
Mzlox/src/scanner.zig | 2++
Mzlox/src/vm.zig | 12++++++++++--
5 files changed, 485 insertions(+), 332 deletions(-)

diff --git a/zlox/src/chunk.zig b/zlox/src/chunk.zig @@ -24,6 +24,8 @@ pub const OP = enum(u8) { DEFINE_GLOBAL, GET_GLOBAL, SET_GLOBAL, + GET_LOCAL, + SET_LOCAL, }; pub const Chunk = struct { diff --git a/zlox/src/compiler.zig b/zlox/src/compiler.zig @@ -32,413 +32,547 @@ const Precedence = enum { } }; -pub const Compiler = struct { - current: scanner.Token, - previous: scanner.Token, - scanner: scanner.Scanner, - lastError: CompilerError, - hadError: bool, - panicMode: bool, - compilingChunk: Chunk, - allocator: std.mem.Allocator, - objects: *Obj.List, - - const Self = @This(); - - const ParseFn = *const fn (*Self, bool) void; - - const ParseRule = struct { - prefix: ?ParseFn, - infix: ?ParseFn, - precedence: Precedence, - pub fn init(prefix: ?ParseFn, infix: ?ParseFn, precedence: Precedence) @This() { - return @This(){ .prefix = prefix, .infix = infix, .precedence = precedence }; - } - }; - - const rules = init: { - var new: [@typeInfo(Token).Enum.fields.len]ParseRule = undefined; - for (&new, 0..) |*v, i| { - const T = Token; - const S = Self; - const R = ParseRule.init; - const P = Precedence; - const tok: Token = @enumFromInt(i); - v.* = switch (tok) { - // zig fmt: off - T.LEFT_PAREN => R(S.grouping, null, P.NONE ), - T.RIGHT_PAREN => R(null, null, P.NONE ), - T.LEFT_BRACE => R(null, null, P.NONE ), - T.RIGHT_BRACE => R(null, null, P.NONE ), - T.COMMA => R(null, null, P.NONE ), - T.DOT => R(null, null, P.NONE ), - T.MINUS => R(S.unary, S.binary, P.TERM ), - T.PLUS => R(null, S.binary, P.TERM ), - T.COLON => R(null, null, P.NONE ), - T.SEMICOLON => R(null, null, P.NONE ), - T.SLASH => R(null, S.binary, P.FACTOR ), - T.STAR => R(null, S.binary, P.FACTOR ), - T.QUESTION => R(null, S.ternary, P.TERNARY ), - T.BANG => R(S.unary, null, P.NONE ), - T.BANG_EQUAL => R(null, S.binary, P.EQUALITY ), - T.EQUAL => R(null, null, P.NONE ), - T.EQUAL_EQUAL => R(null, S.binary, P.EQUALITY ), - T.GREATER => R(null, S.binary, P.COMPARISON ), - T.GREATER_EQUAL => R(null, S.binary, P.COMPARISON ), - T.LESS => R(null, S.binary, P.COMPARISON ), - T.LESS_EQUAL => R(null, S.binary, P.COMPARISON ), - T.IDENTIFIER => R(S.variable, null, P.NONE ), - T.STRING => R(S.string, null, P.NONE ), - T.NUMBER => R(S.number, null, P.NONE ), - T.AND => R(null, null, P.NONE ), - T.CLASS => R(null, null, P.NONE ), - T.ELSE => R(null, null, P.NONE ), - T.FALSE => R(S.literal, null, P.NONE ), - T.FOR => R(null, null, P.NONE ), - T.FUN => R(null, null, P.NONE ), - T.IF => R(null, null, P.NONE ), - T.NIL => R(S.literal, null, P.NONE ), - T.OR => R(null, null, P.NONE ), - T.PRINT => R(null, null, P.NONE ), - T.RETURN => R(null, null, P.NONE ), - T.SUPER => R(null, null, P.NONE ), - T.THIS => R(null, null, P.NONE ), - T.TRUE => R(S.literal, null, P.NONE ), - T.VAR => R(null, null, P.NONE ), - T.WHILE => R(null, null, P.NONE ), - T.EOF => R(null, null, P.NONE ), - // zig fmt: on - }; - } - break :init new; - }; - fn getRule(tok: Token) *const ParseRule { - return &Compiler.rules[@intFromEnum(tok)]; - } +pub fn Compiler(size: comptime_int) type { + return struct { + current: scanner.Token, + previous: scanner.Token, + scanner: scanner.Scanner, + lastError: CompilerError, + hadError: bool, + panicMode: bool, + compilingChunk: Chunk, + allocator: std.mem.Allocator, + objects: *Obj.List, + locals: [size]Local, + localCount: usize, + scopeDepth: usize, + + const Self = @This(); + + const Local = struct { + name: scanner.Token, + depth: ?usize, + con: bool, + }; - fn advance(self: *Self) void { - self.previous = self.current; + const ParseFn = *const fn (*Self, bool) void; - while (true) { - self.current = self.scanner.scanToken(); + const ParseRule = struct { + prefix: ?ParseFn, + infix: ?ParseFn, + precedence: Precedence, + pub fn init(prefix: ?ParseFn, infix: ?ParseFn, precedence: Precedence) @This() { + return @This(){ .prefix = prefix, .infix = infix, .precedence = precedence }; + } + }; - if (self.current.type) |_| { - break; - } else |err| { - self.lastError = err; - self.errorAtCurrent(scanner.ScannerErrorString(err)); + const rules = init: { + var new: [@typeInfo(Token).Enum.fields.len]ParseRule = undefined; + for (&new, 0..) |*v, i| { + const T = Token; + const S = Self; + const R = ParseRule.init; + const P = Precedence; + const tok: Token = @enumFromInt(i); + v.* = switch (tok) { + // zig fmt: off + T.LEFT_PAREN => R(S.grouping, null, P.NONE ), + T.RIGHT_PAREN => R(null, null, P.NONE ), + T.LEFT_BRACE => R(null, null, P.NONE ), + T.RIGHT_BRACE => R(null, null, P.NONE ), + T.COMMA => R(null, null, P.NONE ), + T.DOT => R(null, null, P.NONE ), + T.MINUS => R(S.unary, S.binary, P.TERM ), + T.PLUS => R(null, S.binary, P.TERM ), + T.COLON => R(null, null, P.NONE ), + T.SEMICOLON => R(null, null, P.NONE ), + T.SLASH => R(null, S.binary, P.FACTOR ), + T.STAR => R(null, S.binary, P.FACTOR ), + T.QUESTION => R(null, S.ternary, P.TERNARY ), + T.BANG => R(S.unary, null, P.NONE ), + T.BANG_EQUAL => R(null, S.binary, P.EQUALITY ), + T.EQUAL => R(null, null, P.NONE ), + T.EQUAL_EQUAL => R(null, S.binary, P.EQUALITY ), + T.GREATER => R(null, S.binary, P.COMPARISON ), + T.GREATER_EQUAL => R(null, S.binary, P.COMPARISON ), + T.LESS => R(null, S.binary, P.COMPARISON ), + T.LESS_EQUAL => R(null, S.binary, P.COMPARISON ), + T.IDENTIFIER => R(S.variable, null, P.NONE ), + T.STRING => R(S.string, null, P.NONE ), + T.NUMBER => R(S.number, null, P.NONE ), + T.AND => R(null, null, P.NONE ), + T.CLASS => R(null, null, P.NONE ), + T.ELSE => R(null, null, P.NONE ), + T.FALSE => R(S.literal, null, P.NONE ), + T.FOR => R(null, null, P.NONE ), + T.FUN => R(null, null, P.NONE ), + T.IF => R(null, null, P.NONE ), + T.NIL => R(S.literal, null, P.NONE ), + T.OR => R(null, null, P.NONE ), + T.PRINT => R(null, null, P.NONE ), + T.RETURN => R(null, null, P.NONE ), + T.SUPER => R(null, null, P.NONE ), + T.THIS => R(null, null, P.NONE ), + T.TRUE => R(S.literal, null, P.NONE ), + T.VAR => R(null, null, P.NONE ), + T.CON => R(null, null, P.NONE ), + T.WHILE => R(null, null, P.NONE ), + T.EOF => R(null, null, P.NONE ), + // zig fmt: on + }; } + break :init new; + }; + + fn getRule(tok: Token) *const ParseRule { + return &Self.rules[@intFromEnum(tok)]; } - } - fn currentChunk(self: *Self) *Chunk { - return &self.compilingChunk; - } + fn advance(self: *Self) void { + self.previous = self.current; - fn emitByte(self: *Self, byte: u8) void { - self.currentChunk().write(byte, self.previous.line) catch |err| { - self.lastError = err; - self.errorAtCurrent("Out of Memory"); - }; - } + while (true) { + self.current = self.scanner.scanToken(); - fn emitOP(self: *Self, op: OP) void { - self.currentChunk().writeOP(op, self.previous.line) catch |err| { - self.lastError = err; - self.errorAtCurrent("Out of Memory"); - }; - } + if (self.current.type) |_| { + break; + } else |err| { + self.lastError = err; + self.errorAtCurrent(scanner.ScannerErrorString(err)); + } + } + } - fn emit(self: *Self, op: OP, byte: u8) void { - self.emitOP(op); - self.emitByte(byte); - } + fn currentChunk(self: *Self) *Chunk { + return &self.compilingChunk; + } - fn emit2OP(self: *Self, op: OP, op2: OP) void { - self.emitOP(op); - self.emitOP(op2); - } + fn emitByte(self: *Self, byte: u8) void { + self.currentChunk().write(byte, self.previous.line) catch |err| { + self.lastError = err; + self.errorAtCurrent("Out of Memory"); + }; + } - fn endCompiler(self: *Self) void { - self.emitReturn(); - } + fn emitOP(self: *Self, op: OP) void { + self.currentChunk().writeOP(op, self.previous.line) catch |err| { + self.lastError = err; + self.errorAtCurrent("Out of Memory"); + }; + } - fn emitReturn(self: *Self) void { - self.emitOP(OP.RETURN); - } + fn emit(self: *Self, op: OP, byte: u8) void { + self.emitOP(op); + self.emitByte(byte); + } - fn errorAtCurrent(self: *Self, message: []const u8) void { - self.errorAt(self.current, message); - } + fn emit2OP(self: *Self, op: OP, op2: OP) void { + self.emitOP(op); + self.emitOP(op2); + } - fn errorAtPrevious(self: *Self, message: []const u8) void { - self.errorAt(self.previous, message); - } + fn endCompiler(self: *Self) void { + self.emitReturn(); + } - fn expression(self: *Self) void { - self.parsePrecedence(Precedence.ASSIGNMENT); - } + fn emitReturn(self: *Self) void { + self.emitOP(OP.RETURN); + } - fn parsePrecedence(self: *Self, precedence: Precedence) void { - const canAssign = precedence.lessOrEq(Precedence.ASSIGNMENT); + fn errorAtCurrent(self: *Self, message: []const u8) void { + self.errorAt(self.current, message); + } - self.advance(); - if (getRule(self.previous.type catch unreachable).prefix) |prefixRule| { - prefixRule(self, canAssign); - } else { - self.lastError = CompilerError.NotAnExpression; - self.errorAtPrevious("Expect expression."); - return; + fn errorAtPrevious(self: *Self, message: []const u8) void { + self.errorAt(self.previous, message); } - while (precedence.lessOrEq(getRule(self.current.type catch unreachable).precedence)) { + fn expression(self: *Self) void { + self.parsePrecedence(Precedence.ASSIGNMENT); + } + + fn parsePrecedence(self: *Self, precedence: Precedence) void { + const canAssign = precedence.lessOrEq(Precedence.ASSIGNMENT); + self.advance(); - if (getRule(self.previous.type catch unreachable).infix) |infixRule| { - infixRule(self, canAssign); + if (getRule(self.previous.type catch unreachable).prefix) |prefixRule| { + prefixRule(self, canAssign); } else { self.lastError = CompilerError.NotAnExpression; self.errorAtPrevious("Expect expression."); return; } + + while (precedence.lessOrEq(getRule(self.current.type catch unreachable).precedence)) { + self.advance(); + if (getRule(self.previous.type catch unreachable).infix) |infixRule| { + infixRule(self, canAssign); + } else { + self.lastError = CompilerError.NotAnExpression; + self.errorAtPrevious("Expect expression."); + return; + } + } + + if (canAssign and self.match(Token.EQUAL)) { + self.errorAtPrevious("Invalid assignment target."); + } } - if (canAssign and self.match(Token.EQUAL)) { - self.errorAtPrevious("Invalid assignment target."); + fn errorAt(self: *Self, token: scanner.Token, message: []const u8) void { + if (self.panicMode) return; + self.panicMode = true; + std.debug.print("[{d}:{d}] Error", .{ token.line, token.column }); + if (token.type) |tpe| { + if (tpe == Token.EOF) { + std.debug.print(" at end", .{}); + } else { + std.debug.print(" at {s}", .{token.lexeme}); + } + } else |_| {} + std.debug.print(": {s}\n", .{message}); + self.hadError = true; } - } - fn errorAt(self: *Self, token: scanner.Token, message: []const u8) void { - if (self.panicMode) return; - self.panicMode = true; - std.debug.print("[{d}:{d}] Error", .{ token.line, token.column }); - if (token.type) |tpe| { - if (tpe == Token.EOF) { - std.debug.print(" at end", .{}); - } else { - std.debug.print(" at {s}", .{token.lexeme}); - } - } else |_| {} - std.debug.print(": {s}\n", .{message}); - self.hadError = true; - } + fn consume(self: *Self, tok: Token, message: []const u8) void { + if (self.current.type) |tpe| { + if (tpe == tok) { + self.advance(); + return; + } + } else |_| {} + self.lastError = CompilerError.UnexpectedToken; + self.errorAtCurrent(message); + } - fn consume(self: *Self, tok: Token, message: []const u8) void { - if (self.current.type) |tpe| { - if (tpe == tok) { - self.advance(); + fn number(self: *Self, _: bool) void { + self.emitConstant(Value.parseNumber(self.previous.lexeme) catch |err| { + self.lastError = err; + self.errorAtPrevious("Invalid numeric literal"); + return; + }); + } + + fn string(self: *Self, _: bool) void { + self.emitConstant(Value.init(self.objects.emplace(.String, &.{self.previous.lexeme[1 .. self.previous.lexeme.len - 1]}) catch |err| { + self.lastError = err; + self.errorAtPrevious("Couldn't allocate object"); return; + })); + } + + fn variable(self: *Self, canAssign: bool) void { + self.namedVariable(self.previous, canAssign); + } + + fn namedVariable(self: *Self, tok: scanner.Token, canAssign: bool) void { + var getOP = OP.GET_LOCAL; + var setOP = OP.SET_LOCAL; + const arg = self.resolveLocal(tok) catch blk: { + getOP = OP.GET_GLOBAL; + setOP = OP.SET_GLOBAL; + break :blk self.identifierConstant(tok) catch return; + }; + + if (canAssign and self.match(Token.EQUAL)) { + if(setOP == OP.SET_LOCAL and self.locals[arg].con) { + self.errorAtPrevious("Cannot assign to a constant"); + return; + } + self.expression(); + self.emit(setOP, arg); + } else { + self.emit(getOP, arg); } - } else |_| {} - self.lastError = CompilerError.UnexpectedToken; - self.errorAtCurrent(message); - } + } - fn number(self: *Self, _: bool) void { - self.emitConstant(Value.parseNumber(self.previous.lexeme) catch |err| { - self.lastError = err; - self.errorAtPrevious("Invalid numeric literal"); - return; - }); - } + fn resolveLocal(self: *Self, name: scanner.Token) !u8 { + var i = self.localCount; + while (i > 0) : (i -= 1) { + if(self.locals[i-1].depth) |_| { + if (identifiersEql(self.locals[i-1].name, name)) { + return @intCast(i-1); + } + } else { + self.errorAt(name, "Can't read local variable in it's own initializer"); + } + } + return error.NotFound; + } - fn string(self: *Self, _: bool) void { - self.emitConstant(Value.init(self.objects.emplace(.String, &.{self.previous.lexeme[1 .. self.previous.lexeme.len - 1]}) catch |err| { - self.lastError = err; - self.errorAtPrevious("Couldn't allocate object"); - return; - })); - } + fn emitConstant(self: *Self, val: Value) void { + self.emit(OP.CONSTANT, self.makeConstant(val)); + } - fn variable(self: *Self, canAssign: bool) void { - self.namedVariable(self.previous, canAssign); - } + fn makeConstant(self: *Self, val: Value) u8 { + return self.currentChunk().addConstant(val) catch |err| { + self.lastError = err; + self.errorAtPrevious("Too many constants in one chunk"); + return 0; + }; + } - fn namedVariable(self: *Self, tok: scanner.Token, canAssign: bool) void { - const arg = self.identifierConstant(tok) catch return; - if (canAssign and self.match(Token.EQUAL)) { + fn grouping(self: *Self, _: bool) void { self.expression(); - self.emit(OP.SET_GLOBAL, arg); - } else { - self.emit(OP.GET_GLOBAL, arg); + self.consume(Token.RIGHT_PAREN, "Expected ')' after expression"); } - } - fn emitConstant(self: *Self, val: Value) void { - self.emit(OP.CONSTANT, self.makeConstant(val)); - } + fn unary(self: *Self, _: bool) void { + const operatorType = self.previous.type catch unreachable; - fn makeConstant(self: *Self, val: Value) u8 { - return self.currentChunk().addConstant(val) catch |err| { - self.lastError = err; - self.errorAtPrevious("Too many constants in one chunk"); - return 0; - }; - } + self.parsePrecedence(Precedence.UNARY); - fn grouping(self: *Self, _: bool) void { - self.expression(); - self.consume(Token.RIGHT_PAREN, "Expected ')' after expression"); - } + switch (operatorType) { + Token.MINUS => self.emitOP(OP.NEGATE), + Token.BANG => self.emitOP(OP.NOT), + else => unreachable, + } + } + + fn literal(self: *Self, _: bool) void { + switch (self.previous.type catch unreachable) { + Token.FALSE => self.emitOP(OP.FALSE), + Token.TRUE => self.emitOP(OP.TRUE), + Token.NIL => self.emitOP(OP.NIL), + else => unreachable, + } + } + + fn binary(self: *Self, _: bool) void { + const operatorType = self.previous.type catch unreachable; + self.parsePrecedence(getRule(operatorType).precedence.inc()); + + switch (operatorType) { + Token.PLUS => self.emitOP(OP.ADD), + Token.MINUS => self.emitOP(OP.SUBTRACT), + Token.STAR => self.emitOP(OP.MULTIPLY), + Token.SLASH => self.emitOP(OP.DIVIDE), + Token.BANG_EQUAL => self.emit2OP(OP.EQUAL, OP.NOT), + Token.EQUAL_EQUAL => self.emitOP(OP.EQUAL), + Token.GREATER => self.emitOP(OP.GREATER), + Token.GREATER_EQUAL => self.emit2OP(OP.LESS, OP.NOT), + Token.LESS => self.emitOP(OP.LESS), + Token.LESS_EQUAL => self.emit2OP(OP.GREATER, OP.NOT), + else => unreachable, + } + } + + fn ternary(self: *Self, _: bool) void { + const operatorType = self.previous.type catch unreachable; + self.parsePrecedence(getRule(operatorType).precedence.inc()); - fn unary(self: *Self, _: bool) void { - const operatorType = self.previous.type catch unreachable; + // emit bytecode - self.parsePrecedence(Precedence.UNARY); + self.consume(Token.COLON, "Expected ':' in ternary expression."); - switch (operatorType) { - Token.MINUS => self.emitOP(OP.NEGATE), - Token.BANG => self.emitOP(OP.NOT), - else => unreachable, + self.parsePrecedence(getRule(operatorType).precedence.inc()); + + // emit bytecode } - } - fn literal(self: *Self, _: bool) void { - switch (self.previous.type catch unreachable) { - Token.FALSE => self.emitOP(OP.FALSE), - Token.TRUE => self.emitOP(OP.TRUE), - Token.NIL => self.emitOP(OP.NIL), - else => unreachable, + fn declaration(self: *Self) void { + if (self.match(Token.VAR)) { + self.varDeclaration(); + } else if (self.match(Token.CON)) { + self.conDeclaration(); + } else { + self.statement(); + } + + if (self.panicMode) self.synchronize(); } - } - fn binary(self: *Self, _: bool) void { - const operatorType = self.previous.type catch unreachable; - self.parsePrecedence(getRule(operatorType).precedence.inc()); - - switch (operatorType) { - Token.PLUS => self.emitOP(OP.ADD), - Token.MINUS => self.emitOP(OP.SUBTRACT), - Token.STAR => self.emitOP(OP.MULTIPLY), - Token.SLASH => self.emitOP(OP.DIVIDE), - Token.BANG_EQUAL => self.emit2OP(OP.EQUAL, OP.NOT), - Token.EQUAL_EQUAL => self.emitOP(OP.EQUAL), - Token.GREATER => self.emitOP(OP.GREATER), - Token.GREATER_EQUAL => self.emit2OP(OP.LESS, OP.NOT), - Token.LESS => self.emitOP(OP.LESS), - Token.LESS_EQUAL => self.emit2OP(OP.GREATER, OP.NOT), - else => unreachable, + fn varDeclaration(self: *Self) void { + const global = self.parseVariable("Expect variable name.", false) catch return; + + if (self.match(Token.EQUAL)) { + self.expression(); + } else { + self.emitOP(OP.NIL); + } + + self.consume(Token.SEMICOLON, "Expect ';' after variable declaration."); + + self.defineVariable(global); } - } - fn ternary(self: *Self, _: bool) void { - const operatorType = self.previous.type catch unreachable; - self.parsePrecedence(getRule(operatorType).precedence.inc()); + fn conDeclaration(self: *Self) void { + if (self.scopeDepth == 0) { + self.errorAtPrevious("Global constans are not supported"); + return; + } - // emit bytecode + const global = self.parseVariable("Expect variable name.", true) catch return; - self.consume(Token.COLON, "Expected ':' in ternary expression."); + self.consume(Token.EQUAL, "Constant variable has to be initialized."); - self.parsePrecedence(getRule(operatorType).precedence.inc()); + self.expression(); - // emit bytecode - } + self.consume(Token.SEMICOLON, "Expect ';' after variable declaration."); - fn declaration(self: *Self) void { - if (self.match(Token.VAR)) { - self.varDeclaration(); - } else { - self.statement(); + self.defineVariable(global); } - if (self.panicMode) self.synchronize(); - } + fn parseVariable(self: *Self, errorMessage: []const u8, con: bool) !u8 { + self.consume(Token.IDENTIFIER, errorMessage); - fn varDeclaration(self: *Self) void { - const global = self.parseVariable("Expect variable name.") catch return; + self.declareVariable(con); + if(self.scopeDepth > 0) return 0; - if (self.match(Token.EQUAL)) { - self.expression(); - } else { - self.emitOP(OP.NIL); + return self.identifierConstant(self.previous); } - self.consume(Token.SEMICOLON, "Expect ';' after variable declaration."); + fn identifierConstant(self: *Self, tok: scanner.Token) !u8 { + return self.makeConstant(Value.init(self.objects.emplace(.String, &.{tok.lexeme}) catch |err| { + self.lastError = err; + self.errorAtPrevious("Couldn't allocate identifier"); + return err; + })); + } - self.defineVariable(global); - } + fn declareVariable(self: *Self, con: bool) void { + if (self.scopeDepth == 0) return; - fn parseVariable(self: *Self, errorMessage: []const u8) !u8 { - self.consume(Token.IDENTIFIER, errorMessage); - return self.identifierConstant(self.previous); - } + var i = self.localCount; + while (i > 0) : (i -= 1) { + const local = self.locals[i-1]; + if (local.depth) |depth| { + if (depth < self.scopeDepth) break; + } - fn identifierConstant(self: *Self, tok: scanner.Token) !u8 { - return self.makeConstant(Value.init(self.objects.emplace(.String, &.{tok.lexeme}) catch |err| { - self.lastError = err; - self.errorAtPrevious("Couldn't allocate identifier"); - return err; - })); - } + if (identifiersEql(local.name, self.previous)) { + self.errorAtPrevious("Already a variable with this name in this scope."); + } + } - fn defineVariable(self: *Self, global: u8) void { - self.emit(OP.DEFINE_GLOBAL, global); - } + self.addLocal(self.previous, con); + } - fn synchronize(self: *Self) void { - self.panicMode = false; + fn identifiersEql(a: scanner.Token, b: scanner.Token) bool { + return std.mem.eql(u8, a.lexeme, b.lexeme); + } - while ((self.current.type catch Token.NIL) != Token.EOF) { - if ((self.previous.type catch Token.NIL) == Token.SEMICOLON) return; - switch (self.current.type catch Token.NIL) { - Token.CLASS, Token.FUN, Token.VAR, Token.IF, Token.FOR, Token.WHILE, Token.PRINT, Token.RETURN => return, - else => self.advance(), + fn addLocal(self: *Self, name: scanner.Token, con: bool) void { + if (self.localCount == size) { + self.errorAt(name, "Too many variables in function"); + return; } + self.locals[self.localCount] = Local {.name = name, .depth = null, .con = con}; + self.localCount += 1; } - } - fn statement(self: *Self) void { - if (self.match(Token.PRINT)) { - self.printStatement(); - } else { - self.expressionStatement(); + fn markInitialized(self: *Self) void { + self.locals[self.localCount-1].depth = self.scopeDepth; } - } - fn expressionStatement(self: *Self) void { - self.expression(); - self.consume(Token.SEMICOLON, "Expect ';' after expression."); - self.emitOP(OP.POP); - } + fn defineVariable(self: *Self, global: u8) void { + if (self.scopeDepth > 0){ + self.markInitialized(); + return; + } - fn match(self: *Self, token: Token) bool { - if (!self.check(token)) return false; - self.advance(); - return true; - } + self.emit(OP.DEFINE_GLOBAL, global); + } - fn check(self: *const Self, token: Token) bool { - return if (self.current.type) |tp| tp == token else |_| false; - } + fn synchronize(self: *Self) void { + self.panicMode = false; - fn printStatement(self: *Self) void { - self.expression(); - self.consume(Token.SEMICOLON, "Expect ';' after value."); - self.emitOP(OP.PRINT); - } + while ((self.current.type catch Token.NIL) != Token.EOF) { + if ((self.previous.type catch Token.NIL) == Token.SEMICOLON) return; + switch (self.current.type catch Token.NIL) { + Token.CLASS, Token.FUN, Token.VAR, Token.IF, Token.FOR, Token.WHILE, Token.PRINT, Token.RETURN => return, + else => self.advance(), + } + } + } - pub fn compile(source: []const u8, objects: *Obj.List, allocator: std.mem.Allocator) CompilerError!Chunk { - // zig fmt: off - var self = Self{ - .scanner = try scanner.Scanner.init(source), - .current = scanner.Token.Empty, - .previous = scanner.Token.Empty, - .panicMode = false, - .hadError = false, - .lastError = scanner.ScannerError.EmptyToken, - .compilingChunk = try Chunk.init(allocator), - .allocator = allocator, - .objects = objects - }; - // zig fmt: on - errdefer self.compilingChunk.deinit(); + fn statement(self: *Self) void { + if (self.match(Token.PRINT)) { + self.printStatement(); + } else if (self.match(Token.LEFT_BRACE)) { + self.beginScope(); + self.block(); + self.endScope(); + } else { + self.expressionStatement(); + } + } + + fn block(self: *Self) void { + while(!self.check(Token.RIGHT_BRACE) and !self.check(Token.EOF)) { + self.declaration(); + } - self.advance(); - while (!self.match(Token.EOF)) { - self.declaration(); + self.consume(Token.RIGHT_BRACE, "Expect '}' after block."); } - self.endCompiler(); - return if (self.hadError) self.lastError else self.compilingChunk; - } -}; + fn beginScope(self: *Self) void { + self.scopeDepth += 1; + } + + fn endScope(self: *Self) void { + self.scopeDepth -= 1; + + while(self.localCount > 0) { + if (self.locals[self.localCount-1].depth) |depth| { + if (depth <= self.scopeDepth) break; + } else { + self.errorAt(self.locals[self.localCount-1].name, "Unitialized variable at scope end"); + } + self.emitOP(OP.POP); + self.localCount -= 1; + } + } + + fn expressionStatement(self: *Self) void { + self.expression(); + self.consume(Token.SEMICOLON, "Expect ';' after expression."); + self.emitOP(OP.POP); + } + + fn match(self: *Self, token: Token) bool { + if (!self.check(token)) return false; + self.advance(); + return true; + } + + fn check(self: *const Self, token: Token) bool { + return if (self.current.type) |tp| tp == token else |_| false; + } + + fn printStatement(self: *Self) void { + self.expression(); + self.consume(Token.SEMICOLON, "Expect ';' after value."); + self.emitOP(OP.PRINT); + } + + pub fn compile(source: []const u8, objects: *Obj.List, allocator: std.mem.Allocator) CompilerError!Chunk { + // zig fmt: off + var self = Self{ + .scanner = try scanner.Scanner.init(source), + .current = scanner.Token.Empty, + .previous = scanner.Token.Empty, + .panicMode = false, + .hadError = false, + .lastError = scanner.ScannerError.EmptyToken, + .compilingChunk = try Chunk.init(allocator), + .allocator = allocator, + .objects = objects, + .locals = [_]Local{Local{.name = scanner.Token.Empty, .depth = 0, .con = true}} ** size, + .localCount = 0, + .scopeDepth = 0, + }; + // zig fmt: on + errdefer self.compilingChunk.deinit(); + + self.advance(); + while (!self.match(Token.EOF)) { + self.declaration(); + } + self.endCompiler(); + + return if (self.hadError) self.lastError else self.compilingChunk; + } + }; +} diff --git a/zlox/src/debug.zig b/zlox/src/debug.zig @@ -42,6 +42,8 @@ pub fn disassembleInstruction(ch: chunk.Chunk, offset: usize) !usize { @intFromEnum(OP.SET_GLOBAL) => try constantInstruction("OP_SET_GLOBAL", ch, offset), @intFromEnum(OP.PRINT) => simpleInstruction("OP_PRINT", offset), @intFromEnum(OP.POP) => simpleInstruction("OP_POP", offset), + @intFromEnum(OP.GET_LOCAL) => try byteInstruction("OP_GET_LOCAL", ch, offset), + @intFromEnum(OP.SET_LOCAL) => try byteInstruction("OP_SET_LOCAL", ch, offset), else => blk: { print("Unknown opcode {}\n", .{try ch.code.get(offset)}); break :blk offset + 1; @@ -61,3 +63,8 @@ fn constantInstruction(name: []const u8, ch: chunk.Chunk, offset: usize) !usize print("'\n", .{}); return offset + 2; } + +fn byteInstruction(name: []const u8, ch: chunk.Chunk, offset: usize) !usize { + print("{s:<16} {d:4}\n", .{name, try ch.code.get(offset+1)}); + return offset + 2; +} diff --git a/zlox/src/scanner.zig b/zlox/src/scanner.zig @@ -43,6 +43,7 @@ pub const TokenType = enum { THIS, TRUE, VAR, + CON, WHILE, EOF, @@ -85,6 +86,7 @@ pub const Scanner = struct { .{ "this", TokenType.THIS }, .{ "true", TokenType.TRUE }, .{ "var", TokenType.VAR }, + .{ "con", TokenType.CON }, .{ "while", TokenType.WHILE }, }); diff --git a/zlox/src/vm.zig b/zlox/src/vm.zig @@ -23,10 +23,12 @@ pub const VM = struct { } pub fn interpret(self: *@This(), source: []const u8, dbg: bool) InterpreterError!void { - var chunk = try compiler.Compiler.compile(source, &self.objects, self.allocator); + const stackSize = 256; + + var chunk = try compiler.Compiler(stackSize).compile(source, &self.objects, self.allocator); defer chunk.deinit(); - try Interpreter(256).run(self, &chunk, dbg); + try Interpreter(stackSize).run(self, &chunk, dbg); } fn Interpreter(size: comptime_int) type { @@ -122,6 +124,12 @@ pub const VM = struct { try self.binary_op(Value.number, Value.number, Callback.add); } }, + @intFromEnum(OP.GET_LOCAL) => { + self.push(self.stack[self.read_byte()]); + }, + @intFromEnum(OP.SET_LOCAL) => { + self.stack[self.read_byte()] = self.peek(0); + }, @intFromEnum(OP.GET_GLOBAL) => { const name = self.read_string(); self.push(self.vm.globals.get(name) catch {