diff --git a/src/ast/expressions/if_then_else.cpp b/src/ast/expressions/if_then_else.cpp new file mode 100644 index 0000000..5648d10 --- /dev/null +++ b/src/ast/expressions/if_then_else.cpp @@ -0,0 +1,23 @@ + +#include "if_then_else.hpp" + +#include + +namespace ast { + if_then_else::if_then_else(token::token token) + : token(std::move(token)), + consequence(nullptr), + alternative(nullptr) {}; + + std::string if_then_else::token_literal() const { + return token.literal; + }; + + std::string if_then_else::str() const { + std::stringstream ss; + ss << "if " << condition->str() << consequence->str(); + if (alternative != nullptr) + ss << "else" << alternative->str(); + return ss.str(); + }; +} // namespace ast diff --git a/src/ast/expressions/if_then_else.hpp b/src/ast/expressions/if_then_else.hpp new file mode 100644 index 0000000..99d0f46 --- /dev/null +++ b/src/ast/expressions/if_then_else.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include "ast/ast.hpp" +#include "ast/statements/block.hpp" +#include "token/token.hpp" + +#include + +namespace ast { + struct if_then_else : expression { + if_then_else(token::token); + token::token token; + ast::expression* condition; + ast::block_stmt *consequence, *alternative; + + std::string token_literal() const override; + std::string str() const override; + }; +} // namespace ast diff --git a/src/ast/statements/block.cpp b/src/ast/statements/block.cpp new file mode 100644 index 0000000..645f537 --- /dev/null +++ b/src/ast/statements/block.cpp @@ -0,0 +1,25 @@ +#include "block.hpp" + +#include + +namespace ast { + block_stmt::block_stmt(token::token token): token(std::move(token)) {} + + std::string block_stmt::token_literal() const { + return token.literal; + } + + block_stmt::~block_stmt() { + for (const auto& stmt : statements) + delete stmt; + }; + + std::string block_stmt::str() const { + std::stringstream ss; + ss << "{"; + for (const auto& stmt : statements) + ss << stmt->str(); + ss << "}"; + return ss.str(); + }; +} // namespace ast diff --git a/src/ast/statements/block.hpp b/src/ast/statements/block.hpp new file mode 100644 index 0000000..58180f8 --- /dev/null +++ b/src/ast/statements/block.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include "ast/ast.hpp" +#include "token/token.hpp" + +#include + +namespace ast { + struct block_stmt : statement { + block_stmt(token::token token); + + token::token token; + std::vector statements; + + std::string token_literal() const override; + std::string str() const override; + + ~block_stmt(); + }; +} // namespace ast diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index 5538a4b..302f5e9 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -3,9 +3,11 @@ #include "ast/errors/error.hpp" #include "ast/expressions/boolean.hpp" #include "ast/expressions/identifier.hpp" +#include "ast/expressions/if_then_else.hpp" #include "ast/expressions/infix.hpp" #include "ast/expressions/integer.hpp" #include "ast/expressions/prefix.hpp" +#include "ast/statements/block.hpp" #include "token/token.hpp" #include "token/type.hpp" #include "utils/tracer.hpp" @@ -51,6 +53,11 @@ namespace parser { std::bind(&parser::parse_grouped_expr, this) ); + register_prefix( + token::type::IF, + std::bind(&parser::parse_if_then_else, this) + ); + using namespace std::placeholders; register_infix( {token::type::PLUS, @@ -256,6 +263,59 @@ namespace parser { return ret; }; + ast::expression* parser::parse_if_then_else() { + // TRACE_FUNCTION; + ast::if_then_else* ret = new ast::if_then_else(current); + if (!expect_next(token::type::LPAREN)) { + delete ret; + return nullptr; + } + + next_token(); + ret->condition = parse_expression(); + + if (!expect_next(token::type::RPAREN)) { + delete ret; + return nullptr; + } + + if (!expect_next(token::type::LBRACE)) { + delete ret; + return nullptr; + } + + ret->consequence = parse_block(); + if (next.type != token::type::ELSE) + return ret; + next_token(); + + if (!expect_next(token::type::LBRACE)) { + delete ret; + return nullptr; + } + + ret->alternative = parse_block(); + return ret; + }; + + ast::block_stmt* parser::parse_block() { + // TRACE_FUNCTION; + ast::block_stmt* ret = new ast::block_stmt(current); + // ret->statements.push_back(parse_statement()); + next_token(); + int i = 0; + while (current.type != token::type::RBRACE && i++ < 10) { + // for (next_token(); next.type != token::type::RBRACE; + // next_token()) { + ast::statement* stmt = parse_statement(); + if (stmt != nullptr) + ret->statements.push_back(stmt); + next_token(); + } + + return ret; + } + ast::expression* parser::parse_infix_expr(ast::expression* left) { // TRACE_FUNCTION; ast::infix_expr* ret = diff --git a/src/parser/parser.hpp b/src/parser/parser.hpp index df33cf1..a873eef 100644 --- a/src/parser/parser.hpp +++ b/src/parser/parser.hpp @@ -3,6 +3,7 @@ #include "ast/ast.hpp" #include "ast/errors/error.hpp" #include "ast/program.hpp" +#include "ast/statements/block.hpp" #include "ast/statements/expression.hpp" #include "ast/statements/let.hpp" #include "ast/statements/return.hpp" @@ -54,6 +55,8 @@ namespace parser { ast::expression* parse_boolean(); ast::expression* parse_prefix_expr(); ast::expression* parse_grouped_expr(); + ast::expression* parse_if_then_else(); + ast::block_stmt* parse_block(); ast::expression* parse_infix_expr(ast::expression*); }; diff --git a/test/parser/if.cpp b/test/parser/if.cpp new file mode 100644 index 0000000..c98b353 --- /dev/null +++ b/test/parser/if.cpp @@ -0,0 +1,94 @@ +#include "ast/expressions/if_then_else.hpp" +#include "ast/expressions/infix.hpp" +#include "ast/statements/block.hpp" +#include "ast/statements/expression.hpp" +#include "utils.hpp" + +#include + +TEST_SUITE("Parser: if") { + TEST_CASE("Malformed if then else (checking for memory leaks)") {} + + TEST_CASE_FIXTURE(ParserFixture, "Parse well formed simple if ") { + setup("\ +if (x > 15) {\ + return x;\ +}\ +"); + REQUIRE(program->statements.size() == 1); + CHECK(program->statements[0]->token_literal() == "if"); + + ast::expression_stmt* expr_stmt = + cast(program->statements[0]); + + ast::if_then_else* if_stmt = + cast(expr_stmt->expression); + + // condition + ast::infix_expr* cond = cast(if_stmt->condition); + test_infix_expression(cond, "x", ">", 15); + CHECK(if_stmt->condition->str() == "(x > 15)"); + + // consequence + REQUIRE(if_stmt->consequence->statements.size() == 1); + ast::return_stmt* ret_stmt = + cast(if_stmt->consequence->statements[0]); + test_identifier(ret_stmt->value, "x"); + CHECK(if_stmt->consequence->str() == "{return x;}"); + + // alternative + CHECK(if_stmt->alternative == nullptr); + + // full string + CHECK(if_stmt->str() == "if (x > 15){return x;}"); + } + + TEST_CASE_FIXTURE(ParserFixture, "Parse well formed if then else") { + setup("\ +if (x < 5) {\ + return 15;\ +} else {\ + let b = 1;\ + return x;\ +}\ +"); + REQUIRE(program->statements.size() == 1); + CHECK(program->statements[0]->token_literal() == "if"); + + ast::expression_stmt* expr_stmt = + cast(program->statements[0]); + + ast::if_then_else* if_stmt = + cast(expr_stmt->expression); + + // condition + ast::infix_expr* cond = cast(if_stmt->condition); + test_infix_expression(cond, "x", "<", 5); + CHECK(if_stmt->condition->str() == "(x < 5)"); + + // consequence + REQUIRE(if_stmt->consequence->statements.size() == 1); + ast::return_stmt* ret_stmt = + cast(if_stmt->consequence->statements[0]); + test_integer_literal(ret_stmt->value, 15); + CHECK(if_stmt->consequence->str() == "{return 15;}"); + + // alternative + REQUIRE(if_stmt->alternative != nullptr); + REQUIRE(if_stmt->alternative->statements.size() == 2); + ast::let_stmt* let_stmt = + cast(if_stmt->alternative->statements[0]); + test_identifier(let_stmt->name, "b"); + test_integer_literal(let_stmt->value, 1); + + ast::return_stmt* ret_stmt2 = + cast(if_stmt->alternative->statements[1]); + test_identifier(ret_stmt2->value, "x"); + CHECK(if_stmt->alternative->str() == "{let b = 1;return x;}"); + + // full string + CHECK( + if_stmt->str() == "if (x < 5){return 15;}else{let b = 1;return x;}" + ); + } +}