From 76073d893be9a65bd8ae3c331edea747d1a50f09 Mon Sep 17 00:00:00 2001 From: Amanuel Bogale Date: Wed, 12 Jun 2024 19:23:52 -0700 Subject: [PATCH] facing insane cpp issues --- .vscode/settings.json | 4 +- README.md | 2 +- include/ast/expr.hh | 12 ++- include/parser/parser.hh | 66 +++++++++++++++++ include/reader/reader.hh | 80 ++++++++++++++++++++ include/scanner/scanner.hh | 30 ++------ include/scanner/tokens.hh | 89 ++++++++++++++++------ lib/CMakeLists.txt | 6 +- lib/driver/driver.cc | 22 ++++++ lib/parser/parser.cc | 147 +++++++++++++++++++++++++++++++++++++ lib/reader/reader.cc | 8 ++ lib/scanner/scanner.cc | 32 ++++---- lib/scanner/tokens.cc | 20 ++++- notes/grammar.md | 43 ++++++++++- tests/CMakeLists.txt | 1 - 15 files changed, 489 insertions(+), 73 deletions(-) create mode 100644 include/parser/parser.hh create mode 100644 include/reader/reader.hh create mode 100644 lib/parser/parser.cc create mode 100644 lib/reader/reader.cc diff --git a/.vscode/settings.json b/.vscode/settings.json index b073479..76d6ad5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -64,6 +64,8 @@ "condition_variable": "cpp", "memory": "cpp", "print": "cpp", - "stack": "cpp" + "stack": "cpp", + "list": "cpp", + "unordered_set": "cpp" } } \ No newline at end of file diff --git a/README.md b/README.md index 9783d54..3cf564f 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ See `LICENSE` for more information. # Roadmap -- Lexer +- Le - Parser - Compiler - JIT diff --git a/include/ast/expr.hh b/include/ast/expr.hh index 29ad95c..fda2e91 100644 --- a/include/ast/expr.hh +++ b/include/ast/expr.hh @@ -1,8 +1,9 @@ #pragma once -#include "../scanner/tokens.hh" -#include "../utils/macros.hh" +#include +#include +#include #include #include #include @@ -24,9 +25,6 @@ namespace rift Unary ); - // template - // class Visitor; - // class Binary; /// @class Expr /// @brief Base class for all expressions /// @details Types of expression include @@ -97,8 +95,8 @@ namespace rift class Literal: public Expr { public: - Literal(const char* value): value(value) {}; - const char* value; + Literal(std::any value): value(value) {}; + std::any value; inline T accept(const Visitor &visitor) override {return visitor.visit_literal(this);} }; diff --git a/include/parser/parser.hh b/include/parser/parser.hh new file mode 100644 index 0000000..61e6ab6 --- /dev/null +++ b/include/parser/parser.hh @@ -0,0 +1,66 @@ + +#pragma once + +#include +#include +#include +#include +#include +#include + +using namespace rift::scanner; +using namespace rift::reader; + +using GenExpr = rift::ast::Expr::Expr; + +namespace rift +{ + namespace parser + { + class Parser : public Reader + { + public: + Parser(const std::vector &tokens) : Reader(tokens), tokens(tokens) {}; + ~Parser() = default; + protected: + std::vector tokens; + std::exception exception; + + private: + #pragma mark - Grammar Evaluators + + /// @note rules in order of precedence + + /// @example 1 + 2 * 3 + std::unique_ptr expression(); + /// @example 1 == 1, 1 != 2 + std::unique_ptr equality(); + /// @example 1 > 2, 1 <= 2 + std::unique_ptr comparison(); + /// @example 1 + 2, 1 - 2 + std::unique_ptr term(); + /// @example 1 * 2, 1 / 2 + std::unique_ptr factor(); + /// @example -1, !1 + std::unique_ptr unary(); + /// @example 1, "string", true, false, nil + std::unique_ptr primary(); + + void synchronize(); + + std::unique_ptr parse(); + }; + + class ParserException : public ReaderException + { + public: + ParserException(const std::string &message) : message(message) {} + ~ParserException() = default; + + const char *what() const noexcept override { return message.c_str(); } + + private: + std::string message; + }; + } +} \ No newline at end of file diff --git a/include/reader/reader.hh b/include/reader/reader.hh new file mode 100644 index 0000000..99f48f8 --- /dev/null +++ b/include/reader/reader.hh @@ -0,0 +1,80 @@ + +#pragma once + +#include +#include + +namespace rift +{ + namespace reader + { + + class ReaderException; + + /// @class Reader + /// @tparam T The type of the reader + /// @brief The base class for reading through lines with utilities + template + class Reader + { + public: + Reader(const std::vector &source): source(source) {start=0;curr=0;line=1;}; + ~Reader() = default; + + protected: + std::vector source; + unsigned start, curr, line; + + #pragma mark - Reader Methods + + inline bool atEnd() { return this->curr >= source.size(); } + inline T advance() { return (!atEnd()) ? source.at(curr++) : T(); } + // inline typename std::enable_if::value, T>::type advance() { return (!atEnd()) ? source.at(curr++) : '\0'; } + // inline typename std::enable_if::value, T>::type advance() { return (!atEnd()) ? source.at(curr++) : nullptr; } + + /// @brief Peeks at the current character + inline bool peek(T expected) { return !atEnd() && source.at(curr) == expected; }; + /// @brief Peeks at the current character with an offset + inline bool peek_off(T expected, int offset) { return curr+offset=0 ? source.at(curr-1) : T(); }; + + /// @brief Scans a comment and advances the cursor + inline void scanComment() { while (peek('\n')) advance();}; + /// @brief Consumes a T and advances the cursor (Error if not found) + inline T consume (T expected, std::unique_ptr error) { + if (peek(expected)) return advance(); + rift::error::report(line, "consume", "expected " << expected); + if (error) throw error; + } + /// @brief Matches a T from a set of T's{token, character} and advances the cursor + inline bool match(std::vector expected) { + for (auto &c : expected) { + if (peek(c)) { + advance(); + return true; + } + } + return false; + } + + inline bool match_one(T expected) { return peek(expected) ? advance() : false; } + }; + + /// @class ReaderException + /// @brief The base exception for the reader (implement in derived classes) + class ReaderException + { + public: + ReaderException() = default; + ~ReaderException() = default; + virtual const char* what() const noexcept { return "BaseException"; } + }; + } +} \ No newline at end of file diff --git a/include/scanner/scanner.hh b/include/scanner/scanner.hh index c813e1b..f2f68ed 100644 --- a/include/scanner/scanner.hh +++ b/include/scanner/scanner.hh @@ -1,7 +1,8 @@ #pragma once #include "tokens.hh" -#include "../error/error.hh" +#include +#include #include #include #include @@ -9,20 +10,21 @@ #include #include +using namespace rift::reader; typedef rift::scanner::Token Token; typedef rift::scanner::TokenType Type; namespace rift { namespace scanner { - struct Scanner + struct Scanner : public Reader { - std::string source; + std::vector source; std::vector tokens; std::unordered_map keywords; static unsigned start, curr, line; - Scanner(const std::string& source); + Scanner(const std::vector& source); ~Scanner(){} /// @fn scan_token @@ -43,33 +45,15 @@ namespace rift void addToken(Type type) { addToken(type, ""); }; void addToken(Type type, std::string literal) { - tokens.push_back(Token(type, source.substr(start, curr-start), literal, line)); + tokens.push_back(Token(type, std::string(source.begin()+start, source.begin()+curr), literal, line)); } #pragma mark - Helper Functions (Inline) - inline bool atEnd() { return this->curr >= source.size(); } - inline char advance() { return (!atEnd()) ? source.at(curr++) : '\0'; } inline bool isDigit(char c) { return c>='0' && c<='9'; } inline bool isAlpha(char c) { return (c>='a' && c<='z') || (c>='A' && c<='Z') || c=='_'; } inline bool isAlphaNumeric(char c) { return isAlpha(c) || isDigit(c); } - /// @brief Peeks at the current character - inline bool peek(char expected) { return !atEnd() && source.at(curr) == expected; }; - /// @brief Peeks at the current character with an offset - inline bool peek_off(char expected, int offset) { return curr+offset +#include namespace rift { @@ -11,23 +12,53 @@ namespace rift enum TokenType { // Single-character tokens - LEFT_PAREN, RIGHT_PAREN, LEFT_BRACE, RIGHT_BRACE, - COMMA, DOT, MINUS, PLUS, SEMICOLON, SLASH, STAR, + LEFT_PAREN, + RIGHT_PAREN, + LEFT_BRACE, + RIGHT_BRACE, + COMMA, + DOT, + MINUS, + PLUS, + SEMICOLON, + SLASH, + STAR, // One or two character tokens - BANG, BANG_EQUAL, - EQUAL, EQUAL_EQUAL, - GREATER, GREATER_EQUAL, - LESS, LESS_EQUAL, - SEPARATOR, WHITESPACE, + BANG, + BANG_EQUAL, + EQUAL, + EQUAL_EQUAL, + GREATER, + GREATER_EQUAL, + LESS, + LESS_EQUAL, + SEPARATOR, + WHITESPACE, OPERATOR, // Literals - IDENTIFIER, STRINGLITERAL, NUMERICLITERAL, + IDENTIFIER, + STRINGLITERAL, + NUMERICLITERAL, // Keywords - AND, CLASS, ELSE, FALSE, FUN, FOR, IF, NIL, OR, - PRINT, RETURN, SUPER, THIS, TRUE, VAR, WHILE, + AND, + CLASS, + ELSE, + FALSE, + FUN, + FOR, + IF, + NIL, + OR, + PRINT, + RETURN, + SUPER, + THIS, + TRUE, + VAR, + WHILE, EOFF }; @@ -37,20 +68,36 @@ namespace rift struct Token { TokenType type; - std::string text, lexeme, literal; + std::string text, lexeme; + std::any literal; int line; - Token(TokenType type, std::string lexeme, std::string literal, int line) + Token(TokenType type, std::string lexeme, std::any literal, int line) { - this->type = type; this->lexeme = lexeme; - this->literal = literal; this->line = line; + this->type = type; + this->lexeme = lexeme; + this->literal = literal; + this->line = line; } - private: - /// @brief Converts a TokenType to a string - std::string convertTypeString(TokenType type); - /// @brief Converts a Token to a string - std::string to_string(); + + Token() : type(TokenType::EOFF), lexeme(""), literal(0), line(0) {} + + /// @brief Converts a TokenType to a string + static std::string convertTypeString(TokenType type); + /// @brief Converts a Token to a string + std::string to_string() const; + + // friend std::ostream &operator<<(std::ostream &os, const Token &token); + bool operator==(const Token &token); }; - } -} \ No newline at end of file + + // std::ostream& operator<<(std::ostream& os, const Token& token) { + // os << std::string("Token(Type=") << token.convertTypeString(token.type) + // /*<< ", Lexeme=\"" << token.lexeme + // << "\", Literal=" << token.literal.type().name() + // << ", Line=" << token.line << ")"*/; + // return os; + // } + + } \ No newline at end of file diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 0d04e92..a54ae31 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -2,12 +2,14 @@ set( SOURCES # Main main.cc - + # Scanner scanner/scanner.cc scanner/tokens.cc + reader/reader.cc - # AST + # Parsing + parser/parser.cc ast/printer.cc # Driver diff --git a/lib/driver/driver.cc b/lib/driver/driver.cc index f0bc7e6..4575747 100644 --- a/lib/driver/driver.cc +++ b/lib/driver/driver.cc @@ -6,6 +6,28 @@ #include #include +// //////////////////////////////////////////////////////////////////////////////////////////////////////// +/// /// +/// ██████████████████████████████████████████████████████████████████████████████████████████████ /// +/// ████████▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀██████ /// +/// ██████▀░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▀█████ /// +/// ████▀░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▀████ /// +/// ██▀░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▀██ /// +/// █░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░█ /// +/// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ /// +/// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +/// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ /// +/// /// +/// ██████████████████████████████████████████████████████████████████████████████████████████████ /// +/// ████████████████████████████████████████████████████████████████████████████████████████████████ /// +/// ██████ ▀██████████████████▀ ▀██████████████████▀ ▀██████████████████▀ ▀█████████████████ /// +/// ████▀ ░░░▀██████████████▀ ░░░▀██████████████▀ ░░░▀██████████████▀ ░░░▀██████████████████ /// +/// ██▀ ░░░░░░░▀██████████▀ ░░░░░░░▀██████████▀ ░░░░░░░▀██████████▀ ░░░░░░░▀████████████████ /// +/// █ ░░░░░░░░░░░▀██████▀ ░░░░░░░░░░░▀██████▀ ░░░░░░░░░░░▀██████▀ ░░░░░░░░░░░▀██████████████ /// +/// ░░░░░░░░░░░░░░░░░████░░░░░░░░░░░░░░░░░░░███░░░░░░░░░░░░░░░░░░███░░░░░░░░░░░░░░░░░░▀█████████████ /// +/// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▀████████████ /// +/// + // REMOVE #include #include diff --git a/lib/parser/parser.cc b/lib/parser/parser.cc new file mode 100644 index 0000000..74cf527 --- /dev/null +++ b/lib/parser/parser.cc @@ -0,0 +1,147 @@ +#include +#include + +using namespace rift::scanner; +using namespace rift::ast::Expr; +using GenExpr = rift::ast::Expr::Expr; + +namespace rift +{ + namespace parser + { + #pragma mark - Grammar Evaluators + + std::unique_ptr Parser::primary() + { + if (match({Token(TokenType::FALSE, "false", "", line)})) + return std::unique_ptr>(new Literal(false)); + if (match({Token(TokenType::TRUE, "true", "", line)})) + return std::unique_ptr>(new Literal(true)); + if (match({Token(TokenType::NIL, "nil", "", line)})) + return std::unique_ptr>(new Literal(nullptr)); + + if (match({Token(TokenType::NUMERICLITERAL, "", "", line)})) + return std::unique_ptr>(new Literal(peekPrev().literal)); + if (match({Token(TokenType::STRINGLITERAL, "", "", line)})) + return std::unique_ptr>(new Literal(peekPrev().literal)); + + if (match({Token(TokenType::LEFT_PAREN, "(", "", line)})) { + auto expr = expression(); + consume(Token(TokenType::RIGHT_PAREN, ")", "", line), std::unique_ptr(new ParserException("Expected ')' after expression"))); + return expr; + } + + throw ParserException("Expected expression"); + } + + std::unique_ptr Parser::unary() + { + if (match({Token(TokenType::BANG, "!", "", line)})) { + auto op = peekPrev(); + auto right = unary(); + return std::unique_ptr>(new ast::Expr::Unary(op, std::move(right))); + } + + if (match({Token(TokenType::MINUS, "-", "", line)})) { + auto op = peekPrev(); + auto right = unary(); + return std::unique_ptr>(new ast::Expr::Unary(op, std::move(right))); + } + + return primary(); + } + + std::unique_ptr Parser::factor() + { + auto expr = unary(); + + while (match({Token(TokenType::STAR, "*", "", line)}) || match({Token(TokenType::SLASH, "/", "", line)})) { + auto op = peekPrev(); + auto right = unary(); + expr = std::unique_ptr>(new ast::Expr::Binary(std::move(expr), op, std::move(right))); + } + + return expr; + } + + std::unique_ptr Parser::term() + { + auto expr = factor(); + + while (match({Token(TokenType::MINUS, "-", "", line)}) || match({Token(TokenType::PLUS, "+", "", line)})) { + auto op = peekPrev(); + auto right = factor(); + expr = std::unique_ptr>(new ast::Expr::Binary(std::move(expr), op, std::move(right))); + } + + return expr; + } + + std::unique_ptr Parser::comparison() + { + auto expr = term(); + + while (match({Token(TokenType::GREATER, ">", "", line)}) || match({Token(TokenType::GREATER_EQUAL, ">=", "", line)}) || match({Token(TokenType::LESS, "<", "", line)}) || match({Token(TokenType::LESS_EQUAL, "<=", "", line)}) ) { + auto op = peekPrev(); + auto right = term(); + expr = std::unique_ptr>(new ast::Expr::Binary(std::move(expr), op, std::move(right))); + } + + return expr; + } + + std::unique_ptr Parser::equality() + { + auto expr = comparison(); + + while (match({Token(TokenType::BANG_EQUAL, "!=", "", line)}) || match({Token(TokenType::EQUAL_EQUAL, "==", "", line)})) { + auto op = peekPrev(); + auto right = comparison(); + expr = std::unique_ptr>(new ast::Expr::Binary(std::move(expr), op, std::move(right))); + } + + return expr; + } + + std::unique_ptr Parser::expression() + { + return equality(); + } + + std::unique_ptr Parser::parse() + { + try { + return expression(); + } catch (const ParserException &e) { + return nullptr; + } + } + + # pragma mark - Utilities + + void Parser::synchronize() + { + advance(); + + while (!atEnd()) { + if (peekPrev().type == TokenType::SEMICOLON) return; + + switch (peek().type) { + case TokenType::CLASS: + case TokenType::FUN: + case TokenType::VAR: + case TokenType::FOR: + case TokenType::IF: + case TokenType::WHILE: + case TokenType::PRINT: + case TokenType::RETURN: + return; + default: + break; + } + + advance(); + } + } + } +} \ No newline at end of file diff --git a/lib/reader/reader.cc b/lib/reader/reader.cc new file mode 100644 index 0000000..60fcd10 --- /dev/null +++ b/lib/reader/reader.cc @@ -0,0 +1,8 @@ +#include + +namespace rift +{ + namespace reader + { + } +} \ No newline at end of file diff --git a/lib/scanner/scanner.cc b/lib/scanner/scanner.cc index b78d42e..a796a54 100644 --- a/lib/scanner/scanner.cc +++ b/lib/scanner/scanner.cc @@ -3,6 +3,7 @@ typedef rift::scanner::Token Token; typedef rift::scanner::TokenType Type; +using namespace rift::reader; namespace rift { @@ -16,8 +17,8 @@ namespace rift unsigned Scanner::curr = 0; unsigned Scanner::line = 1; - Scanner::Scanner(const std::string& source) { - this->source = std::string(source); + Scanner::Scanner(const std::vector& source) : Reader(source) { + this->source = source; this->tokens = std::vector(); keywords = std::unordered_map(); @@ -59,11 +60,14 @@ namespace rift return; } - if (peek3('"')) {advance(); advance(); advance();} - else advance(); - - if (peek3('"')) addToken(Type::STRINGLITERAL, source.substr(start+3, curr-3)); - else addToken(Type::STRINGLITERAL, source.substr(start+1, curr-1)); + if (peek3('"')) { + addToken(Type::STRINGLITERAL, std::string(source.begin()+start+3, source.begin()+curr)); + advance(); advance(); advance(); + } + else { + addToken(Type::STRINGLITERAL, std::string(source.begin()+start+1, source.begin()+curr)); + advance(); + } } void Scanner::num() { @@ -72,12 +76,12 @@ namespace rift advance(); while (isDigit(advance())); } - addToken(Type::NUMERICLITERAL, source.substr(start, curr)); + addToken(Type::NUMERICLITERAL, std::string(source.begin()+start, source.begin()+curr)); } void Scanner::identifier() { while (isAlphaNumeric(advance())); - std::string text = source.substr(start, curr-start); + std::string text = std::string(source.begin()+start, source.begin()+curr); if (keywords.find(text)!= keywords.end()) { addToken(keywords.at(text)); } else { @@ -104,11 +108,11 @@ namespace rift case '+': addToken(Type::PLUS);break; case ';': addToken(Type::SEMICOLON);break; case '*': addToken(Type::STAR);break; - case '!': addToken(match('=') ? Type::BANG_EQUAL : Type::BANG);break; - case '=': addToken(match('=') ? Type::EQUAL_EQUAL : Type::EQUAL);break; - case '<': addToken(match('=') ? Type::LESS_EQUAL : Type::LESS);break; - case '>': addToken(match('=') ? Type::GREATER_EQUAL : Type::GREATER);break; - case '/': match('/') ? scanComment() : addToken(Type::SLASH);break; + case '!': addToken(match_one('=') ? Type::BANG_EQUAL : Type::BANG);break; + case '=': addToken(match_one('=') ? Type::EQUAL_EQUAL : Type::EQUAL);break; + case '<': addToken(match_one('=') ? Type::LESS_EQUAL : Type::LESS);break; + case '>': addToken(match_one('=') ? Type::GREATER_EQUAL : Type::GREATER);break; + case '/': match_one('/') ? scanComment() : addToken(Type::SLASH);break; case '"': string(); break; case ' ': break; case '\r': break; diff --git a/lib/scanner/tokens.cc b/lib/scanner/tokens.cc index 17043b7..21a25be 100644 --- a/lib/scanner/tokens.cc +++ b/lib/scanner/tokens.cc @@ -1,3 +1,4 @@ +#include #include using namespace rift::scanner; @@ -49,7 +50,22 @@ std::string Token::convertTypeString(TokenType type) { } } -std::string Token::to_string() +std::string Token::to_string() const { - return std::string(convertTypeString(type) + " " + lexeme + " " + literal); + return std::string(convertTypeString(type) + " " + lexeme); +} + +#pragma mark - Operators + +std::ostream& operator<<(std::ostream& os, const rift::scanner::Token& token) { + os << "Token(Type=" << static_cast(token.type) + << ", Lexeme=\"" << token.lexeme + << "\", Literal=" << token.literal.type().name() + << ", Line=" << token.line << ")"; + return os; +} + +bool Token::operator==(const Token &token) +{ + return this->type == token.type; } \ No newline at end of file diff --git a/notes/grammar.md b/notes/grammar.md index 945061d..b8bdcce 100644 --- a/notes/grammar.md +++ b/notes/grammar.md @@ -10,4 +10,45 @@ grouping → "(" expression ")" ; unary → ( "-" | "!" ) expression ; binary → expression operator expression ; operator → "==" | "!=" | "<" | "<=" | ">" | ">=" - | "+" | "-" | "*" | "/" ; \ No newline at end of file + | "+" | "-" | "*" | "/" ; + +## Flat + +expression → equality ; +equality → comparison ( ( "!=" | "==" ) comparison )* ; +comparison → term ( ( ">" | ">=" | "<" | "<=" ) term )* ; +term → factor ( ( "-" | "+" ) factor )* ; +factor → unary ( ( "/" | "*" ) unary )* ; +unary → ( "!" | "-" ) unary + | primary ; +primary → NUMBER | STRING | "true" | "false" | "nil" + | "(" expression ")" ; + + +# Precedence and Associativity + +Name | Operators | Associates +------|------------|------------ +Equality | == != | Left + +Comparison | > >= < <= | Left + +Term | - + | Left + +Factor | / * | Left + +Unary | ! - | Right + +## Precedence + +1. expression +2. equality +3. comparison +4. term +5. factor +6. unary +7. primary + +## Associativity + +... \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4ba97f9..951efc6 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -7,7 +7,6 @@ set( test/scanner.cc # Mock Tests - mock/parser.cc ) add_executable(