diff --git a/furc/include/furc/ast/literal.hpp b/furc/include/furc/ast/literal.hpp index 557ba95..1938ee7 100644 --- a/furc/include/furc/ast/literal.hpp +++ b/furc/include/furc/ast/literal.hpp @@ -48,6 +48,8 @@ public: literal_node_t literal_type() const override { return literal_node_t::Integer; } const handle& value() const { return m_value; } + + bool operator==(front::integer_token integer) const { return m_value == integer; } public: std::ostream& print(std::ostream& os) const override; protected: diff --git a/furc/include/furc/front/parser.hpp b/furc/include/furc/front/parser.hpp index a7084cb..f7f9e9b 100644 --- a/furc/include/furc/front/parser.hpp +++ b/furc/include/furc/front/parser.hpp @@ -33,6 +33,9 @@ private: ast::node_handle parse_expression(); ast::node_handle parse_literal(); + ast::expression_node_h parse_expression_primary(); + ast::expression_node_h parse_expression_rhs(const ast::expression_node_h& init, std::uint32_t precedence); + ast::function_body_handle parse_body(); private: token_handle<> next_token(); diff --git a/furc/include/furc/front/token.hpp b/furc/include/furc/front/token.hpp index 510c774..251bfc1 100644 --- a/furc/include/furc/front/token.hpp +++ b/furc/include/furc/front/token.hpp @@ -26,6 +26,12 @@ enum class token_t { Colon, Comma, Dot, + + Plus, + Minus, + Star, + Slash, + Percent, }; static inline std::ostream& operator<<(std::ostream& os, token_t type) { @@ -45,7 +51,13 @@ static inline std::ostream& operator<<(std::ostream& os, token_t type) { case token_t::Colon: return os << "':'"; case token_t::Comma: return os << "','"; case token_t::Dot: return os << "'.'"; + case token_t::Plus: return os << "'+'"; + case token_t::Minus: return os << "'-'"; + case token_t::Star: return os << "'*'"; + case token_t::Slash: return os << "'/'"; + case token_t::Percent: return os << "'%'"; } + return os; } static inline std::string operator+(const std::string& str, token_t type) { @@ -65,7 +77,13 @@ static inline std::string operator+(const std::string& str, token_t type) { case token_t::Colon: return str + "':'"; case token_t::Comma: return str + "','"; case token_t::Dot: return str + "'.'"; + case token_t::Plus: return str + "'+'"; + case token_t::Minus: return str + "'-'"; + case token_t::Star: return str + "'*'"; + case token_t::Slash: return str + "'/'"; + case token_t::Percent: return str + "'%'"; } + return str; } enum class keyword_token { @@ -144,7 +162,12 @@ struct token { case token_t::Semicolon: case token_t::Colon: case token_t::Comma: - case token_t::Dot: return true; + case token_t::Dot: + case token_t::Plus: + case token_t::Minus: + case token_t::Star: + case token_t::Slash: + case token_t::Percent: return true; } } }; diff --git a/furc/src/front/lexer.cpp b/furc/src/front/lexer.cpp index 1b7eecf..7ba5e7e 100644 --- a/furc/src/front/lexer.cpp +++ b/furc/src/front/lexer.cpp @@ -45,16 +45,21 @@ token_handle<> lexer::next_token() { char ch = get(); switch (ch) { - case '(': return { location, token_t::Lparen, m_content.substr(m_cursor++, 1) }; - case ')': return { location, token_t::Rparen, m_content.substr(m_cursor++, 1) }; - case '{': return { location, token_t::Lbrace, m_content.substr(m_cursor++, 1) }; - case '}': return { location, token_t::Rbrace, m_content.substr(m_cursor++, 1) }; - case '[': return { location, token_t::Lbracket, m_content.substr(m_cursor++, 1) }; - case ']': return { location, token_t::Rbracket, m_content.substr(m_cursor++, 1) }; - case ';': return { location, token_t::Semicolon, m_content.substr(m_cursor++, 1) }; - case ':': return { location, token_t::Colon, m_content.substr(m_cursor++, 1) }; - case ',': return { location, token_t::Comma, m_content.substr(m_cursor++, 1) }; - case '.': return { location, token_t::Dot, m_content.substr(m_cursor++, 1) }; + case '(': ++m_cursor; return { location, token_t::Lparen }; + case ')': ++m_cursor; return { location, token_t::Rparen }; + case '{': ++m_cursor; return { location, token_t::Lbrace }; + case '}': ++m_cursor; return { location, token_t::Rbrace }; + case '[': ++m_cursor; return { location, token_t::Lbracket }; + case ']': ++m_cursor; return { location, token_t::Rbracket }; + case ';': ++m_cursor; return { location, token_t::Semicolon }; + case ':': ++m_cursor; return { location, token_t::Colon }; + case ',': ++m_cursor; return { location, token_t::Comma }; + case '.': ++m_cursor; return { location, token_t::Dot }; + case '+': ++m_cursor; return { location, token_t::Plus }; + case '-': ++m_cursor; return { location, token_t::Minus }; + case '*': ++m_cursor; return { location, token_t::Star }; + case '/': ++m_cursor; return { location, token_t::Slash }; + case '%': ++m_cursor; return { location, token_t::Percent }; case '"': { std::size_t begin = ++m_cursor; while (m_cursor < m_content.size() && m_content[m_cursor] != '"') diff --git a/furc/src/front/parser.cpp b/furc/src/front/parser.cpp index 9b1ce03..7e2b261 100644 --- a/furc/src/front/parser.cpp +++ b/furc/src/front/parser.cpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace furc::front { @@ -121,11 +122,7 @@ ast::node_handle parser::parse_statement() { } ast::node_handle parser::parse_expression() { - const auto& tok = peek_token(); - - auto literal = parse_literal(); - if (literal.present()) return std::move(literal); - return { tok.location(), "unexpected token"s + tok->type + ", expected expression or literal" }; + return parse_expression_rhs(parse_expression_primary(), 16); } ast::node_handle parser::parse_literal() { @@ -149,6 +146,64 @@ ast::node_handle parser::parse_literal() { return { tok.location(), "unexpected token "s + tok->type + ", expected literal" }; } +enum class binop_associativity { + Left, + Right, +}; + +struct binop_info { + ast::binop_expression_node_t type; + std::uint32_t precedence; + binop_associativity associativity; + + static binop_info left(ast::binop_expression_node_t type, std::uint32_t precedence) { + return { type, precedence, binop_associativity::Left }; + } + + static binop_info right(ast::binop_expression_node_t type, std::uint32_t precedence) { + return { type, precedence, binop_associativity::Right }; + } +}; + +ast::expression_node_h parser::parse_expression_primary() { + const auto& tok = peek_token(); + + auto literal = parse_literal(); + if (literal.present()) return std::move(literal); + return { tok.location(), "unexpected token"s + tok->type + ", expected expression or literal" }; +} + +ast::expression_node_h parser::parse_expression_rhs(const ast::expression_node_h& init, std::uint32_t precedence) { + static std::unordered_map s_binops = { + { token_t::Plus, binop_info::left(ast::binop_expression_node_t::Add, 5) }, + { token_t::Minus, binop_info::left(ast::binop_expression_node_t::Sub, 5) }, + { token_t::Star, binop_info::left(ast::binop_expression_node_t::Mul, 4) }, + { token_t::Slash, binop_info::left(ast::binop_expression_node_t::Div, 4) }, + { token_t::Percent, binop_info::left(ast::binop_expression_node_t::Mod, 5) }, + }; + + ast::expression_node_h lhs = init; + while (true) { + auto it = s_binops.find(peek_token()->type); + if (it == s_binops.end()) return lhs; + binop_info current = it->second; + if (current.precedence >= precedence) return lhs; + auto opToken = next_token(); + + auto rhs = parse_expression_primary(); + + auto nextIt = s_binops.find(peek_token()->type); + if (nextIt != s_binops.end()) { + binop_info next = nextIt->second; + + rhs = std::move(parse_expression_rhs(rhs, + current.precedence + static_cast(current.associativity == binop_associativity::Right))); + } + + lhs = ast::binop_expression_node_h{ opToken.location(), m_arena, current.type, std::move(lhs), std::move(rhs) }; + } +} + ast::function_body_handle parser::parse_body() { ast::function_body body; diff --git a/furc/src/main.cpp b/furc/src/main.cpp index cf47972..d32b8d7 100644 --- a/furc/src/main.cpp +++ b/furc/src/main.cpp @@ -5,7 +5,7 @@ #include int main(void) { - furc::front::parser parser("", "func main() {\n return 67;return \"uwu\";\n}"); + furc::front::parser parser("", "func main() {\n return 7 + 6 * 10; // 130\n}"); std::cout << parser.parse() << '\n'; return 0; diff --git a/furc/test/lexer.cpp b/furc/test/lexer.cpp index 867ae11..e29e3fa 100644 --- a/furc/test/lexer.cpp +++ b/furc/test/lexer.cpp @@ -18,17 +18,17 @@ using namespace std::string_view_literals; TEST(Lexer, Tokens) { lexer lexer("", "()\n\t\t{\n}[\"shto-to\"]; :,.main return func"); - EXPECT_TOKEN(lexer, (token{ token_t::Lparen, "("sv })); - EXPECT_TOKEN(lexer, (token{ token_t::Rparen, ")"sv })); - EXPECT_TOKEN(lexer, (token{ token_t::Lbrace, "{"sv })); - EXPECT_TOKEN(lexer, (token{ token_t::Rbrace, "}"sv })); - EXPECT_TOKEN(lexer, (token{ token_t::Lbracket, "["sv })); + EXPECT_TOKEN(lexer, (token{ token_t::Lparen })); + EXPECT_TOKEN(lexer, (token{ token_t::Rparen })); + EXPECT_TOKEN(lexer, (token{ token_t::Lbrace })); + EXPECT_TOKEN(lexer, (token{ token_t::Rbrace })); + EXPECT_TOKEN(lexer, (token{ token_t::Lbracket })); EXPECT_TOKEN(lexer, (token{ token_t::String, "shto-to"sv })); - EXPECT_TOKEN(lexer, (token{ token_t::Rbracket, "]"sv })); - EXPECT_TOKEN(lexer, (token{ token_t::Semicolon, ";"sv })); - EXPECT_TOKEN(lexer, (token{ token_t::Colon, ":"sv })); - EXPECT_TOKEN(lexer, (token{ token_t::Comma, ","sv })); - EXPECT_TOKEN(lexer, (token{ token_t::Dot, "."sv })); + EXPECT_TOKEN(lexer, (token{ token_t::Rbracket })); + EXPECT_TOKEN(lexer, (token{ token_t::Semicolon })); + EXPECT_TOKEN(lexer, (token{ token_t::Colon })); + EXPECT_TOKEN(lexer, (token{ token_t::Comma })); + EXPECT_TOKEN(lexer, (token{ token_t::Dot })); EXPECT_TOKEN(lexer, (token{ token_t::Identifier, "main"sv })); EXPECT_TOKEN(lexer, (token{ keyword_token::Return })); EXPECT_TOKEN(lexer, (token{ keyword_token::Func })); @@ -37,10 +37,10 @@ TEST(Lexer, Tokens) { TEST(Lexer, Comments) { lexer lexer("", "(/** skibidi **/func{//)\n}"); - EXPECT_TOKEN(lexer, (token{ token_t::Lparen, "("sv })); + EXPECT_TOKEN(lexer, (token{ token_t::Lparen, "("sv })); // left out the string-view deliberately EXPECT_TOKEN(lexer, (token{ keyword_token::Func })); - EXPECT_TOKEN(lexer, (token{ token_t::Lbrace, "{"sv })); - EXPECT_TOKEN(lexer, (token{ token_t::Rbrace, "}"sv })); + EXPECT_TOKEN(lexer, (token{ token_t::Lbrace })); + EXPECT_TOKEN(lexer, (token{ token_t::Rbrace })); EXPECT_EOF(lexer); } diff --git a/furc/test/parser.cpp b/furc/test/parser.cpp index f61b43f..172f93a 100644 --- a/furc/test/parser.cpp +++ b/furc/test/parser.cpp @@ -57,4 +57,80 @@ TEST(Parser, Literals) { } } +#define EXPECT_INTLIT(expr, integer) \ + do { \ + EXPECT_EQ((expr)->expression_type(), expression_node_t::Literal); \ + node_handle literal = (expr); \ + EXPECT_EQ(literal->literal_type(), literal_node_t::Integer); \ + node_handle intLit = literal; \ + EXPECT_EQ(*intLit, (integer)); \ + } while (0); + +// TODO: Use arena (I am too exhausted rn to do it) +TEST(Parser, OperatorPrecedence_AddMul) { + parser parser("", "func main() { return 1 + 2 * 3; }"); + auto program = parser.parse(); + EXPECT_TRUE(program.present()); + EXPECT_EQ(program->declarations().size(), 1); + auto func = program->declarations()[0]; + EXPECT_TRUE(func.present()); + EXPECT_EQ(func->declaration_type(), declaration_node_t::FunctionDefinition); + node_handle funcDef = func; + EXPECT_EQ(funcDef->name()->string, "main"); + EXPECT_EQ(funcDef->body()->statements.size(), 1); + node_handle ret = funcDef->body()->statements[0]; + + auto retVal = ret->value(); + EXPECT_TRUE(retVal.present()); + + EXPECT_EQ(retVal->expression_type(), expression_node_t::Binop); + binop_expression_node_h add = retVal; + EXPECT_EQ(add->type(), binop_expression_node_t::Add); + EXPECT_INTLIT(add->lhs(), 1); + + EXPECT_EQ(add->rhs()->expression_type(), expression_node_t::Binop); + binop_expression_node_h mul = add->rhs(); + EXPECT_EQ(mul->type(), binop_expression_node_t::Mul); + EXPECT_INTLIT(mul->lhs(), 2); + EXPECT_INTLIT(mul->rhs(), 3); +} + +TEST(Parser, OperatorPrecedence_Complex) { + parser parser("", "func main() { return 1 + 2 * 3 - 4 / 2; }"); + auto program = parser.parse(); + EXPECT_TRUE(program.present()); + EXPECT_EQ(program->declarations().size(), 1); + auto func = program->declarations()[0]; + EXPECT_TRUE(func.present()); + EXPECT_EQ(func->declaration_type(), declaration_node_t::FunctionDefinition); + node_handle funcDef = func; + EXPECT_EQ(funcDef->name()->string, "main"); + EXPECT_EQ(funcDef->body()->statements.size(), 1); + node_handle ret = funcDef->body()->statements[0]; + + auto retVal = ret->value(); + EXPECT_TRUE(retVal.present()); + + EXPECT_EQ(retVal->expression_type(), expression_node_t::Binop); + binop_expression_node_h sub = retVal; + EXPECT_EQ(sub->type(), binop_expression_node_t::Sub); + + EXPECT_EQ(sub->lhs()->expression_type(), expression_node_t::Binop); + binop_expression_node_h add = sub->lhs(); + EXPECT_EQ(add->type(), binop_expression_node_t::Add); + EXPECT_INTLIT(add->lhs(), 1); + + EXPECT_EQ(add->rhs()->expression_type(), expression_node_t::Binop); + binop_expression_node_h mul = add->rhs(); + EXPECT_EQ(mul->type(), binop_expression_node_t::Mul); + EXPECT_INTLIT(mul->lhs(), 2); + EXPECT_INTLIT(mul->rhs(), 3); + + EXPECT_EQ(sub->rhs()->expression_type(), expression_node_t::Binop); + binop_expression_node_h div = sub->rhs(); + EXPECT_EQ(div->type(), binop_expression_node_t::Div); + EXPECT_INTLIT(div->lhs(), 4); + EXPECT_INTLIT(div->rhs(), 2); +} + } // namespace \ No newline at end of file