A simple scripting language in C++
Ferenc Szontágh
2025-04-18 c91e935c62b8e254b9daadf37b915c983518bff4
add dynamic module load, more escape seq
10 files modified
13 files added
4 files deleted
692 ■■■■ changed files
CMakeLists.txt 13 ●●●●● patch | view | raw | blame | history
Modules/CurlModule/CMakeLists.txt 18 ●●●●● patch | view | raw | blame | history
Modules/CurlModule/src/CurlModule.cpp 86 ●●●●● patch | view | raw | blame | history
Modules/CurlModule/src/CurlModule.hpp 31 ●●●●● patch | view | raw | blame | history
Modules/CurlModule/src/PLuginInit.hpp 14 ●●●●● patch | view | raw | blame | history
Modules/CurlModule/src/PluginInit.cpp 14 ●●●●● patch | view | raw | blame | history
Modules/CurlModule/src/PluginInit.h 11 ●●●●● patch | view | raw | blame | history
Modules/CurlModule/test_scripts/curl_get.vs 5 ●●●●● patch | view | raw | blame | history
Modules/CurlModule/test_scripts/curl_post.vs 8 ●●●●● patch | view | raw | blame | history
cli/main.cpp 72 ●●●● patch | view | raw | blame | history
src/Interpreter/BinaryExpressionNode.hpp 41 ●●●●● patch | view | raw | blame | history
src/Interpreter/Interpreter.hpp 13 ●●●●● patch | view | raw | blame | history
src/Lexer/Lexer.cpp 25 ●●●●● patch | view | raw | blame | history
src/Lexer/Operators.hpp 21 ●●●● patch | view | raw | blame | history
src/Modules/ModuleManager.hpp 120 ●●●● patch | view | raw | blame | history
src/Modules/PrintModule.hpp 1 ●●●● patch | view | raw | blame | history
src/Modules/PrintNlModule.hpp 31 ●●●●● patch | view | raw | blame | history
src/Parser/Parser.cpp 14 ●●●●● patch | view | raw | blame | history
src/VoidScript.hpp 46 ●●●● patch | view | raw | blame | history
test_scripts/escape.vs 2 ●●●●● patch | view | raw | blame | history
test_scripts/escape_sequences.vs 17 ●●●●● patch | view | raw | blame | history
test_scripts/expressions.vs 33 ●●●●● patch | view | raw | blame | history
test_scripts/function_test.vs 20 ●●●●● patch | view | raw | blame | history
test_scripts/test1.vs 5 ●●●●● patch | view | raw | blame | history
test_scripts/test2.vs 16 ●●●●● patch | view | raw | blame | history
test_scripts/undefined.vs 1 ●●●● patch | view | raw | blame | history
test_scripts/variables.vs 14 ●●●●● patch | view | raw | blame | history
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)
Modules/CurlModule/CMakeLists.txt
New file
@@ -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)
Modules/CurlModule/src/CurlModule.cpp
New file
@@ -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);
}
Modules/CurlModule/src/CurlModule.hpp
New file
@@ -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
Modules/CurlModule/src/PLuginInit.hpp
New file
@@ -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
}
Modules/CurlModule/src/PluginInit.cpp
New file
@@ -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>()
    );
}
Modules/CurlModule/src/PluginInit.h
New file
@@ -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
Modules/CurlModule/test_scripts/curl_get.vs
New file
@@ -0,0 +1,5 @@
string $url = "https://jsonplaceholder.typicode.com/todos/1";
string $response = curlGet($url);
printnl("Response: ", $response);
Modules/CurlModule/test_scripts/curl_post.vs
New file
@@ -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);
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" },
    { "--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") {
    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;
            }
            if (arg == "--version") {
                std::cout << "Version:      " << VERSION_MAJOR << "." << VERSION_MINOR << "." << VERSION_PATCH;
                std::cout << " (" << VERSION_GIT_HASH << ")\n";
        } 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;
            }
            return 0;
        }
        std::cerr << "Error: Unknown option " << arg << "\n";
        } 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;
    }
    file = arg;
            } 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;
        }
    }
    if (file.empty()) {
        std::cerr << "Error: No input file specified\n";
        std::cerr << usage << "\n";
        return 1;
    }
    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();
}
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_);
        }
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:
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         }
    };
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) {
    // 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)));
        } else {
        return true;
    }
    if (token.type == Tokens::Type::STRING_LITERAL) {
        // String literal: use literal value
            output_queue.push_back(
                Parser::ParsedExpression::makeLiteral(
                    Symbols::Value::fromString(token.value, expected_var_type)));
                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) {
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.
@@ -28,9 +39,7 @@
     * @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.
@@ -45,8 +54,9 @@
    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_;
    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.
@@ -61,21 +71,105 @@
    /**
     * @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
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();
        });
    }
src/Modules/PrintNlModule.hpp
New file
@@ -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
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();
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,13 +80,27 @@
                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();
                if (debugSymbolTable_) {
                std::cout << Symbols::SymbolContainer::dump() << "\n";
                }
            }  // while (!files.empty())
            return 0;
test_scripts/escape.vs
New file
@@ -0,0 +1,2 @@
string $variable = "This is an escaped \"string\" ";
print($variable);
test_scripts/escape_sequences.vs
New file
@@ -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");
test_scripts/expressions.vs
New file
@@ -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");
test_scripts/function_test.vs
File was deleted
test_scripts/test1.vs
File was deleted
test_scripts/test2.vs
File was deleted
test_scripts/undefined.vs
File was deleted
test_scripts/variables.vs
New file
@@ -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");