diff --git a/src/compiler.zig b/src/compiler.zig index 3fea307..6695de9 100644 --- a/src/compiler.zig +++ b/src/compiler.zig @@ -13,7 +13,8 @@ const TokenType = tokens.TokenType; const Value = values.Value; const OpCode = chunks.OpCode; -pub const Parser = struct { +/// Holds parser state for the compiler. +const Parser = struct { previous: Token = undefined, current: Token = undefined, @@ -22,6 +23,93 @@ pub const Parser = struct { 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{}, + // 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{}, + ParseRule{}, + ParseRule{}, + ParseRule{}, + ParseRule{}, + ParseRule{}, + ParseRule{ .precedence = .Or }, + ParseRule{}, + ParseRule{}, + ParseRule{}, + ParseRule{}, + ParseRule{}, + ParseRule{}, + ParseRule{}, + ParseRule{}, +}; + pub const Compiler = struct { src: []const u8, stdout: vm.StdOut, @@ -29,12 +117,14 @@ pub const Compiler = struct { 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, @@ -42,6 +132,7 @@ pub const Compiler = struct { .allocator = allocator, .stdout = stdout, .parser = Parser{}, + .debug_flag = debug_flag, }; } @@ -117,6 +208,10 @@ pub const Compiler = struct { 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 { @@ -126,21 +221,66 @@ pub const Compiler = struct { /// Emits bytecode for a number being loaded into the code. fn number(self: *Compiler) !void { - var value: f64 = try std.fmt.parseFloat(f64, parser.previous.lexeme); + var value: f64 = try std.fmt.parseFloat( + f64, + self.parser.previous.lexeme, + ); try self.emitConstant(value); } /// Emits bytecode for a given unary. fn unary(self: *Compiler) !void { var ttype = self.parser.previous.ttype; - try self.expression(); + try self.parsePrecedence(.Unary); + switch (ttype) { .MINUS => try self.emitByte(OpCode.Negate), else => unreachable, } } - fn expression(self: *Compiler) !void {} + 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 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. @@ -148,7 +288,7 @@ pub const Compiler = struct { self.scanr = try scanner.Scanner.init(self.allocator, self.src); try self.advance(); - //try self.expression(); + try self.expression(); try self.consume(.EOF, "Expect end of expression."); try self.end(); diff --git a/src/vm.zig b/src/vm.zig index 3375684..e58bc66 100644 --- a/src/vm.zig +++ b/src/vm.zig @@ -167,7 +167,13 @@ pub const VM = struct { //return res; var chk = try Chunk.init(self.allocator); - var cmpr = Compiler.init(self.allocator, &chk, self.stdout, self.src); + var cmpr = Compiler.init( + self.allocator, + &chk, + self.stdout, + self.src, + self.debug_flag, + ); if (!try cmpr.compile(&chk)) { return InterpretResult.CompileError; }