From a016bbaa5e15103feb589ccad8b3b041c8b562d5 Mon Sep 17 00:00:00 2001 From: Karma Riuk Date: Tue, 15 Jul 2025 19:42:43 +0200 Subject: [PATCH] added parsing of function call --- src/ast/expressions/function.cpp | 31 +++++++++++ src/ast/expressions/function.hpp | 11 ++++ src/parser/parser.cpp | 28 ++++++++++ src/parser/parser.hpp | 1 + src/parser/precedence.cpp | 2 + test/parser/function.cpp | 96 +++++++++++++++++++++++++++++++- 6 files changed, 168 insertions(+), 1 deletion(-) diff --git a/src/ast/expressions/function.cpp b/src/ast/expressions/function.cpp index 9b21ac0..92e921c 100644 --- a/src/ast/expressions/function.cpp +++ b/src/ast/expressions/function.cpp @@ -32,4 +32,35 @@ namespace ast { if (body != nullptr) delete body; } + + // ---------------------------- FUNCTION CALL ---------------------------- + function_call::function_call(token::token token, expression* target) + : token(std::move(token)), + target(target) {}; + + std::string function_call::token_literal() const { + return token.literal; + }; + + std::string function_call::str() const { + std::stringstream ss; + ss << target->str() << "("; + bool first = true; + for (auto& param : parameters) { + if (!first) + ss << ", "; + ss << param->str(); + first = false; + } + ss << ")"; + return ss.str(); + }; + + function_call::~function_call() { + if (target != nullptr) + delete target; + for (auto& param : parameters) + delete param; + } + } // namespace ast diff --git a/src/ast/expressions/function.hpp b/src/ast/expressions/function.hpp index 1d7052c..b98a80e 100644 --- a/src/ast/expressions/function.hpp +++ b/src/ast/expressions/function.hpp @@ -18,4 +18,15 @@ namespace ast { std::string str() const override; ~function_literal(); }; + + struct function_call : expression { + function_call(token::token, expression*); + token::token token; + expression* target; + std::vector parameters; + + std::string token_literal() const override; + std::string str() const override; + ~function_call(); + }; } // namespace ast diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index db3ae6b..a9b49e1 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -80,6 +80,11 @@ namespace parser { token::type::LT}, std::bind(&parser::parse_infix_expr, this, _1) ); + + register_infix( + token::type::LPAREN, + std::bind(&parser::parse_function_call, this, _1) + ); } void parser::next_token() { @@ -398,4 +403,27 @@ namespace parser { ret->right = parse_expression(prec); return ret; }; + + ast::function_call* parser::parse_function_call(ast::expression* target) { + TRACE_FUNCTION; + ast::function_call* ret = new ast::function_call(current, target); + next_token(); + if (current.type == token::type::RPAREN) + return ret; + + ret->parameters.push_back(parse_expression()); + + // parameters + while (next.type == token::type::COMMA) { + next_token(); + next_token(); + ret->parameters.push_back(parse_expression()); + } + if (!expect_next(token::type::RPAREN)) { + delete ret; + return nullptr; + } + + return ret; + }; } // namespace parser diff --git a/src/parser/parser.hpp b/src/parser/parser.hpp index 0269884..37977a8 100644 --- a/src/parser/parser.hpp +++ b/src/parser/parser.hpp @@ -68,5 +68,6 @@ namespace parser { ast::block_stmt* parse_block(); ast::infix_expr* parse_infix_expr(ast::expression*); + ast::function_call* parse_function_call(ast::expression*); }; } // namespace parser diff --git a/src/parser/precedence.cpp b/src/parser/precedence.cpp index 6c43623..e2d99b5 100644 --- a/src/parser/precedence.cpp +++ b/src/parser/precedence.cpp @@ -15,6 +15,8 @@ namespace parser { case token::type::ASTERISK: case token::type::SLASH: return precedence::PRODUCT; + case token::type::LPAREN: + return precedence::CALL; default: return precedence::LOWEST; } diff --git a/test/parser/function.cpp b/test/parser/function.cpp index bd0971b..5d4d400 100644 --- a/test/parser/function.cpp +++ b/test/parser/function.cpp @@ -5,7 +5,7 @@ #include -TEST_SUITE("Parser: function") { +TEST_SUITE("Parser: function literal") { TEST_CASE("Malformed function literal (checking for memory leaks)") { SUBCASE("Missing opening paren no param") { test_failing_parsing("fn ) { return 2; }", {token::type::LPAREN}); @@ -202,3 +202,97 @@ let fun = fn (x, y) {\ } } } + +TEST_SUITE("Parser: function call") { + TEST_CASE("Malformed function call (checking for memory leaks)") {} + + TEST_CASE_FIXTURE(ParserFixture, "Parse well formed function call") { + SUBCASE("no param function") { + setup("value();"); + REQUIRE(program->statements.size() == 1); + + ast::expression_stmt* expr_stmt = + cast(program->statements[0]); + + ast::function_call* fun = + cast(expr_stmt->expression); + + // target + test_identifier(fun->target, "value"); + + // parameters + CHECK(fun->parameters.size() == 0); + + // full string + CHECK(fun->str() == "value()"); + } + + SUBCASE("one param identifier function") { + setup("is_odd(1);"); + REQUIRE(program->statements.size() == 1); + + ast::expression_stmt* expr_stmt = + cast(program->statements[0]); + + ast::function_call* fun = + cast(expr_stmt->expression); + + // target + test_identifier(fun->target, "is_odd"); + + // parameters + CHECK(fun->parameters.size() == 1); + test_integer_literal(fun->parameters[0], 1); + + // full string + CHECK(fun->str() == "is_odd(1)"); + } + + SUBCASE("two param function") { + setup("is_gt(1, a + 10);"); + REQUIRE(program->statements.size() == 1); + + ast::expression_stmt* expr_stmt = + cast(program->statements[0]); + + ast::function_call* fun = + cast(expr_stmt->expression); + + // target + test_identifier(fun->target, "is_gt"); + + // parameters + CHECK(fun->parameters.size() == 2); + test_integer_literal(fun->parameters[0], 1); + test_infix_expression(fun->parameters[1], "a", "+", 10); + + // full string + CHECK(fun->str() == "is_gt(1, (a + 10))"); + } + + SUBCASE("two param identifier function assigned to variable") { + setup("let res = add(1, a + 10, 2 * 3);"); + REQUIRE(program->statements.size() == 1); + + ast::let_stmt* let_stmt = + cast(program->statements[0]); + + test_identifier(let_stmt->name, "res"); + + ast::function_call* fun = cast(let_stmt->value); + + // target + test_identifier(fun->target, "add"); + + // parameters + CHECK(fun->parameters.size() == 3); + test_integer_literal(fun->parameters[0], 1); + test_infix_expression(fun->parameters[1], "a", "+", 10); + test_infix_expression(fun->parameters[2], 2, "*", 3); + + // full string + CHECK(fun->str() == "add(1, (a + 10), (2 * 3))"); + CHECK(let_stmt->str() == "let res = add(1, (a + 10), (2 * 3));"); + } + } +}