From 4abeb5f8a6ad77b32496f3e8b20e1fd1b6f428fb Mon Sep 17 00:00:00 2001
From: Ferenc Szontágh <szf@fsociety.hu>
Date: Fri, 18 Apr 2025 07:54:40 +0000
Subject: [PATCH] function checking

---
 src/Parser/Parser.cpp                 |   63 ++++++++++++++++++--
 .gitignore                            |    4 +
 src/Parser/Parser.hpp                 |   21 +++++-
 src/Interpreter/OperationsFactory.hpp |   19 ++++++
 .env                                  |    1 
 src/Interpreter/Interpreter.hpp       |   11 +++
 .gitsecret/paths/mapping.cfg          |    0 
 src/Symbols/Value.hpp                 |    3 
 src/VoidScript.hpp                    |    7 --
 src/Parser/ParsedExpression.hpp       |    4 
 src/Symbols/SymbolTable.hpp           |    4 
 src/Interpreter/Operation.hpp         |    7 +-
 src/Symbols/SymbolContainer.hpp       |    6 +
 13 files changed, 117 insertions(+), 33 deletions(-)

diff --git a/.env b/.env
new file mode 100644
index 0000000..0ba4e9e
--- /dev/null
+++ b/.env
@@ -0,0 +1 @@
+OPENAI_API_KEY=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
diff --git a/.gitignore b/.gitignore
index 09f2655..975043a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,4 +21,6 @@
 *.app
 .vscode
 .cache
-build
\ No newline at end of file
+build
+.gitsecret/keys/random_seed
+!*.secret
diff --git a/.gitsecret/paths/mapping.cfg b/.gitsecret/paths/mapping.cfg
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/.gitsecret/paths/mapping.cfg
diff --git a/src/Interpreter/Interpreter.hpp b/src/Interpreter/Interpreter.hpp
index cb617c8..b8e2ce8 100644
--- a/src/Interpreter/Interpreter.hpp
+++ b/src/Interpreter/Interpreter.hpp
@@ -6,6 +6,7 @@
 
 #include "Interpreter/Operation.hpp"
 #include "Interpreter/OperationContainer.hpp"
+#include "Symbols/SymbolContainer.hpp"
 
 namespace Interpreter {
 
@@ -38,7 +39,13 @@
                     break;
                 }
 
-            case Operations::Type::FunctionCall:
+            case Operations::Type::FunctionCall: {
+                // Check that the called function is defined in the symbol table
+                if (!Symbols::SymbolContainer::instance()->exists(op.targetName)) {
+                    throw std::runtime_error("Function not declared: " + op.targetName);
+                }
+                break;
+            }
             case Operations::Type::Return:
             case Operations::Type::Loop:
             case Operations::Type::Break:
@@ -47,7 +54,7 @@
             case Operations::Type::Import:
             case Operations::Type::Error:
             case Operations::Type::Conditional:
-                // TODO: implementálható később
+                // TODO: implement these operations later
                 break;
             default:
                 throw std::runtime_error("Not implemented operation type");
diff --git a/src/Interpreter/Operation.hpp b/src/Interpreter/Operation.hpp
index 8804075..7b0f88d 100644
--- a/src/Interpreter/Operation.hpp
+++ b/src/Interpreter/Operation.hpp
@@ -6,7 +6,7 @@
 #include <string>
 #include <unordered_map>
 
-#include "Interpreter/StatementNode.hpp"
+#include "StatementNode.hpp"
 
 namespace Operations {
 enum class Type : std::uint8_t {
@@ -62,8 +62,9 @@
     }
 
     std::string toString() const {
-        return "Target: " + targetName + " Type: " + this->typeToString() + " Statement: " + statement->toString();
-     }
+        return "Target: " + targetName + " Type: " + this->typeToString() +
+               " Statement: " + ((statement == nullptr) ? "no statement" : statement->toString());
+    }
 };
 };  // namespace Operations
 #endif
diff --git a/src/Interpreter/OperationsFactory.hpp b/src/Interpreter/OperationsFactory.hpp
index 5adbaf7..2dc39af 100644
--- a/src/Interpreter/OperationsFactory.hpp
+++ b/src/Interpreter/OperationsFactory.hpp
@@ -52,6 +52,25 @@
                                               Operations::Container::instance()->add(
                                                   ns, Operations::Operation{Operations::Type::Declaration, varName, std::move(stmt)});
                                           }
+    
+    /**
+     * @brief Record a function call operation for later detection.
+     * @param functionName Name of the function being called.
+     * @param ns Current namespace scope.
+     * @param fileName Source filename.
+     * @param line Line number of call.
+     * @param column Column number of call.
+     */
+    static void callFunction(const std::string & functionName,
+                             const std::string & ns,
+                             const std::string & fileName,
+                             int line,
+                             size_t column) {
+        // No associated StatementNode; this is for detection only
+        Operations::Container::instance()->add(
+            ns,
+            Operations::Operation{Operations::Type::FunctionCall, functionName, nullptr});
+    }
 };
 
 }  // namespace Interpreter
diff --git a/src/Parser/ParsedExpression.hpp b/src/Parser/ParsedExpression.hpp
index 7d7c8fb..efc501f 100644
--- a/src/Parser/ParsedExpression.hpp
+++ b/src/Parser/ParsedExpression.hpp
@@ -4,8 +4,8 @@
 #include <memory>
 #include <string>
 
-#include "Symbols/SymbolContainer.hpp"
-#include "Symbols/Value.hpp"
+#include "../Symbols/SymbolContainer.hpp"
+#include "../Symbols/Value.hpp"
 
 namespace Parser {
 
diff --git a/src/Parser/Parser.cpp b/src/Parser/Parser.cpp
index 9e8ad75..b59b193 100644
--- a/src/Parser/Parser.cpp
+++ b/src/Parser/Parser.cpp
@@ -1,4 +1,5 @@
 #include "Parser/Parser.hpp"
+
 #include <stack>
 
 #include "Interpreter/OperationsFactory.hpp"
@@ -108,6 +109,34 @@
     parseFunctionBody(opening_brace, func_name, func_return_type, param_infos);
 }
 
+// Parse a top-level function call, e.g., foo(arg1, arg2);
+void Parser::parseCallStatement() {
+    // Function name
+    auto        id_token  = expect(Lexer::Tokens::Type::IDENTIFIER);
+    std::string func_name = id_token.value;
+    // Opening parenthesis
+    expect(Lexer::Tokens::Type::PUNCTUATION, "(");
+    // Parse comma-separated argument expressions
+    std::vector<ParsedExpressionPtr> args;
+    if (!(currentToken().type == Lexer::Tokens::Type::PUNCTUATION && currentToken().value == ")")) {
+        while (true) {
+            // Parse expression with no expected type
+            auto expr = parseParsedExpression(Symbols::Variables::Type::NULL_TYPE);
+            args.push_back(std::move(expr));
+            if (match(Lexer::Tokens::Type::PUNCTUATION, ",")) {
+                continue;
+            }
+            break;
+        }
+    }
+    // Closing parenthesis and semicolon
+    expect(Lexer::Tokens::Type::PUNCTUATION, ")");
+    expect(Lexer::Tokens::Type::PUNCTUATION, ";");
+    // Record the function call operation
+    Interpreter::OperationsFactory::callFunction(func_name, Symbols::SymbolContainer::instance()->currentScopeName(),
+                                                 this->current_filename_, id_token.line_number, id_token.column_number);
+}
+
 Symbols::Value Parser::parseNumericLiteral(const std::string & value, bool is_negative, Symbols::Variables::Type type) {
     try {
         switch (type) {
@@ -175,7 +204,7 @@
     if (startIt != tokens_.end() && endIt != tokens_.end() && startIt < endIt) {
         filtered_tokens = std::vector<Lexer::Tokens::Token>(startIt + 1, endIt);
     }
-    auto len = closing_brace.start_pos - opening_brace.end_pos;
+    auto             len          = closing_brace.start_pos - opening_brace.end_pos;
     std::string_view input_string = input_str_view_.substr(opening_brace.end_pos, len);
 
     current_token_index_ = tokenIndex;
@@ -210,7 +239,23 @@
             consumeToken();
             expect_unary = true;
         } else if (token.type == Lexer::Tokens::Type::PUNCTUATION && token.lexeme == ")") {
+            // Only handle grouping parentheses if a matching "(" exists on the operator stack
+            std::stack<std::string> temp_stack = operator_stack;
+            bool has_paren = false;
+            while (!temp_stack.empty()) {
+                if (temp_stack.top() == "(") {
+                    has_paren = true;
+                    break;
+                }
+                temp_stack.pop();
+            }
+            if (!has_paren) {
+                // End of this expression context; do not consume call-closing parenthesis here
+                break;
+            }
+            // Consume the grouping closing parenthesis
             consumeToken();
+            // Unwind operators until the matching "(" is found
             while (!operator_stack.empty() && operator_stack.top() != "(") {
                 std::string op = operator_stack.top();
                 operator_stack.pop();
@@ -233,11 +278,11 @@
                     output_queue.push_back(Lexer::applyOperator(op, std::move(rhs), std::move(lhs)));
                 }
             }
-
             if (operator_stack.empty() || operator_stack.top() != "(") {
-                reportError("Mismatched parentheses");
+                Parser::reportError("Mismatched parentheses", token);
             }
-            operator_stack.pop();  // remove "("
+            // Pop the matching "("
+            operator_stack.pop();
             expect_unary = false;
         } else if (token.type == Lexer::Tokens::Type::OPERATOR_ARITHMETIC) {
             std::string op = std::string(token.lexeme);
@@ -254,14 +299,14 @@
 
                     if (top == "u-" || top == "u+") {
                         if (output_queue.empty()) {
-                            reportError("Missing operand for unary operator");
+                            Parser::reportError("Missing operand for unary operator", token);
                         }
                         auto rhs = std::move(output_queue.back());
                         output_queue.pop_back();
                         output_queue.push_back(Lexer::applyOperator(top, std::move(rhs)));
                     } else {
                         if (output_queue.size() < 2) {
-                            reportError("Malformed expression");
+                            Parser::reportError("Malformed expression", token);
                         }
                         auto rhs = std::move(output_queue.back());
                         output_queue.pop_back();
@@ -281,7 +326,7 @@
                    token.type == Lexer::Tokens::Type::KEYWORD ||
                    token.type == Lexer::Tokens::Type::VARIABLE_IDENTIFIER) {
             if (Lexer::pushOperand(token, expected_var_type, output_queue) == false) {
-                reportError("Expected literal or variable");
+                Parser::reportError("Invalid type", token, "literal or variable");
             }
             consumeToken();
             expect_unary = false;
@@ -296,12 +341,13 @@
         operator_stack.pop();
 
         if (op == "(" || op == ")") {
-            reportError("Mismatched parentheses");
+            Parser::reportError("Mismatched parentheses", tokens_[current_token_index_]);
         }
 
         if (op == "u-" || op == "u+") {
             if (output_queue.empty()) {
                 reportError("Missing operand for unary operator");
+                Parser::reportError("Invalid type", tokens_[current_token_index_], "literal or variable");
             }
             auto rhs = std::move(output_queue.back());
             output_queue.pop_back();
@@ -309,6 +355,7 @@
         } else {
             if (output_queue.size() < 2) {
                 reportError("Malformed expression");
+                Parser::reportError("Mailformed expression", tokens_[current_token_index_]);
             }
             auto rhs = std::move(output_queue.back());
             output_queue.pop_back();
diff --git a/src/Parser/Parser.hpp b/src/Parser/Parser.hpp
index 5996bab..af087c6 100644
--- a/src/Parser/Parser.hpp
+++ b/src/Parser/Parser.hpp
@@ -62,7 +62,7 @@
             // Technically we should never reach this if parseScript's loop is correct
             // But it's useful as a safety check
             if (!tokens_.empty() && tokens_.back().type == Lexer::Tokens::Type::END_OF_FILE) {
-            return tokens_.back();  // return the EOF token
+                return tokens_.back();  // return the EOF token
             }
             throw std::runtime_error("Unexpected end of token stream reached.");
         }
@@ -152,13 +152,18 @@
                (current_token_index_ == tokens_.size() - 1 && tokens_.back().type == Lexer::Tokens::Type::END_OF_FILE);
     }
 
-    [[noreturn]] void reportError(const std::string & message, const std::string& expected = "") {
+    [[noreturn]] void reportError(const std::string & message, const std::string & expected = "") {
         if (current_token_index_ < tokens_.size()) {
             throw Exception(message, expected, tokens_[current_token_index_]);
         }
         int line = tokens_.empty() ? 0 : tokens_.back().line_number;
         int col  = tokens_.empty() ? 0 : tokens_.back().column_number;
         throw Exception(message, expected, line, col);
+    }
+
+    [[noreturn]] static void reportError(const std::string & message, const Lexer::Tokens::Token & token,
+                                         const std::string & expected = "") {
+        throw Exception(message, expected, token);
     }
 
     // parseStatement (unchanged)
@@ -175,12 +180,20 @@
             parseVariableDefinition();
             return;
         }
+        // Function call if identifier followed by '('
+        if (currentToken().type == Lexer::Tokens::Type::IDENTIFIER &&
+            peekToken().type == Lexer::Tokens::Type::PUNCTUATION && peekToken().value == "(") {
+            parseCallStatement();
+            return;
+        }
 
         reportError("Unexpected token at beginning of statement");
     }
 
     void parseVariableDefinition();
     void parseFunctionDefinition();
+    // Parse a top-level function call statement (e.g., foo(arg1, arg2);)
+    void parseCallStatement();
 
     // --- Parsing helper functions ---
 
@@ -189,7 +202,7 @@
     Symbols::Variables::Type parseType() {
         const auto & token = currentToken();
         // Direct lookup for type keyword
-        auto it = Parser::variable_types.find(token.type);
+        auto         it    = Parser::variable_types.find(token.type);
         if (it != Parser::variable_types.end()) {
             consumeToken();
             return it->second;
@@ -201,7 +214,7 @@
         Lexer::Tokens::Token token       = currentToken();
         bool                 is_negative = false;
 
-    // Handle unary sign
+        // Handle unary sign
         if (token.type == Lexer::Tokens::Type::OPERATOR_ARITHMETIC && (token.lexeme == "-" || token.lexeme == "+") &&
             peekToken().type == Lexer::Tokens::Type::NUMBER) {
             is_negative = (token.lexeme == "-");
diff --git a/src/Symbols/SymbolContainer.hpp b/src/Symbols/SymbolContainer.hpp
index fc3fc5d..99f79b3 100644
--- a/src/Symbols/SymbolContainer.hpp
+++ b/src/Symbols/SymbolContainer.hpp
@@ -1,6 +1,7 @@
 #ifndef SYMBOL_CONTAINER_HPP
 #define SYMBOL_CONTAINER_HPP
 
+#include <iostream>
 #include <memory>
 #include <stdexcept>
 #include <unordered_map>
@@ -33,7 +34,8 @@
     }
 
     void enter(const std::string & name) {
-        if (scopes_.contains(name)) {
+        auto it = scopes_.find(name);
+        if (it != scopes_.end()) {
             previousScope_ = currentScope_;
             currentScope_  = name;
         } else {
@@ -104,7 +106,7 @@
     }
 
     static std::string dump() {
-        std::string result = "";
+        std::string result;
 
         std::cout << "\n--- Defined Scopes ---" << '\n';
         for (const auto & scope_name : instance()->getScopeNames()) {
diff --git a/src/Symbols/SymbolTable.hpp b/src/Symbols/SymbolTable.hpp
index 7295bc6..72d8b69 100644
--- a/src/Symbols/SymbolTable.hpp
+++ b/src/Symbols/SymbolTable.hpp
@@ -2,7 +2,7 @@
 #define SYMBOL_TABLE_HPP
 
 #include <vector>
-
+#include <string>
 #include "SymbolTypes.hpp"
 
 namespace Symbols {
@@ -45,7 +45,7 @@
     std::vector<SymbolPtr> listAll(const std::string & prefix = "") const {
         std::vector<SymbolPtr> result;
         for (const auto & [ns, map] : symbols_) {
-            if (prefix.empty() || ns.starts_with(prefix)) {
+            if (prefix.empty() || ns.substr(0,prefix.length()) == prefix) {
                 for (const auto & [_, sym] : map) {
                     result.push_back(sym);
                 }
diff --git a/src/Symbols/Value.hpp b/src/Symbols/Value.hpp
index a0ff38b..fe202ea 100644
--- a/src/Symbols/Value.hpp
+++ b/src/Symbols/Value.hpp
@@ -2,12 +2,11 @@
 #define SYMBOL_VALUE_HPP
 
 #include <algorithm>
-#include <iostream>
 #include <stdexcept>
 #include <string>
 #include <variant>
 
-#include "Symbols/VariableTypes.hpp"
+#include "VariableTypes.hpp"
 
 namespace Symbols {
 
diff --git a/src/VoidScript.hpp b/src/VoidScript.hpp
index 661456c..879afeb 100644
--- a/src/VoidScript.hpp
+++ b/src/VoidScript.hpp
@@ -54,13 +54,6 @@
 
                 this->lexer->addNamespaceInput(ns, file_content);
                 const auto tokens = this->lexer->tokenizeNamespace(ns);
-                // dump tokens
-                std::cout << "--- Tokens ---\n";
-                for (const auto & token : tokens) {
-                    token.print();
-                }
-
-                std::cout << Operations::Container::dump() << "\n";
 
                 parser->parseScript(tokens, file_content, file);
 

--
Gitblit v1.9.3