diff --git a/docs/Language Specification/bnf.txt b/docs/Language Specification/bnf.txt index e5f082b37f9aa29909426267c078638596fad10e..c29e13874b346e24e99d1d85d1f20c01e83aa56f 100644 --- a/docs/Language Specification/bnf.txt +++ b/docs/Language Specification/bnf.txt @@ -53,10 +53,12 @@ <factor> -> <literal> | <identifier> | <call> + | <method_call> | <list_literal> | '(' <expression> ')' <call> -> <identifier> '(' [<argument_list>] ')' +<method_call> -> <expression> '.' <identifier> '(' [<argument_list>] ')' <argument_list> -> <expression> [',' <expression>]* <list_literal> -> '[' [<expression> [',' <expression>]*] ']' diff --git a/examples/list.funk b/examples/list.funk new file mode 100644 index 0000000000000000000000000000000000000000..40bdfecce926d2839304139c3fadd83a7271cc5b --- /dev/null +++ b/examples/list.funk @@ -0,0 +1 @@ +print([1,2,3,4,5,6,7,8,9].length()); diff --git a/include/ast/expression/CallNode.h b/include/ast/expression/CallNode.h index 1491c28c7ccce6c878adca71b4b7c6fb24577cd4..0cf21d0c3440c209c4290510e31454c112f78722 100644 --- a/include/ast/expression/CallNode.h +++ b/include/ast/expression/CallNode.h @@ -20,7 +20,7 @@ public: NodeValue get_value() const override; -private: +protected: Token identifier; Vector<ExpressionNode*> args; }; diff --git a/include/ast/expression/ListNode.h b/include/ast/expression/ListNode.h new file mode 100644 index 0000000000000000000000000000000000000000..ad2e3d4ed03e84f341c45e473aeba789f25d6d00 --- /dev/null +++ b/include/ast/expression/ListNode.h @@ -0,0 +1,24 @@ +#pragma once +#include "ast/expression/ExpressionNode.h" +#include "logging/LogMacros.h" +#include "token/Token.h" + +namespace funk +{ +class ListNode : public ExpressionNode +{ +public: + ListNode(const SourceLocation& location, const TokenType& type, const Vector<ExpressionNode*>& elements); + ~ListNode() override; + + Node* evaluate() const override; + String to_s() const override; + NodeValue get_value() const override; + + size_t length() const; + +private: + const TokenType type; + Vector<ExpressionNode*> elements; +}; +} // namespace funk diff --git a/include/ast/expression/MethodCallNode.h b/include/ast/expression/MethodCallNode.h new file mode 100644 index 0000000000000000000000000000000000000000..52427452ff81c17d9837fdbbfce47106815e036c --- /dev/null +++ b/include/ast/expression/MethodCallNode.h @@ -0,0 +1,21 @@ +#pragma once +#include "ast/expression/CallNode.h" +#include "ast/expression/ListNode.h" +#include "logging/LogMacros.h" + +namespace funk +{ +class MethodCallNode : public CallNode +{ +public: + MethodCallNode(ExpressionNode* object, const Token& method, const Vector<ExpressionNode*>& args); + ~MethodCallNode() override; + + Node* evaluate() const override; + String to_s() const override; + NodeValue get_value() const override; + +private: + ExpressionNode* object; +}; +} // namespace funk diff --git a/include/parser/Parser.h b/include/parser/Parser.h index f08c405cfeb126866e9cd803818d3869a9a77534..5fc8142e5d6f407d680a4a06ac4f292c0c8e6067 100644 --- a/include/parser/Parser.h +++ b/include/parser/Parser.h @@ -11,9 +11,12 @@ #include "ast/control/IfNode.h" #include "ast/control/WhileNode.h" #include "ast/declaration/DeclarationNode.h" +#include "ast/declaration/VariableNode.h" #include "ast/expression/BinaryOpNode.h" #include "ast/expression/CallNode.h" +#include "ast/expression/ListNode.h" #include "ast/expression/LiteralNode.h" +#include "ast/expression/MethodCallNode.h" #include "ast/expression/UnaryOpNode.h" #include "lexer/Lexer.h" #include "logging/LogMacros.h" @@ -213,5 +216,23 @@ private: * @return Node* The AST node representing the identifier */ Node* parse_identifier(); + + /** + * @brief Parses a call expression + * @return Node* The AST node representing the call expression + */ + Node* parse_call(const Token& identifier); + + /** + * @brief Parses a method call expression + * @return Node* The AST node representing the method call expression + */ + Node* parse_method_call(ExpressionNode* object); + + /** + * @brief Parses a list expression + * @return Node* The AST node representing the list expression + */ + Node* parse_list(); }; } // namespace funk diff --git a/source/ast/expression/ListNode.cc b/source/ast/expression/ListNode.cc new file mode 100644 index 0000000000000000000000000000000000000000..a09e44c35e355dc5cd0cfc5d88fd5fe8c8a3af3e --- /dev/null +++ b/source/ast/expression/ListNode.cc @@ -0,0 +1,43 @@ +#include "ast/expression/ListNode.h" + +namespace funk +{ +ListNode::ListNode(const SourceLocation& location, const TokenType& type, const Vector<ExpressionNode*>& elements) : + ExpressionNode(location), type(type), elements(elements) +{ +} + +ListNode::~ListNode() +{ + for (Node* element : elements) { delete element; } +} + +Node* ListNode::evaluate() const +{ + return const_cast<ListNode*>(this); +} + +String ListNode::to_s() const +{ + String result{"[ "}; + for (size_t i{0}; i < elements.size(); i++) + { + result += elements[i]->to_s(); + if (i < elements.size() - 1) { result += ", "; } + } + result += " ]"; + return result; +} + +NodeValue ListNode::get_value() const +{ + ExpressionNode* result{dynamic_cast<ExpressionNode*>(evaluate())}; + if (!result) { throw RuntimeError(location, "List did not evaluate to an expression"); } + return result->get_value(); +} + +size_t ListNode::length() const +{ + return elements.size(); +} +} // namespace funk diff --git a/source/ast/expression/MethodCallNode.cc b/source/ast/expression/MethodCallNode.cc new file mode 100644 index 0000000000000000000000000000000000000000..2a4afbca0072bd07e57f83887e123a3716de05a4 --- /dev/null +++ b/source/ast/expression/MethodCallNode.cc @@ -0,0 +1,57 @@ +#include "ast/expression/MethodCallNode.h" + +namespace funk +{ +MethodCallNode::MethodCallNode(ExpressionNode* object, const Token& method, const Vector<ExpressionNode*>& args) : + CallNode(method, args), object(object) +{ +} + +MethodCallNode::~MethodCallNode() +{ + delete object; + CallNode::~CallNode(); +} + +Node* MethodCallNode::evaluate() const +{ + LOG_DEBUG("Evaluating method call " + identifier.get_lexeme() + " on " + object->to_s()); + + Node* evaluated_object{object->evaluate()}; + if (!evaluated_object) { throw RuntimeError(location, "Failed to evaluate object for method call"); } + + if (auto list_node = dynamic_cast<ListNode*>(evaluated_object)) + { + if (identifier.get_lexeme() == "length") + { + return new LiteralNode(location, static_cast<int>(list_node->length())); + } + } + + throw RuntimeError( + location, "Unknown method '" + identifier.get_lexeme() + "' for object " + evaluated_object->to_s()); +} + +String MethodCallNode::to_s() const +{ + String result{object->to_s()}; + result += "." + identifier.get_lexeme() + "( "; + + for (size_t i{0}; i < args.size(); i++) + { + result += args[i]->to_s(); + if (i < args.size() - 1) { result += ", "; } + } + + result += " )"; + return result; +} + +NodeValue MethodCallNode::get_value() const +{ + ExpressionNode* result{dynamic_cast<ExpressionNode*>(evaluate())}; + if (!result) { throw RuntimeError(location, "Method call did not evaluate to an expression"); } + + return result->get_value(); +} +} // namespace funk diff --git a/source/parser/Parser.cc b/source/parser/Parser.cc index 6cb9c26acfd130b967412016effbabe4f420de98..eb66ad07773bfd2a15ee1526ae67accf0a8d60c6 100644 --- a/source/parser/Parser.cc +++ b/source/parser/Parser.cc @@ -288,28 +288,25 @@ Node* Parser::parse_unary() Node* Parser::parse_factor() { LOG_DEBUG("Parse factor"); - if (check(TokenType::IDENTIFIER)) { return parse_identifier(); } + Node* expr = nullptr; + + if (check(TokenType::IDENTIFIER)) { expr = parse_identifier(); } else if (check(TokenType::NUMB) || check(TokenType::REAL) || check(TokenType::BOOL) || check(TokenType::CHAR) || check(TokenType::TEXT)) { - return parse_literal(); + expr = parse_literal(); } else if (match(TokenType::L_PAR)) { - Node* expr{parse_expression()}; - - if (!match(TokenType::R_PAR)) - { - Token prev{peek_prev()}; - SourceLocation loc{prev.get_location()}; - SourceLocation error_loc(loc.filename, loc.line, static_cast<int>(loc.column + prev.get_lexeme().length())); - throw SyntaxError(error_loc, "Expected ')'"); - } - - return expr; + expr = parse_expression(); + if (!match(TokenType::R_PAR)) { throw SyntaxError(peek().get_location(), "Expected ')'"); } } + else if (check(TokenType::L_BRACKET)) { expr = parse_list(); } + else { throw SyntaxError(peek().get_location(), "Expected expression, got " + token_type_to_s(peek().get_type())); } + + while (match(TokenType::DOT)) { expr = parse_method_call(dynamic_cast<ExpressionNode*>(expr)); } - throw SyntaxError(peek_prev().get_location(), "Expected expression, got " + token_type_to_s(peek().get_type())); + return expr; } Node* Parser::parse_literal() @@ -324,20 +321,74 @@ Node* Parser::parse_identifier() LOG_DEBUG("Parse identifier"); Token identifier{next()}; - if (match(TokenType::L_PAR)) + if (check(TokenType::L_PAR)) { return parse_call(identifier); } + else if (match(TokenType::DOT)) { - Vector<ExpressionNode*> arguments{}; - if (!check(TokenType::R_PAR)) - { - do { - arguments.push_back(dynamic_cast<ExpressionNode*>(parse_expression())); - } while (match(TokenType::COMMA)); - } - if (!match(TokenType::R_PAR)) { throw SyntaxError(peek().get_location(), "Expected ')'"); } - return new CallNode(identifier, arguments); + return parse_method_call( + new VariableNode(identifier.get_location(), identifier.get_lexeme(), false, TokenType::NONE, nullptr)); } - return nullptr; + return new VariableNode(identifier.get_location(), identifier.get_lexeme(), false, TokenType::NONE, nullptr); +} + +Node* Parser::parse_call(const Token& identifier) +{ + LOG_DEBUG("Parse call"); + + if (!match(TokenType::L_PAR)) { throw SyntaxError(peek().get_location(), "Expected '('"); } + + Vector<ExpressionNode*> arguments{}; + if (!check(TokenType::R_PAR)) + { + do { + arguments.push_back(dynamic_cast<ExpressionNode*>(parse_expression())); + } while (match(TokenType::COMMA)); + } + if (!match(TokenType::R_PAR)) { throw SyntaxError(peek().get_location(), "Expected ')'"); } + return new CallNode(identifier, arguments); +} + +Node* Parser::parse_method_call(ExpressionNode* object) +{ + LOG_DEBUG("Parse method call"); + if (!check(TokenType::IDENTIFIER)) { throw SyntaxError(peek().get_location(), "Expected method name after '.'"); } + + Token method{next()}; + + if (!match(TokenType::L_PAR)) { throw SyntaxError(peek().get_location(), "Expected '(' after method name"); } + + Vector<ExpressionNode*> arguments{}; + if (!check(TokenType::R_PAR)) + { + do { + arguments.push_back(dynamic_cast<ExpressionNode*>(parse_expression())); + } while (match(TokenType::COMMA)); + } + + if (!match(TokenType::R_PAR)) { throw SyntaxError(peek().get_location(), "Expected ')' after method arguments"); } + + return new MethodCallNode(object, method, arguments); +} + +Node* Parser::parse_list() +{ + LOG_DEBUG("Parse list"); + if (!match(TokenType::L_BRACKET)) { throw SyntaxError(peek().get_location(), "Expected '['"); } + + Vector<ExpressionNode*> elements{}; + TokenType type{TokenType::NONE}; + + if (!check(TokenType::R_BRACKET)) + { + do { + if (type == TokenType::NONE) { type = peek().get_type(); } + else if (type != peek().get_type()) { throw SyntaxError(peek().get_location(), "Inconsistent list types"); } + elements.push_back(dynamic_cast<ExpressionNode*>(parse_expression())); + } while (match(TokenType::COMMA)); + } + + if (!match(TokenType::R_BRACKET)) { throw SyntaxError(peek().get_location(), "Expected ']'"); } + return new ListNode(peek_prev().get_location(), type, elements); } } // namespace funk