From c91e935c62b8e254b9daadf37b915c983518bff4 Mon Sep 17 00:00:00 2001
From: Ferenc Szontágh <szf@fsociety.hu>
Date: Fri, 18 Apr 2025 16:25:35 +0000
Subject: [PATCH] add dynamic module load, more escape seq

---
 src/Parser/Parser.cpp                        |   14 
 Modules/CurlModule/src/PluginInit.cpp        |   14 +
 Modules/CurlModule/src/PluginInit.h          |   11 
 test_scripts/expressions.vs                  |   33 ++
 Modules/CurlModule/src/PLuginInit.hpp        |   14 +
 src/Interpreter/BinaryExpressionNode.hpp     |   41 +++
 Modules/CurlModule/src/CurlModule.cpp        |   86 +++++++
 test_scripts/escape_sequences.vs             |   17 +
 Modules/CurlModule/CMakeLists.txt            |   18 +
 src/Lexer/Lexer.cpp                          |   25 ++
 Modules/CurlModule/test_scripts/curl_post.vs |    8 
 src/Interpreter/Interpreter.hpp              |   13 
 CMakeLists.txt                               |   13 
 Modules/CurlModule/src/CurlModule.hpp        |   31 ++
 src/Modules/PrintNlModule.hpp                |   31 ++
 test_scripts/escape.vs                       |    2 
 cli/main.cpp                                 |   86 ++++--
 src/VoidScript.hpp                           |   50 +++
 /dev/null                                    |    1 
 Modules/CurlModule/test_scripts/curl_get.vs  |    5 
 test_scripts/variables.vs                    |   14 +
 src/Modules/PrintModule.hpp                  |    1 
 src/Lexer/Operators.hpp                      |   33 +-
 src/Modules/ModuleManager.hpp                |  134 +++++++++-
 24 files changed, 611 insertions(+), 84 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index ddc26dc..968ad4b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -110,10 +110,8 @@
 # PACKAGING PROPERTIES END
 
 configure_file("cmake/options.h.in" "include/options.h" @ONLY)
-configure_file("test_scripts/test1.vs" "test_scripts/test1.vs" @ONLY)
-configure_file("test_scripts/test2.vs" "test_scripts/test2.vs" @ONLY)
-configure_file("test_scripts/test2.vs" "test_scripts/test2.vs" @ONLY)
-configure_file("test_scripts/function_test.vs" "test_scripts/function_test.vs" @ONLY)
+    configure_file("test_scripts/variables.vs" "test_scripts/variables.vs" @ONLY)
+    configure_file("test_scripts/expressions.vs" "test_scripts/expressions.vs" @ONLY)
 
 
 
@@ -157,6 +155,13 @@
     set(CPACK_DEBIAN_BIN_PACKAGE_DEPENDS "libvoidscript (= ${CMAKE_PROJECT_VERSION})")
 endif()
 
+# Plugin modules options
+option(BUILD_MODULE_CURL "Enable building CurlModule" OFF)
+
+if (BUILD_MODULE_CURL)
+ add_subdirectory(Modules/CurlModule)
+endif()
+
 # CPACK CONFIGURATION
 set(CPACK_DEB_COMPONENT_INSTALL ON)
 
diff --git a/Modules/CurlModule/CMakeLists.txt b/Modules/CurlModule/CMakeLists.txt
new file mode 100644
index 0000000..da7927d
--- /dev/null
+++ b/Modules/CurlModule/CMakeLists.txt
@@ -0,0 +1,18 @@
+project(
+    CurlModule
+    VERSION 0.0.1
+    DESCRIPTION "A simple curl module for voidscript"
+    LANGUAGES CXX
+    HOMEPAGE_URL "https://github.com/fszontagh/voidshell"
+)
+
+find_package(CURL REQUIRED)
+
+add_library(CurlModule SHARED)
+target_sources(CurlModule
+    PRIVATE
+        src/CurlModule.cpp
+        src/PluginInit.cpp
+)
+
+target_link_libraries(CurlModule PRIVATE curl)
\ No newline at end of file
diff --git a/Modules/CurlModule/src/CurlModule.cpp b/Modules/CurlModule/src/CurlModule.cpp
new file mode 100644
index 0000000..ed7174e
--- /dev/null
+++ b/Modules/CurlModule/src/CurlModule.cpp
@@ -0,0 +1,86 @@
+// CurlModule implementation: HTTP GET and POST using libcurl
+#include "CurlModule.hpp"
+#include "Modules/ModuleManager.hpp"
+#include "Symbols/Value.hpp"
+#include <curl/curl.h>
+#include <stdexcept>
+#include <string>
+#include <vector>
+
+// Callback for libcurl to write received data into a std::string
+static size_t write_callback(void* ptr, size_t size, size_t nmemb, void* userdata) {
+    auto* buffer = static_cast<std::string*>(userdata);
+    buffer->append(static_cast<char*>(ptr), size * nmemb);
+    return size * nmemb;
+}
+
+// Register module functions
+void Modules::CurlModule::registerModule() {
+    auto& mgr = Modules::ModuleManager::instance();
+    // Register HTTP GET: curlGet(url)
+    mgr.registerFunction("curlGet", [this](const std::vector<Symbols::Value>& args) -> Symbols::Value {
+        return this->curlGet(args);
+    });
+    // Register HTTP POST: curlPost(url, data)
+    mgr.registerFunction("curlPost", [this](const std::vector<Symbols::Value>& args) -> Symbols::Value {
+        return this->curlPost(args);
+    });
+}
+
+
+Symbols::Value Modules::CurlModule::curlPost(const std::vector<Symbols::Value>& args) {
+    if (args.size() != 2) {
+        throw std::runtime_error("curlPost: missing URL and data arguments");
+    }
+    std::string url  = Symbols::Value::to_string(args[0]);
+    std::string data = Symbols::Value::to_string(args[1]);
+
+    CURL * curl = curl_easy_init();
+    if (!curl) {
+        throw std::runtime_error("curl: failed to initialize");
+    }
+
+    std::string response;
+    curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
+    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str());
+    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
+    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
+
+    CURLcode res = curl_easy_perform(curl);
+    if (res != CURLE_OK) {
+        std::string error = curl_easy_strerror(res);
+        curl_easy_cleanup(curl);
+        throw std::runtime_error("curl: request failed: " + error);
+    }
+
+    curl_easy_cleanup(curl);
+    return Symbols::Value(response);
+}
+
+Symbols::Value Modules::CurlModule::curlGet(const std::vector<Symbols::Value>& args) {
+    if (args.size() != 1) {
+        throw std::runtime_error("curlGet: missing URL argument");
+    }
+
+    std::string url = Symbols::Value::to_string(args[0]);
+
+    CURL * curl = curl_easy_init();
+    if (!curl) {
+        throw std::runtime_error("curl: failed to initialize");
+    }
+
+    std::string response;
+    curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
+    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
+    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
+
+    CURLcode res = curl_easy_perform(curl);
+    if (res != CURLE_OK) {
+        std::string error = curl_easy_strerror(res);
+        curl_easy_cleanup(curl);
+        throw std::runtime_error("curl: request failed: " + error);
+    }
+
+    curl_easy_cleanup(curl);
+    return Symbols::Value(response);
+}
diff --git a/Modules/CurlModule/src/CurlModule.hpp b/Modules/CurlModule/src/CurlModule.hpp
new file mode 100644
index 0000000..c2d30ec
--- /dev/null
+++ b/Modules/CurlModule/src/CurlModule.hpp
@@ -0,0 +1,31 @@
+// CurlModule: declares a module that provides 'curl' function via libcurl
+#ifndef CURLMODULE_CURLMODULE_HPP
+#define CURLMODULE_CURLMODULE_HPP
+
+#include "Modules/BaseModule.hpp"
+#include <vector>
+#include "Symbols/Value.hpp"
+
+namespace Modules {
+
+class CurlModule : public BaseModule {
+public:
+    /**
+     * @brief Register this module's symbols (HTTP GET and POST functions).
+     */
+    void registerModule() override;
+    
+    /**
+     * @brief Perform HTTP GET: curlGet(url)
+     */
+    Symbols::Value curlGet(const std::vector<Symbols::Value>& args);
+    
+    /**
+     * @brief Perform HTTP POST: curlPost(url, data)
+     */
+    Symbols::Value curlPost(const std::vector<Symbols::Value>& args);
+};
+
+} // namespace Modules
+
+#endif // CURLMODULE_CURLMODULE_HPP
diff --git a/Modules/CurlModule/src/PLuginInit.hpp b/Modules/CurlModule/src/PLuginInit.hpp
new file mode 100644
index 0000000..14cdd77
--- /dev/null
+++ b/Modules/CurlModule/src/PLuginInit.hpp
@@ -0,0 +1,14 @@
+#include "CurlModule.hpp"
+#include "Modules/ModuleManager.hpp"
+#include "Symbols/Value.hpp"
+
+extern "C" void plugin_init() {
+    auto & mgr = Modules::ModuleManager::instance();
+
+
+    mgr.registerFunction("curlGET", CallbackFunction(&CurlModule::curlGet));
+    mgr.registerFunction("curlPOST", CallbackFunction(&CurlModule::curlPost));
+
+    // curl alias, ha szükséges
+    mgr.registerFunction("curl", &CurlModule::curlGet);  // vagy írj külön wrapper-t
+}
diff --git a/Modules/CurlModule/src/PluginInit.cpp b/Modules/CurlModule/src/PluginInit.cpp
new file mode 100644
index 0000000..d919563
--- /dev/null
+++ b/Modules/CurlModule/src/PluginInit.cpp
@@ -0,0 +1,14 @@
+// PluginInit.cpp
+#include "PluginInit.h"
+#include <memory>
+#include "Modules/ModuleManager.hpp"
+#include "CurlModule.hpp"
+
+/**
+ * @brief Plugin initialization. Registers CurlModule with the ModuleManager.
+ */
+extern "C" void plugin_init() {
+    Modules::ModuleManager::instance().addModule(
+        std::make_unique<Modules::CurlModule>()
+    );
+}
\ No newline at end of file
diff --git a/Modules/CurlModule/src/PluginInit.h b/Modules/CurlModule/src/PluginInit.h
new file mode 100644
index 0000000..8ef04e1
--- /dev/null
+++ b/Modules/CurlModule/src/PluginInit.h
@@ -0,0 +1,11 @@
+// PluginInit.h
+#ifndef CURLMODULE_PLUGININIT_H
+#define CURLMODULE_PLUGININIT_H
+
+/**
+ * @brief Plugin entry point for CurlModule.
+ * Called when the shared library is loaded.
+ */
+extern "C" void plugin_init();
+
+#endif // CURLMODULE_PLUGININIT_H
\ No newline at end of file
diff --git a/Modules/CurlModule/test_scripts/curl_get.vs b/Modules/CurlModule/test_scripts/curl_get.vs
new file mode 100644
index 0000000..42e775d
--- /dev/null
+++ b/Modules/CurlModule/test_scripts/curl_get.vs
@@ -0,0 +1,5 @@
+string $url = "https://jsonplaceholder.typicode.com/todos/1";
+
+string $response = curlGet($url);
+
+printnl("Response: ", $response);
\ No newline at end of file
diff --git a/Modules/CurlModule/test_scripts/curl_post.vs b/Modules/CurlModule/test_scripts/curl_post.vs
new file mode 100644
index 0000000..7d93f43
--- /dev/null
+++ b/Modules/CurlModule/test_scripts/curl_post.vs
@@ -0,0 +1,8 @@
+string $url = "https://jsonplaceholder.typicode.com/posts";
+
+string $postData = "{\"title\":\"foo\",\"body\":\"bar\",\"userId\":1}";
+
+printnl("Posting data: '",$postData,"' to URL: ", $url);
+string $response = curlPost($url, $postData);
+
+printnl("Response: ", $response);
\ No newline at end of file
diff --git a/cli/main.cpp b/cli/main.cpp
index 65dca3a..f0daa59 100644
--- a/cli/main.cpp
+++ b/cli/main.cpp
@@ -5,9 +5,11 @@
 #include "options.h"
 #include "VoidScript.hpp"
 
+// Supported command-line parameters and descriptions
 const std::unordered_map<std::string, std::string> params = {
-    { "--help",    "Print this help message"          },
-    { "--version", "Print the version of the program" },
+    { "--help",    "Print this help message"                                                                     },
+    { "--version", "Print the version of the program"                                                            },
+    { "--debug",   "Enable debug output (all components or use --debug=lexer, parser, interpreter, symboltable)" },
 };
 
 int main(int argc, char * argv[]) {
@@ -15,38 +17,67 @@
     for (const auto & [key, value] : params) {
         usage.append(" [" + key + "]");
     }
-    if (argc < 2) {
-        std::cerr << usage << "\n";
-        return 1;
-    }
+    // Parse arguments: allow --help, --version, --debug[=component], and a single file
+    bool debugLexer       = false;
+    bool debugParser      = false;
+    bool debugInterp      = false;
+    bool debugSymbolTable = false;
 
     std::string file;
-
-    const std::string arg = std::string(argv[1]);
-    if (arg.starts_with("-")) {
-        auto it = params.find(arg);
-        if (it != params.end()) {
-            if (arg == "--help") {
-                std::cout << usage << "\n";
-                for (const auto & [key, value] : params) {
-                    std::cout << "  " << key << ": " << value << "\n";
-                }
-                return 0;
-            }
-            if (arg == "--version") {
-                std::cout << "Version:      " << VERSION_MAJOR << "." << VERSION_MINOR << "." << VERSION_PATCH;
-                std::cout << " (" << VERSION_GIT_HASH << ")\n";
-                std::cout << "Architecture: " << VERSION_ARCH << "\n";
-                std::cout << "System:       " << VERSION_SYSTEM_NAME << "\n";
-                return 0;
+    for (int i = 1; i < argc; ++i) {
+        std::string a = argv[i];
+        if (a == "--help") {
+            std::cout << usage << "\n";
+            for (const auto & [key, value] : params) {
+                std::cout << "  " << key << ": " << value << "\n";
             }
             return 0;
+        } else if (a == "--version") {
+            std::cout << "Version:      " << VERSION_MAJOR << "." << VERSION_MINOR << "." << VERSION_PATCH << " ("
+                      << VERSION_GIT_HASH << ")\n";
+            std::cout << "Architecture: " << VERSION_ARCH << "\n";
+            std::cout << "System:       " << VERSION_SYSTEM_NAME << "\n";
+            return 0;
+        } else if (a.rfind("--debug", 0) == 0) {
+            if (a == "--debug") {
+                debugLexer = debugParser = debugInterp = true;
+            } else if (a.rfind("--debug=", 0) == 0) {
+                std::string comp = a.substr(std::string("--debug=").size());
+                if (comp == "lexer") {
+                    debugLexer = true;
+                } else if (comp == "parser") {
+                    debugParser = true;
+                } else if (comp == "interpreter") {
+                    debugInterp = true;
+                } else if (comp == "symboltable") {
+                    debugSymbolTable = true;
+                } else {
+                    std::cerr << "Error: Unknown debug component '" << comp << "'\n";
+                    std::cerr << usage << "\n";
+                    return 1;
+                }
+            } else {
+                std::cerr << "Error: Unknown option '" << a << "'\n";
+                std::cerr << usage << "\n";
+                return 1;
+            }
+        } else if (a.starts_with("-")) {
+            std::cerr << "Error: Unknown option '" << a << "'\n";
+            std::cerr << usage << "\n";
+            return 1;
+        } else if (file.empty()) {
+            file = a;
+        } else {
+            std::cerr << "Error: Multiple files specified\n";
+            std::cerr << usage << "\n";
+            return 1;
         }
-        std::cerr << "Error: Unknown option " << arg << "\n";
+    }
+    if (file.empty()) {
+        std::cerr << "Error: No input file specified\n";
         std::cerr << usage << "\n";
         return 1;
     }
-    file = arg;
 
     if (!std::filesystem::exists(file)) {
         std::cerr << "Error: File " << file << " does not exist.\n";
@@ -55,6 +86,7 @@
 
     const std::string filename = std::filesystem::canonical(file).string();
 
-    VoidScript voidscript(filename);
+    // Initialize and run with debug options
+    VoidScript voidscript(filename, debugLexer, debugParser, debugInterp, debugSymbolTable);
     return voidscript.run();
 }
diff --git a/src/Interpreter/BinaryExpressionNode.hpp b/src/Interpreter/BinaryExpressionNode.hpp
index f741d3f..3281aab 100644
--- a/src/Interpreter/BinaryExpressionNode.hpp
+++ b/src/Interpreter/BinaryExpressionNode.hpp
@@ -23,6 +23,26 @@
                 "Unsupported types in binary expression: " + Symbols::Variables::TypeToString(leftVal.getType()) +
                 " and " + Symbols::Variables::TypeToString(rightVal.getType()) + " " + toString());
         }
+        if (leftVal.getType() == Symbols::Variables::Type::BOOLEAN &&
+            rightVal.getType() == Symbols::Variables::Type::BOOLEAN) {
+            bool l = leftVal.get<bool>();
+            bool r = rightVal.get<bool>();
+
+            if (op_ == "&&") {
+                return Symbols::Value(l && r);
+            }
+            if (op_ == "||") {
+                return Symbols::Value(l || r);
+            }
+            if (op_ == "==") {
+                return Symbols::Value(l == r);
+            }
+            if (op_ == "!=") {
+                return Symbols::Value(l != r);
+            }
+
+            throw std::runtime_error("Unknown operator: " + op_);
+        }
 
         if (leftVal.getType() == Symbols::Variables::Type::INTEGER &&
             rightVal.getType() == Symbols::Variables::Type::INTEGER) {
@@ -41,6 +61,27 @@
             if (op_ == "/") {
                 return Symbols::Value(l / r);  // TODO: 0 div
             }
+            if (op_ == "%") {
+                return Symbols::Value(l % r);
+            }
+            if (op_ == "==") {
+                return Symbols::Value(l == r);
+            }
+            if (op_ == "!=") {
+                return Symbols::Value(l != r);
+            }
+            if (op_ == "<") {
+                return Symbols::Value(l < r);
+            }
+            if (op_ == ">") {
+                return Symbols::Value(l > r);
+            }
+            if (op_ == "<=") {
+                return Symbols::Value(l <= r);
+            }
+            if (op_ == ">=") {
+                return Symbols::Value(l >= r);
+            }
 
             throw std::runtime_error("Unknown operator: " + op_);
         }
diff --git a/src/Interpreter/Interpreter.hpp b/src/Interpreter/Interpreter.hpp
index fe1dfb7..fe18030 100644
--- a/src/Interpreter/Interpreter.hpp
+++ b/src/Interpreter/Interpreter.hpp
@@ -12,10 +12,13 @@
 
 class Interpreter {
   private:
-
-
+    bool debug_ = false;
   public:
-    Interpreter() {}
+    /**
+     * @brief Construct interpreter with optional debug output
+     * @param debug enable interpreter debug output
+     */
+    Interpreter(bool debug = false) : debug_(debug) {}
 
     /**
      * @brief Execute all operations in the current namespace (e.g., file-level or function-level).
@@ -29,7 +32,9 @@
     }
 
     void runOperation(const Operations::Operation & op) {
-        std::cout << "Operation: " << op.toString() << "\n";
+        if (debug_) {
+            std::cerr << "[Debug][Interpreter] Operation: " << op.toString() << "\n";
+        }
 
         switch (op.type) {
             case Operations::Type::Declaration:
diff --git a/src/Lexer/Lexer.cpp b/src/Lexer/Lexer.cpp
index b5a4261..d229c6b 100644
--- a/src/Lexer/Lexer.cpp
+++ b/src/Lexer/Lexer.cpp
@@ -218,8 +218,29 @@
                 case 't':
                     value += '\t';
                     break;
+                case 'r':
+                    value += '\r';
+                    break;
+                case 'b':
+                    value += '\b';
+                    break;
+                case 'f':
+                    value += '\f';
+                    break;
+                case 'v':
+                    value += '\v';
+                    break;
+                case 'a':
+                    value += '\a';
+                    break;
+                case '0':
+                    value += '\0';
+                    break;
                 case '"':
-                    value += opening_quote;
+                    value += '"';
+                    break;
+                case '\'':
+                    value += '\'';
                     break;
                 case '\\':
                     value += '\\';
@@ -271,8 +292,10 @@
         }
     }
 
+    // Single-character operator or punctuation tokens
     const std::vector<std::pair<const std::vector<std::string> *, Tokens::Type>> one_char_op_types = {
         { &OPERATOR_ARITHMETIC, Tokens::Type::OPERATOR_ARITHMETIC },
+        { &OPERATOR_RELATIONAL, Tokens::Type::OPERATOR_RELATIONAL },
         { &OPERATOR_ASSIGNMENT, Tokens::Type::OPERATOR_ASSIGNMENT },
         { &PUNCTUATION,         Tokens::Type::PUNCTUATION         }
     };
diff --git a/src/Lexer/Operators.hpp b/src/Lexer/Operators.hpp
index 6725b99..794ef86 100644
--- a/src/Lexer/Operators.hpp
+++ b/src/Lexer/Operators.hpp
@@ -57,18 +57,27 @@
 
 [[nodiscard]] inline bool pushOperand(const Tokens::Token & token, const Symbols::Variables::Type & expected_var_type,
                                       std::vector<Parser::ParsedExpressionPtr> & output_queue) {
-    if (token.type == Tokens::Type::NUMBER || token.type == Tokens::Type::STRING_LITERAL ||
-        token.type == Tokens::Type::KEYWORD) {
-        // Parse literal: use expected type if provided, otherwise auto-detect
-        if (expected_var_type == Symbols::Variables::Type::NULL_TYPE) {
-            output_queue.push_back(
-                Parser::ParsedExpression::makeLiteral(
-                    Symbols::Value::fromString(token.value, /*autoDetectType*/ true)));
-        } else {
-            output_queue.push_back(
-                Parser::ParsedExpression::makeLiteral(
-                    Symbols::Value::fromString(token.value, expected_var_type)));
-        }
+    // Literal operands: number, string, or keyword literals (e.g., true/false/null)
+    if (token.type == Tokens::Type::NUMBER) {
+        // Numeric literal: auto-detect integer/double/float
+        output_queue.push_back(
+            Parser::ParsedExpression::makeLiteral(
+                Symbols::Value::fromString(token.value, /*autoDetectType*/ true)));
+        return true;
+    }
+    if (token.type == Tokens::Type::STRING_LITERAL) {
+        // String literal: use literal value
+        output_queue.push_back(
+            Parser::ParsedExpression::makeLiteral(
+                Symbols::Value(token.value)));
+        return true;
+    }
+    if (token.type == Tokens::Type::KEYWORD) {
+        // Keyword literal: e.g., true, false, null
+        // Auto-detect boolean or null as needed
+        output_queue.push_back(
+            Parser::ParsedExpression::makeLiteral(
+                Symbols::Value::fromString(token.value, /*autoDetectType*/ true)));
         return true;
     }
     if (token.type == Tokens::Type::VARIABLE_IDENTIFIER) {
diff --git a/src/Modules/ModuleManager.hpp b/src/Modules/ModuleManager.hpp
index c694493..03b0a12 100644
--- a/src/Modules/ModuleManager.hpp
+++ b/src/Modules/ModuleManager.hpp
@@ -2,14 +2,25 @@
 #ifndef MODULES_MODULEMANAGER_HPP
 #define MODULES_MODULEMANAGER_HPP
 
-#include <memory>
-#include <vector>
-#include "BaseModule.hpp"
+#include <filesystem>
 #include <functional>
+#include <memory>
+#include <stdexcept>
+#include <string>
 #include <unordered_map>
+#include <vector>
+
+#include "BaseModule.hpp"
 #include "Symbols/Value.hpp"
 
+#ifndef _WIN32
+#    include <dlfcn.h>
+#else
+#    include <windows.h>
+#endif
+
 namespace Modules {
+using CallbackFunction = std::function<Symbols::Value(const std::vector<Symbols::Value> &)>;
 
 /**
  * @brief Manager for registering and invoking modules.
@@ -19,7 +30,7 @@
     /**
      * @brief Get singleton instance of ModuleManager.
      */
-    static ModuleManager &instance() {
+    static ModuleManager & instance() {
         static ModuleManager mgr;
         return mgr;
     }
@@ -28,55 +39,138 @@
      * @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));
-    }
+    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_) {
+        for (const auto & module : modules_) {
             module->registerModule();
         }
     }
 
   private:
     ModuleManager() = default;
-    std::vector<std::unique_ptr<BaseModule>> modules_;
+    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_;
+    std::unordered_map<std::string, std::function<Symbols::Value(const std::vector<Symbols::Value> &)>> callbacks_;
+    // Plugin handles for dynamically loaded modules
+    std::vector<void *>                                                                                 pluginHandles_;
   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) {
+    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();
-    }
+    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 {
+    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);
     }
+
+    /**
+     * @brief Load all plugin modules from specified directory.
+     * @param directory Path to directory containing plugin shared libraries.
+     */
+    void loadPlugins(const std::string & directory) {
+        namespace fs = std::filesystem;
+        if (!fs::exists(directory) || !fs::is_directory(directory)) {
+            return;
+        }
+        // Recursively search for plugin shared libraries
+        for (const auto & entry : fs::recursive_directory_iterator(directory)) {
+            if (!entry.is_regular_file()) {
+                continue;
+            }
+#ifdef _WIN32
+            if (entry.path().extension() == ".dll") {
+#else
+            if (entry.path().extension() == ".so") {
+#endif
+                loadPlugin(entry.path().string());
+            }
+        }
+    }
+
+    /**
+     * @brief Load a single plugin module from shared library.
+     * @param path Filesystem path to the shared library.
+     */
+    void loadPlugin(const std::string & path) {
+#ifndef _WIN32
+        void * handle = dlopen(path.c_str(), RTLD_NOW);
+        if (!handle) {
+            throw std::runtime_error("Failed to load module: " + path + ": " + dlerror());
+        }
+#else
+        HMODULE handle = LoadLibraryA(path.c_str());
+        if (!handle) {
+            throw std::runtime_error("Failed to load module: " + path);
+        }
+#endif
+        pluginHandles_.push_back(handle);
+
+#ifndef _WIN32
+        dlerror();  // clear any existing error
+        using PluginInitFunc    = void (*)();
+        auto         initFunc   = reinterpret_cast<PluginInitFunc>(dlsym(handle, "plugin_init"));
+        const char * dlsymError = dlerror();
+        if (dlsymError) {
+            dlclose(handle);
+            pluginHandles_.pop_back();
+            throw std::runtime_error("Cannot find symbol 'plugin_init' in " + path + ": " + dlsymError);
+        }
+        initFunc();
+#else
+        using PluginInitFunc = void(__cdecl *)();
+        auto initFunc        = reinterpret_cast<PluginInitFunc>(GetProcAddress(handle, "plugin_init"));
+        if (!initFunc) {
+            FreeLibrary(handle);
+            pluginHandles_.pop_back();
+            throw std::runtime_error("Cannot find symbol 'plugin_init' in " + path);
+        }
+        initFunc();
+#endif
+    }
+
+    /**
+     * @brief Destructor unloads modules and plugin libraries in safe order.
+     * Modules (and their callback functions) are destroyed before the libraries are unloaded,
+     * ensuring that destructors (in plugin code) run while the libraries are still mapped.
+     */
+    ~ModuleManager() {
+        // Destroy module instances (may call code in plugin libraries)
+        modules_.clear();
+        // Clear callback functions (may refer to plugin code)
+        callbacks_.clear();
+        // Unload all dynamically loaded plugin libraries
+        for (auto handle : pluginHandles_) {
+#ifndef _WIN32
+            dlclose(handle);
+#else
+            FreeLibrary((HMODULE) handle);
+#endif
+        }
+        // Clear handles
+        pluginHandles_.clear();
+    }
 };
 
-} // namespace Modules
-#endif // MODULES_MODULEMANAGER_HPP
\ No newline at end of file
+}  // namespace Modules
+#endif  // MODULES_MODULEMANAGER_HPP
diff --git a/src/Modules/PrintModule.hpp b/src/Modules/PrintModule.hpp
index e4deb6a..bc26b95 100644
--- a/src/Modules/PrintModule.hpp
+++ b/src/Modules/PrintModule.hpp
@@ -20,7 +20,6 @@
             for (const auto &v : args) {
                 std::cout << Symbols::Value::to_string(v);
             }
-            std::cout << std::endl;
             return Symbols::Value();
         });
     }
diff --git a/src/Modules/PrintNlModule.hpp b/src/Modules/PrintNlModule.hpp
new file mode 100644
index 0000000..509006e
--- /dev/null
+++ b/src/Modules/PrintNlModule.hpp
@@ -0,0 +1,31 @@
+// PrintLnModule.hpp
+#ifndef MODULES_PRINTLNMODULE_HPP
+#define MODULES_PRINTLNMODULE_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 PrintNlModule : public BaseModule {
+  public:
+    void registerModule() override {
+        auto & mgr = ModuleManager::instance();
+        mgr.registerFunction("printnl", [](const std::vector<Symbols::Value> & args) {
+            for (const auto & v : args) {
+                std::cout << Symbols::Value::to_string(v);
+            }
+            std::cout << "\n";
+            return Symbols::Value();
+        });
+    }
+};
+
+}  // namespace Modules
+#endif  // MODULES_PrintLnModule_HPP
diff --git a/src/Parser/Parser.cpp b/src/Parser/Parser.cpp
index 1b900a3..7e99d37 100644
--- a/src/Parser/Parser.cpp
+++ b/src/Parser/Parser.cpp
@@ -335,7 +335,9 @@
             // Create call expression node
             output_queue.push_back(ParsedExpression::makeCall(func_name, std::move(call_args)));
             expect_unary = false;
-        } else if (token.type == Lexer::Tokens::Type::OPERATOR_ARITHMETIC) {
+        } else if (token.type == Lexer::Tokens::Type::OPERATOR_ARITHMETIC
+                   || token.type == Lexer::Tokens::Type::OPERATOR_RELATIONAL
+                   || token.type == Lexer::Tokens::Type::OPERATOR_LOGICAL) {
             std::string op = std::string(token.lexeme);
 
             if (expect_unary && Lexer::isUnaryOperator(op)) {
@@ -395,18 +397,18 @@
             Parser::reportError("Mismatched parentheses", tokens_[current_token_index_]);
         }
 
-        if (op == "u-" || op == "u+") {
+        // Handle unary operators (plus, minus, logical NOT)
+        if (op == "u-" || 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");
+                Parser::reportError("Missing operand for unary operator", tokens_[current_token_index_]);
             }
             auto rhs = std::move(output_queue.back());
             output_queue.pop_back();
             output_queue.push_back(Lexer::applyOperator(op, std::move(rhs)));
         } else {
+            // Binary operators
             if (output_queue.size() < 2) {
-                reportError("Malformed expression");
-                Parser::reportError("Mailformed expression", tokens_[current_token_index_]);
+                Parser::reportError("Malformed expression", tokens_[current_token_index_]);
             }
             auto rhs = std::move(output_queue.back());
             output_queue.pop_back();
diff --git a/src/VoidScript.hpp b/src/VoidScript.hpp
index 5fa3ef0..6ed0e02 100644
--- a/src/VoidScript.hpp
+++ b/src/VoidScript.hpp
@@ -6,12 +6,18 @@
 
 #include "Interpreter/Interpreter.hpp"
 #include "Lexer/Lexer.hpp"
-#include "Parser/Parser.hpp"
 #include "Modules/ModuleManager.hpp"
+#include "Modules/PrintNlModule.hpp"
 #include "Modules/PrintModule.hpp"
+#include "Parser/Parser.hpp"
 
 class VoidScript {
   private:
+    // Debug flags for various components
+    bool                            debugLexer_       = false;
+    bool                            debugParser_      = false;
+    bool                            debugInterpreter_ = false;
+    bool                            debugSymbolTable_ = false;
     std::vector<std::string>        files;
     std::shared_ptr<Lexer::Lexer>   lexer  = nullptr;
     std::shared_ptr<Parser::Parser> parser = nullptr;
@@ -31,12 +37,23 @@
     }
 
   public:
-    VoidScript(const std::string & file) :
-
+    /**
+     * @param file               initial script file
+     * @param debugLexer         enable lexer debug output
+     * @param debugParser        enable parser debug output
+     * @param debugInterpreter   enable interpreter debug output
+     */
+    VoidScript(const std::string & file, bool debugLexer = false, bool debugParser = false,
+               bool debugInterpreter = false, bool debugSymbolTable = false) :
+        debugLexer_(debugLexer),
+        debugParser_(debugParser),
+        debugInterpreter_(debugInterpreter),
+        debugSymbolTable_(debugSymbolTable),
         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>());
+        Modules::ModuleManager::instance().addModule(std::make_unique<Modules::PrintNlModule>());
         this->files.emplace(this->files.begin(), file);
 
         lexer->setKeyWords(Parser::Parser::keywords);
@@ -44,7 +61,10 @@
 
     int run() {
         try {
-            // Register all built-in modules before execution
+            // Load plugin modules from 'modules' directory (case-insensitive)
+            Modules::ModuleManager::instance().loadPlugins("modules");
+            Modules::ModuleManager::instance().loadPlugins("Modules");
+            // Register all built-in and plugin modules before execution
             Modules::ModuleManager::instance().registerAll();
             while (!files.empty()) {
                 std::string       file         = files.back();
@@ -60,18 +80,32 @@
 
                 this->lexer->addNamespaceInput(ns, file_content);
                 const auto tokens = this->lexer->tokenizeNamespace(ns);
+                if (debugLexer_) {
+                    std::cerr << "[Debug][Lexer] Tokens for namespace '" << ns << "':\n";
+                    for (const auto & tok : tokens) {
+                        std::cerr << tok.dump();
+                    }
+                }
 
                 parser->parseScript(tokens, file_content, file);
+                if (debugParser_) {
+                    std::cerr << "[Debug][Parser] Operations for namespace '" << ns << "':\n";
+                    for (const auto & op : Operations::Container::instance()->getAll(ns)) {
+                        std::cerr << op->toString() << "\n";
+                    }
+                }
 
-                Interpreter::Interpreter interpreter;
+                // Execute interpreter with optional debug output
+                Interpreter::Interpreter interpreter(debugInterpreter_);
                 interpreter.run();
-
-                std::cout << Symbols::SymbolContainer::dump() << "\n";
+                if (debugSymbolTable_) {
+                    std::cout << Symbols::SymbolContainer::dump() << "\n";
+                }
             }  // while (!files.empty())
 
             return 0;
         } catch (const std::exception & e) {
-            std::cerr  << e.what() << '\n';
+            std::cerr << e.what() << '\n';
             return 1;
         }
         return 1;
diff --git a/test_scripts/escape.vs b/test_scripts/escape.vs
new file mode 100644
index 0000000..9742aee
--- /dev/null
+++ b/test_scripts/escape.vs
@@ -0,0 +1,2 @@
+string $variable = "This is an escaped \"string\" ";
+print($variable);
diff --git a/test_scripts/escape_sequences.vs b/test_scripts/escape_sequences.vs
new file mode 100644
index 0000000..cc13d9a
--- /dev/null
+++ b/test_scripts/escape_sequences.vs
@@ -0,0 +1,17 @@
+# String Escape Sequences Feature Test
+
+// Newline
+string $new = "Hello\nWorld";
+print("$new ->\n", $new, "\n");
+
+// Tab
+string $tab = "col1\tcol2";
+print("$tab -> [", $tab, "]\n");
+
+// Double-quote
+string $dq = "\"in quotes\"";
+print("$dq -> [", $dq, "]\n");
+
+// Unknown escape (\x treated as x)
+string $ux = "test\xescape";
+print("$ux -> [", $ux, "]\n");
\ No newline at end of file
diff --git a/test_scripts/expressions.vs b/test_scripts/expressions.vs
new file mode 100644
index 0000000..ac8cf52
--- /dev/null
+++ b/test_scripts/expressions.vs
@@ -0,0 +1,33 @@
+# Expressions Feature Test
+int $a = 10;
+int $b = 3;
+
+printnl("Startig variables");
+printnl("$a = ", $a, "\n$b = ", $b);
+printnl("--------------------------------");
+print("a + b = ", $a + $b, "\n");
+print("a - b = ", $a - $b, "\n");
+printnl("$b - $a = ", $b - $a);
+print("a * b = ", $a * $b, "\n");
+print("a / b = ", $a / $b, "\n");
+print("a % b = ", $a % $b, "\n");
+
+boolean $eq = ($a == $b);
+boolean $neq = ($a != $b);
+boolean $gt = ($a > $b);
+boolean $lt = ($a < $b);
+boolean $gte = ($a >= $b);
+boolean $lte = ($a <= $b);
+
+print("eq = ", $eq, "\n");
+print("neq = ", $neq, "\n");
+print("gt = ", $gt, "\n");
+print("lt = ", $lt, "\n");
+print("gte = ", $gte, "\n");
+print("lte = ", $lte, "\n");
+
+boolean $logical = ($a > 5) && ($b < 5);
+print("logical = ", $logical, "\n");
+
+boolean $not = !($a == $b);
+print("not = ", $not, "\n");
\ No newline at end of file
diff --git a/test_scripts/function_test.vs b/test_scripts/function_test.vs
deleted file mode 100644
index f31ca37..0000000
--- a/test_scripts/function_test.vs
+++ /dev/null
@@ -1,20 +0,0 @@
-int $num = 123;
-double $double = 12.3;
-string $variable = "This is a string content with a number: 123";
-
-string $variable2 = $variable;
-
-function test = (int $i) {
-    print("Param: ",$i);
-    int $result = $i + 1;
-}
-
-function increment = (int $i) int {
-    return $i + 1;
-}
-
-
-int $z = 10;
-increment($z);
-int $t = increment(2);
-print("The result is: ", $t);
\ No newline at end of file
diff --git a/test_scripts/test1.vs b/test_scripts/test1.vs
deleted file mode 100644
index 09f4203..0000000
--- a/test_scripts/test1.vs
+++ /dev/null
@@ -1,5 +0,0 @@
-string $name = "World 😀"; # world test
-string $greeting = "Hello ";
-string $smiley = "😀 = \\U0001F600 = \U0001F600\n";
-int $number = 123;
-double $number2 = 12.3;
diff --git a/test_scripts/test2.vs b/test_scripts/test2.vs
deleted file mode 100644
index 15fc79e..0000000
--- a/test_scripts/test2.vs
+++ /dev/null
@@ -1,16 +0,0 @@
-int $num = 123;
-double $double = 12.3;
-string $variable = "This is a string content with a number: 123";
-
-string $variable2 = $variable;
-
-function test = (int $i) {
-    int $result = $i + 1;
-}
-
-function increment = (int $i) int {
-    return $i + 1;
-}
-
-
-test(1);
\ No newline at end of file
diff --git a/test_scripts/undefined.vs b/test_scripts/undefined.vs
deleted file mode 100644
index a280f9a..0000000
--- a/test_scripts/undefined.vs
+++ /dev/null
@@ -1 +0,0 @@
-foo();
diff --git a/test_scripts/variables.vs b/test_scripts/variables.vs
new file mode 100644
index 0000000..e2a0c5f
--- /dev/null
+++ b/test_scripts/variables.vs
@@ -0,0 +1,14 @@
+# Variables Feature Test
+int $i = 42;
+double $d = 3.14;
+float $f = 2.718;
+boolean $b1 = true;
+boolean $b2 = false;
+string $s = "Hello VoidScript";
+
+print("$i = ", $i, "\n");
+print("$d = ", $d, "\n");
+print("$f = ", $f, "\n");
+print("$b1 = ", $b1, "\n");
+print("$b2 = ", $b2, "\n");
+print("$s = ", $s, "\n");
\ No newline at end of file

--
Gitblit v1.9.3