From e2e9e07a9f50dc1f7a967280a3d1d8ef7fcaa153 Mon Sep 17 00:00:00 2001
From: Ferenc Szontágh <szf@fsociety.hu>
Date: Fri, 18 Apr 2025 09:12:33 +0000
Subject: [PATCH] add built-in modules

---
 /dev/null                             |   43 ----------
 src/VoidScript.hpp                    |    6 +
 src/Interpreter/CallStatementNode.hpp |    9 ++
 src/Modules/PrintModule.hpp           |   30 +++++++
 test_scripts/test2.vs                 |    6 +
 src/Modules/ModuleManager.hpp         |   82 ++++++++++++++++++++
 src/Modules/BaseModule.hpp            |   27 ++++++
 7 files changed, 159 insertions(+), 44 deletions(-)

diff --git a/src/Builtins/BaseFunction.hpp b/src/Builtins/BaseFunction.hpp
deleted file mode 100644
index 3cedf31..0000000
--- a/src/Builtins/BaseFunction.hpp
+++ /dev/null
@@ -1,77 +0,0 @@
-#ifndef SCRIPT_FUNCTION_HPP
-#define SCRIPT_FUNCTION_HPP
-
-#include <functional>
-#include <unordered_map>
-#include <utility>
-#include <vector>
-
-#include "ScriptExceptionMacros.h"
-#include "ScriptInterpreterHelpers.hpp"
-#include "Token.hpp"
-#include "Value.hpp"
-
-class ScriptInterpreter;
-
-using CallbackFunction = std::function<Value(const std::vector<Value> &)>;
-using CallBackStorage  = std::unordered_map<std::string, CallbackFunction>;
-
-class BaseFunction {
-  protected:
-    std::string     name;
-    CallBackStorage functionMap;
-
-
-  public:
-    BaseFunction(const std::string & functionName) : name(functionName) {}
-
-    virtual void addFunction(const std::string & name, std::function<Value(const std::vector<Value> &)> callback) {
-        functionMap[name] = std::move(callback);
-    }
-
-    virtual void validate(const std::vector<Token> & tokens, size_t & i,
-                          const std::unordered_map<std::string, Value> & variables) {
-        size_t index = i;
-
-        if (tokens[index].type != TokenType::Identifier) {
-            THROW_UNEXPECTED_TOKEN_ERROR(tokens[index], "identifier");
-        }
-        index++;  // skip function name
-
-        if (tokens[index].type != TokenType::LeftParenthesis) {
-            THROW_UNEXPECTED_TOKEN_ERROR(tokens[index], "(");
-        }
-        index++;  // skip '('
-
-        std::vector<Token> args;
-        while (tokens[index].type != TokenType::RightParenthesis) {
-            if (tokens[index].type == TokenType::Comma) {
-                index++;
-                continue;
-            }
-            if (tokens[index].type == TokenType::Variable && !variables.contains(tokens[index].lexeme)) {
-                THROW_UNDEFINED_VARIABLE_ERROR(tokens[index].lexeme, tokens[index]);
-            }
-            args.push_back(tokens[index]);
-            index++;
-        }
-
-        index++;  // skip ')'
-
-        if (tokens[index].type != TokenType::Semicolon) {
-            THROW_UNEXPECTED_TOKEN_ERROR(tokens[index], ";");
-        }
-
-        this->validateArgs(args, variables);
-        ScriptInterpreterHelpers::expectSemicolon(tokens, index, "function call");
-    }
-
-    virtual void validateArgs(const std::vector<Token> &                     args,
-                              const std::unordered_map<std::string, Value> & variables) = 0;
-
-    virtual Value call(const std::vector<Value> & args, bool debug = false) const = 0;
-
-    template <typename FuncClass> void registerFunctionTo(ScriptInterpreter & interp) { FuncClass::registerTo(interp); }
-};
-
-#endif  // SCRIPT_FUNCTION_HPP
diff --git a/src/Builtins/MathUtilsModule.hpp b/src/Builtins/MathUtilsModule.hpp
deleted file mode 100644
index e85f4d8..0000000
--- a/src/Builtins/MathUtilsModule.hpp
+++ /dev/null
@@ -1,28 +0,0 @@
-#ifndef MATH_UTILS_MODULE_HPP
-#define MATH_UTILS_MODULE_HPP
-
-#include <stdexcept>
-#include <vector>
-
-#include "ScriptExceptionMacros.h"
-#include "Value.hpp"
-
-class MathUtils {
-  public:
-    static Value multiply(const std::vector<Value> & args) {
-        if (args.size() != 2) {
-            throw std::runtime_error("multiply expects two arguments.");
-        }
-        if (args[0].type == Variables::Type::VT_INT && args[1].type == Variables::Type::VT_INT) {
-            int  left   = args[0].ToInt();
-            int  right  = args[1].ToInt();
-            auto result = Value();
-            result.data = left * right;
-            result.type = Variables::Type::VT_INT;
-            return result;
-            //return Value::fromInt(left * right);
-        }
-        THROW_INVALID_FUNCTION_ARGUMENT_ERROR("multiply", args[0].TypeToString(), args[0].GetToken());
-    };
-};
-#endif
diff --git a/src/Builtins/PrintModule.hpp b/src/Builtins/PrintModule.hpp
deleted file mode 100644
index a7bc07d..0000000
--- a/src/Builtins/PrintModule.hpp
+++ /dev/null
@@ -1,44 +0,0 @@
-#ifndef PRINTFUNCTION_HPP
-#define PRINTFUNCTION_HPP
-
-#include <iostream>
-
-#include "BaseFunction.hpp"
-#include "ScriptExceptionMacros.h"
-#include "Token.hpp"
-#include "Value.hpp"
-
-class PrintFunction : public BaseFunction {
-  private:
-    const std::string name       = "print";
-    bool              addNewLine = false;
-  public:
-    PrintFunction() : BaseFunction(name) {}
-
-    void validateArgs(const std::vector<Token> &                     args,
-                      const std::unordered_map<std::string, Value> & variables) override {
-        if (args.size() == 0) {
-            THROW_UNEXPECTED_TOKEN_ERROR(args[0], "at least one argument");
-        }
-
-        for (const auto & arg : args) {
-            if (arg.type == TokenType::Variable) {
-                if (!variables.contains(arg.lexeme)) {
-                    THROW_UNDEFINED_VARIABLE_ERROR(arg.lexeme, arg);
-                }
-            }
-        }
-        if (args.end()->variableType == Variables::Type::VT_INT || args.end()->type == TokenType::IntLiteral) {
-            this->addNewLine = true;
-        }
-    }
-
-    Value call(const std::vector<Value> & args, bool debug = false) const override {
-        for (const auto & arg : args) {
-            std::cout << arg.ToString(); // todo: add endline if the last parameter is bool
-        }
-        return Value();
-    }
-};
-
-#endif  // PRINTFUNCTION_HPP
diff --git a/src/Builtins/SleepModule.hpp b/src/Builtins/SleepModule.hpp
deleted file mode 100644
index 17585b2..0000000
--- a/src/Builtins/SleepModule.hpp
+++ /dev/null
@@ -1,43 +0,0 @@
-#ifndef SLEEPFUNCTION_HPP
-#define SLEEPFUNCTION_HPP
-
-#include <thread>
-
-#include "BaseFunction.hpp"
-
-class SleepFunction : public BaseFunction {
-  public:
-    SleepFunction() : BaseFunction("sleep") {}
-
-    void validateArgs(const std::vector<Token> &                     args,
-                      const std::unordered_map<std::string, Value> & variables) override {
-        if (args.size() != 1) {
-            throw std::runtime_error("sleep() requires exactly one argument");
-        }
-
-        const Token & arg = args[0];
-
-        if (arg.type == TokenType::IntLiteral) {
-            return;
-        }
-
-        if (arg.type == TokenType::Variable) {
-            const auto & value = variables.at(arg.lexeme);
-            if (value.type != Variables::Type::VT_INT) {
-                THROW_VARIABLE_TYPE_MISSMATCH_ERROR(arg.lexeme, Variables::TypeToString(Variables::Type::VT_INT), "",
-                                                    Variables::TypeToString(value.type), arg);
-            }
-            return;
-        }
-
-        THROW_UNEXPECTED_TOKEN_ERROR(arg, "int literal or variable");
-    }
-
-
-    Value call(const std::vector<Value> & args, bool debug = false) const override {
-        std::this_thread::sleep_for(std::chrono::seconds(args[0].ToInt()));
-        return Value();
-    }
-};
-
-#endif  // SLEEPFUNCTION_HPP
diff --git a/src/Interpreter/CallStatementNode.hpp b/src/Interpreter/CallStatementNode.hpp
index ccd85a1..224ac16 100644
--- a/src/Interpreter/CallStatementNode.hpp
+++ b/src/Interpreter/CallStatementNode.hpp
@@ -13,6 +13,7 @@
 #include "Symbols/SymbolContainer.hpp"
 #include "Symbols/SymbolFactory.hpp"
 #include "Symbols/Value.hpp"
+#include "Modules/ModuleManager.hpp"
 
 namespace Interpreter {
 
@@ -39,6 +40,14 @@
             argValues.push_back(expr->evaluate(interpreter));
         }
 
+        // Handle built-in function callbacks
+        {
+            auto &mgr = Modules::ModuleManager::instance();
+            if (mgr.hasFunction(functionName_)) {
+                mgr.callFunction(functionName_, argValues);
+                return;
+            }
+        }
         // Lookup function symbol in functions namespace
         SymbolContainer * sc        = SymbolContainer::instance();
         const std::string currentNs = sc->currentScopeName();
diff --git a/src/Modules/BaseModule.hpp b/src/Modules/BaseModule.hpp
new file mode 100644
index 0000000..27475bb
--- /dev/null
+++ b/src/Modules/BaseModule.hpp
@@ -0,0 +1,27 @@
+// BaseModule.hpp
+#ifndef MODULES_BASEMODULE_HPP
+#define MODULES_BASEMODULE_HPP
+
+#include <string>
+#include "Symbols/SymbolContainer.hpp"
+#include "Symbols/SymbolFactory.hpp"
+
+namespace Modules {
+
+/**
+ * @brief Base class for modules that can register functions and variables into the symbol table.
+ */
+class BaseModule {
+  public:
+    BaseModule() = default;
+    virtual ~BaseModule() = default;
+
+    /**
+     * @brief Register this module's symbols (functions, variables) into the global symbol container.
+     * Modules should use Symbols::SymbolContainer::instance() and SymbolFactory to add symbols.
+     */
+    virtual void registerModule() = 0;
+};
+
+} // namespace Modules
+#endif // MODULES_BASEMODULE_HPP
\ No newline at end of file
diff --git a/src/Modules/ModuleManager.hpp b/src/Modules/ModuleManager.hpp
new file mode 100644
index 0000000..c694493
--- /dev/null
+++ b/src/Modules/ModuleManager.hpp
@@ -0,0 +1,82 @@
+// ModuleManager.hpp
+#ifndef MODULES_MODULEMANAGER_HPP
+#define MODULES_MODULEMANAGER_HPP
+
+#include <memory>
+#include <vector>
+#include "BaseModule.hpp"
+#include <functional>
+#include <unordered_map>
+#include "Symbols/Value.hpp"
+
+namespace Modules {
+
+/**
+ * @brief Manager for registering and invoking modules.
+ */
+class ModuleManager {
+  public:
+    /**
+     * @brief Get singleton instance of ModuleManager.
+     */
+    static ModuleManager &instance() {
+        static ModuleManager mgr;
+        return mgr;
+    }
+
+    /**
+     * @brief Add a module to the manager.
+     * @param module Unique pointer to a BaseModule.
+     */
+    void addModule(std::unique_ptr<BaseModule> module) {
+        modules_.push_back(std::move(module));
+    }
+
+    /**
+     * @brief Invoke all registered modules to register their symbols.
+     */
+    void registerAll() {
+        for (const auto &module : modules_) {
+            module->registerModule();
+        }
+    }
+
+  private:
+    ModuleManager() = default;
+    std::vector<std::unique_ptr<BaseModule>> modules_;
+    // Built-in function callbacks: name -> function
+    std::unordered_map<std::string,
+        std::function<Symbols::Value(const std::vector<Symbols::Value>&)>> callbacks_;
+  public:
+    /**
+     * @brief Register a built-in function callback.
+     * @param name Name of the function.
+     * @param cb Callable taking argument values and returning a Value.
+     */
+    void registerFunction(const std::string &name,
+                          std::function<Symbols::Value(const std::vector<Symbols::Value>&)> cb) {
+        callbacks_[name] = std::move(cb);
+    }
+
+    /**
+     * @brief Check if a built-in function is registered.
+     */
+    bool hasFunction(const std::string &name) const {
+        return callbacks_.find(name) != callbacks_.end();
+    }
+
+    /**
+     * @brief Call a built-in function callback.
+     */
+    Symbols::Value callFunction(const std::string &name,
+                                const std::vector<Symbols::Value> &args) const {
+        auto it = callbacks_.find(name);
+        if (it == callbacks_.end()) {
+            throw std::runtime_error("Built-in function callback not found: " + name);
+        }
+        return it->second(args);
+    }
+};
+
+} // namespace Modules
+#endif // MODULES_MODULEMANAGER_HPP
\ No newline at end of file
diff --git a/src/Modules/PrintModule.hpp b/src/Modules/PrintModule.hpp
new file mode 100644
index 0000000..e4deb6a
--- /dev/null
+++ b/src/Modules/PrintModule.hpp
@@ -0,0 +1,30 @@
+// PrintModule.hpp
+#ifndef MODULES_PRINTMODULE_HPP
+#define MODULES_PRINTMODULE_HPP
+
+#include <iostream>
+#include "BaseModule.hpp"
+#include "ModuleManager.hpp"
+#include "Symbols/Value.hpp"
+
+namespace Modules {
+
+/**
+ * @brief Module that provides a built-in print function.
+ */
+class PrintModule : public BaseModule {
+  public:
+    void registerModule() override {
+        auto &mgr = ModuleManager::instance();
+        mgr.registerFunction("print", [](const std::vector<Symbols::Value> &args) {
+            for (const auto &v : args) {
+                std::cout << Symbols::Value::to_string(v);
+            }
+            std::cout << std::endl;
+            return Symbols::Value();
+        });
+    }
+};
+
+} // namespace Modules
+#endif // MODULES_PRINTMODULE_HPP
\ No newline at end of file
diff --git a/src/VoidScript.hpp b/src/VoidScript.hpp
index 879afeb..5fa3ef0 100644
--- a/src/VoidScript.hpp
+++ b/src/VoidScript.hpp
@@ -7,6 +7,8 @@
 #include "Interpreter/Interpreter.hpp"
 #include "Lexer/Lexer.hpp"
 #include "Parser/Parser.hpp"
+#include "Modules/ModuleManager.hpp"
+#include "Modules/PrintModule.hpp"
 
 class VoidScript {
   private:
@@ -33,6 +35,8 @@
 
         lexer(std::make_shared<Lexer::Lexer>()),
         parser(std::make_shared<Parser::Parser>()) {
+        // Register built-in modules (print, etc.)
+        Modules::ModuleManager::instance().addModule(std::make_unique<Modules::PrintModule>());
         this->files.emplace(this->files.begin(), file);
 
         lexer->setKeyWords(Parser::Parser::keywords);
@@ -40,6 +44,8 @@
 
     int run() {
         try {
+            // Register all built-in modules before execution
+            Modules::ModuleManager::instance().registerAll();
             while (!files.empty()) {
                 std::string       file         = files.back();
                 const std::string file_content = readFile(file);
diff --git a/test_scripts/test2.vs b/test_scripts/test2.vs
index bb9d822..15fc79e 100644
--- a/test_scripts/test2.vs
+++ b/test_scripts/test2.vs
@@ -8,5 +8,9 @@
     int $result = $i + 1;
 }
 
+function increment = (int $i) int {
+    return $i + 1;
+}
 
-test(5);
\ No newline at end of file
+
+test(1);
\ No newline at end of file

--
Gitblit v1.9.3