From 0b2a12cef7ef962e0c409931a64d2f8735eef368 Mon Sep 17 00:00:00 2001 From: Karma Riuk Date: Tue, 15 Jul 2025 00:56:12 +0200 Subject: [PATCH] added parsing for function literals --- src/ast/expressions/function.cpp | 35 ++++++++ src/ast/expressions/function.hpp | 21 +++++ src/parser/parser.cpp | 38 +++++++++ test/parser/function.cpp | 134 +++++++++++++++++++++++++++++++ 4 files changed, 228 insertions(+) create mode 100644 src/ast/expressions/function.cpp create mode 100644 src/ast/expressions/function.hpp create mode 100644 test/parser/function.cpp diff --git a/src/ast/expressions/function.cpp b/src/ast/expressions/function.cpp new file mode 100644 index 0000000..d831a9e --- /dev/null +++ b/src/ast/expressions/function.cpp @@ -0,0 +1,35 @@ +#include "function.hpp" + +#include + +namespace ast { + function_literal::function_literal(token::token token) + : token(std::move(token)), + block(nullptr) {}; + + std::string function_literal::token_literal() const { + return token.literal; + }; + + std::string function_literal::str() const { + std::stringstream ss; + ss << "fn ("; + bool first = true; + for (auto& param : parameters) { + if (!first) + ss << ", "; + ss << param->str(); + first = false; + } + ss << ")"; + ss << block->str(); + return ss.str(); + }; + + function_literal::~function_literal() { + for (auto& param : parameters) + delete param; + if (block != nullptr) + delete block; + } +} // namespace ast diff --git a/src/ast/expressions/function.hpp b/src/ast/expressions/function.hpp new file mode 100644 index 0000000..d7958d3 --- /dev/null +++ b/src/ast/expressions/function.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "ast/ast.hpp" +#include "ast/expressions/identifier.hpp" +#include "ast/statements/block.hpp" +#include "token/token.hpp" + +#include + +namespace ast { + struct function_literal : expression { + function_literal(token::token); + token::token token; + std::vector parameters; + ast::block_stmt* block; + + std::string token_literal() const override; + std::string str() const override; + ~function_literal(); + }; +} // namespace ast diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index df22326..c5e3568 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -59,6 +59,11 @@ namespace parser { std::bind(&parser::parse_if_then_else, this) ); + register_prefix( + token::type::FUNCTION, + std::bind(&parser::parse_function, this) + ); + using namespace std::placeholders; register_infix( {token::type::PLUS, @@ -243,6 +248,39 @@ namespace parser { ); }; + ast::function_literal* parser::parse_function() { + TRACE_FUNCTION; + ast::function_literal* ret = new ast::function_literal(current); + if (!expect_next(token::type::LPAREN)) { + delete ret; + return nullptr; + } + + while (next.type != token::type::RPAREN + && expect_next(token::type::IDENTIFIER)) { + ret->parameters.push_back(parse_identifier()); + if (next.type == token::type::RPAREN) + break; + if (!expect_next(token::type::COMMA)) { + // delete ret; + return nullptr; + } + } + + if (!expect_next(token::type::RPAREN)) { + // delete ret; + return nullptr; + } + + if (!expect_next(token::type::LBRACE)) { + // delete ret; + return nullptr; + } + ret->block = parse_block(); + + return ret; + }; + ast::prefix_expr* parser::parse_prefix_expr() { TRACE_FUNCTION; ast::prefix_expr* ret = new ast::prefix_expr(current, current.literal); diff --git a/test/parser/function.cpp b/test/parser/function.cpp new file mode 100644 index 0000000..73c5fa1 --- /dev/null +++ b/test/parser/function.cpp @@ -0,0 +1,134 @@ +#include "ast/expressions/function.hpp" + +#include "ast/statements/return.hpp" +#include "utils.hpp" + +#include + +TEST_SUITE("Parser: function") { + TEST_CASE("Malformed if then else (checking for memory leaks)") {} + + TEST_CASE_FIXTURE(ParserFixture, "Parse well formed function literal") { + SUBCASE("no param function") { + setup("\ +fn () {\ + return true;\ +}\ +"); + REQUIRE(program->statements.size() == 1); + CHECK(program->statements[0]->token_literal() == "fn"); + + ast::expression_stmt* expr_stmt = + cast(program->statements[0]); + + ast::function_literal* fun = + cast(expr_stmt->expression); + + // parameters + CHECK(fun->parameters.size() == 0); + + // block + REQUIRE(fun->block->statements.size() == 1); + ast::return_stmt* ret = + cast(fun->block->statements[0]); + test_boolean_literal(ret->value, true); + + // full string + CHECK(fun->str() == "fn (){return true;}"); + } + + SUBCASE("one param function") { + setup("\ +fn (x) {\ + return x + 1;\ +}\ +"); + REQUIRE(program->statements.size() == 1); + CHECK(program->statements[0]->token_literal() == "fn"); + + ast::expression_stmt* expr_stmt = + cast(program->statements[0]); + + ast::function_literal* fun = + cast(expr_stmt->expression); + + // parameters + REQUIRE(fun->parameters.size() == 1); + test_identifier(fun->parameters[0], "x"); + + // block + REQUIRE(fun->block->statements.size() == 1); + ast::return_stmt* ret = + cast(fun->block->statements[0]); + test_infix_expression(ret->value, "x", "+", 1); + + // full string + CHECK(fun->str() == "fn (x){return (x + 1);}"); + } + + SUBCASE("two param function") { + setup("\ +fn (x, y) {\ + return x + y;\ +}\ +"); + REQUIRE(program->statements.size() == 1); + CHECK(program->statements[0]->token_literal() == "fn"); + + ast::expression_stmt* expr_stmt = + cast(program->statements[0]); + + ast::function_literal* fun = + cast(expr_stmt->expression); + + // parameters + REQUIRE(fun->parameters.size() == 2); + test_identifier(fun->parameters[0], "x"); + test_identifier(fun->parameters[1], "y"); + + // block + REQUIRE(fun->block->statements.size() == 1); + ast::return_stmt* ret = + cast(fun->block->statements[0]); + test_infix_expression(ret->value, "x", "+", "y"); + + // full string + CHECK(fun->str() == "fn (x, y){return (x + y);}"); + } + + SUBCASE("two param function assigned to variable") { + setup("\ +let fun = fn (x, y) {\ + return x + y;\ +}\ +"); + REQUIRE(program->statements.size() == 1); + CHECK(program->statements[0]->token_literal() == "let"); + + ast::let_stmt* let_stmt = + cast(program->statements[0]); + + // let lhs + test_identifier(let_stmt->name, "fun"); + + // let rhs + CHECK(let_stmt->value->token_literal() == "fn"); + ast::function_literal* fun = + cast(let_stmt->value); + + // parameters + REQUIRE(fun->parameters.size() == 2); + test_identifier(fun->parameters[0], "x"); + test_identifier(fun->parameters[1], "y"); + + // block + REQUIRE(fun->block->statements.size() == 1); + ast::return_stmt* ret = + cast(fun->block->statements[0]); + test_infix_expression(ret->value, "x", "+", "y"); + + // full string + CHECK(fun->str() == "fn (x, y){return (x + y);}"); + } + } +}