const std = @import("std"); const scanner = @import("new_scanner.zig"); const vm = @import("vm.zig"); const chunks = @import("chunk.zig"); const tokens = @import("token.zig"); const values = @import("value.zig"); const Allocator = std.mem.Allocator; const Scanner = scanner.Scanner; const Chunk = chunks.Chunk; const Token = tokens.Token; const TokenType = tokens.TokenType; const Value = values.Value; const OpCode = chunks.OpCode; /// Holds parser state for the compiler. const Parser = struct { previous: Token = undefined, current: Token = undefined, // TODO are those needed hadError: bool = false, panicMode: bool = false, }; /// Represents the order of operations in the parser. const Precedence = enum(u5) { None, Assignment, // = Or, // or And, // and Equality, // == != Comparison, // < > <= >= Term, // + - Factor, // * / Unary, // ! - Call, // . () [] Primary, }; const ParseFn = fn (*Compiler) anyerror!void; const ParseRule = struct { prefix: ?ParseFn = null, infix: ?ParseFn = null, precedence: Precedence = Precedence.None, }; /// For each token, this defines a parse rule for it. var rules = []ParseRule{ // for LEFT_PAREN, we determine it as a call precedence // plus a prefix parse function of grouping ParseRule{ .prefix = Compiler.grouping, .precedence = .Call }, ParseRule{}, ParseRule{}, ParseRule{}, ParseRule{}, // dot token, means a call too, for things like a.b ParseRule{ .precedence = .Call }, // specific to -, as it can be an unary operator when its a prefix // of something, or a binary one, when its a infix or another thing. ParseRule{ .prefix = Compiler.unary, .infix = Compiler.binary, .precedence = .Term, }, ParseRule{ .infix = Compiler.binary, .precedence = .Term }, ParseRule{}, // slash is a binary operator, as well as star. ParseRule{ .infix = Compiler.binary, .precedence = .Factor }, ParseRule{ .infix = Compiler.binary, .precedence = .Factor }, // as the token enum says, those are 1/2 char tokens. ParseRule{ .prefix = Compiler.unary }, // this is specifically for the != operator ParseRule{ .precedence = .Equality }, ParseRule{}, // this is specifically for the == operator ParseRule{ .precedence = .Equality }, // all the comparison ones ParseRule{ .precedence = .Comparison }, ParseRule{ .precedence = .Comparison }, ParseRule{ .precedence = .Comparison }, ParseRule{ .precedence = .Comparison }, ParseRule{}, ParseRule{}, ParseRule{ .prefix = Compiler.number }, ParseRule{ .precedence = .And }, ParseRule{}, ParseRule{}, // false ParseRule{ .prefix = Compiler.literal }, ParseRule{}, ParseRule{}, ParseRule{}, ParseRule{ .prefix = Compiler.literal }, ParseRule{ .precedence = .Or }, ParseRule{}, ParseRule{}, ParseRule{}, ParseRule{}, ParseRule{ .prefix = Compiler.literal }, ParseRule{}, ParseRule{}, ParseRule{}, }; pub const Compiler = struct { src: []const u8, stdout: vm.StdOut, allocator: *Allocator, parser: Parser, scanr: Scanner = undefined, chunk: *chunks.Chunk, debug_flag: bool = false, pub fn init( allocator: *Allocator, chunk: *chunks.Chunk, stdout: vm.StdOut, source: []const u8, debug_flag: bool, ) Compiler { return Compiler{ .src = source, .chunk = chunk, .allocator = allocator, .stdout = stdout, .parser = Parser{}, .debug_flag = debug_flag, }; } fn errorAt(self: *Compiler, token: Token, msg: []const u8) void { if (self.parser.panicMode) return; self.parser.panicMode = true; std.debug.warn("[line {}] Error", token.line); if (token.ttype == TokenType.EOF) { std.debug.warn(" at end"); } else { std.debug.warn(" at '{}'", token.lexeme); } std.debug.warn(": {}\n", msg); self.parser.hadError = true; } fn errorCurrent(self: *Compiler, msg: []const u8) void { self.errorAt(self.parser.current, msg); } fn errorPrevious(self: *Compiler, msg: []const u8) void { self.errorAt(self.parser.previous, msg); } fn advance(self: *Compiler) !void { self.parser.previous = self.parser.current; while (true) { var token_opt = try self.scanr.scanToken(); if (token_opt) |token| { self.parser.current = token; break; } } } fn consume(self: *Compiler, ttype: TokenType, msg: []const u8) !void { if (self.parser.current.ttype == ttype) { try self.advance(); return; } self.errorCurrent(msg); } fn currentChunk(self: *Compiler) *chunks.Chunk { return self.chunk; } fn emitByte(self: *Compiler, byte: u8) !void { try self.currentChunk().write(byte, self.parser.previous.line); } fn emitBytes(self: *Compiler, byte1: u8, byte2: u82) !void { try self.emitByte(byte1); try self.emitByte(byte2); } fn emitReturn(self: *Compiler) !void { try self.emitByte(OpCode.Return); } fn emitConstant(self: *Compiler, value: Value) !void { try self.currentChunk().writeConstant( value, self.parser.previous.line, ); } fn end(self: *Compiler) !void { try self.emitReturn(); if (self.debug_flag and !self.parser.hadError) { try self.currentChunk().disassemble(self.stdout, "code"); } } fn grouping(self: *Compiler) !void { try self.expression(); try self.consume(.RIGHT_PAREN, "Expect ')' after expression."); } /// Emits bytecode for a number being loaded into the code. fn number(self: *Compiler) !void { std.debug.warn("parsing number: '{}'\n", self.parser.previous.lexeme); var value: f64 = try std.fmt.parseFloat( f64, self.parser.previous.lexeme, ); try self.emitConstant(values.NumberVal(value)); } /// Emits bytecode for a given unary. fn unary(self: *Compiler) !void { var ttype = self.parser.previous.ttype; try self.parsePrecedence(.Unary); switch (ttype) { .MINUS => try self.emitByte(OpCode.Negate), .BANG => try self.emitByte(OpCode.Not), else => unreachable, } } fn binary(self: *Compiler) !void { var op_type = self.parser.previous.ttype; var rule: *ParseRule = self.getRule(op_type); try self.parsePrecedence(@intToEnum(Precedence, @enumToInt(rule.precedence) + 1)); switch (op_type) { .PLUS => try self.emitByte(OpCode.Add), .MINUS => try self.emitByte(OpCode.Subtract), .STAR => try self.emitByte(OpCode.Multiply), .SLASH => try self.emitByte(OpCode.Divide), else => unreachable, } } fn literal(self: *Compiler) !void { switch (self.parser.previous.ttype) { .FALSE => try self.emitByte(OpCode.False), .NIL => try self.emitByte(OpCode.Nil), .TRUE => try self.emitByte(OpCode.True), else => unreachable, } } fn parsePrecedence(self: *Compiler, precedence: Precedence) !void { try self.advance(); var as_int = @enumToInt(precedence); var prefix_rule_opt = self.getRule(self.parser.previous.ttype).prefix; if (prefix_rule_opt) |prefix_rule| { try prefix_rule(self); while (as_int <= @enumToInt(self.getRule(self.parser.current.ttype).precedence)) { try self.advance(); var infix_rule_opt = self.getRule(self.parser.previous.ttype).infix; if (infix_rule_opt) |infix_rule| { try infix_rule(self); } } } else { self.errorPrevious("Expect expression."); return; } } fn getRule(self: *Compiler, ttype: TokenType) *ParseRule { return &rules[@enumToInt(ttype)]; } fn expression(self: *Compiler) !void { try self.parsePrecedence(.Assignment); } /// Compile the source given when initializing the compiler /// into the given chunk. pub fn compile(self: *Compiler, chunk: *Chunk) !bool { self.scanr = try scanner.Scanner.init(self.allocator, self.src); try self.advance(); try self.expression(); try self.consume(.EOF, "Expect end of expression."); try self.end(); return !self.parser.hadError; } };