commit 8ccf4e893ff6704a047a0810818ca4f80f0d3552
parent 19e17da6516be732014392872f3ae07ab5d781f4
Author: Szymon Mikulicz <szymon.mikulicz@aptiv.com>
Date: Sat, 27 Jul 2024 00:25:58 +0200
Local variables finished
Diffstat:
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 {