add dynamic module load, more escape seq
10 files modified
13 files added
4 files deleted
| | |
| | | # 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) |
| | | |
| | | |
| | | |
| | |
| | | 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) |
| | | |
| New file |
| | |
| | | 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) |
| New file |
| | |
| | | // 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); |
| | | } |
| New file |
| | |
| | | // 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 |
| New file |
| | |
| | | #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 |
| | | } |
| New file |
| | |
| | | // 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>() |
| | | ); |
| | | } |
| New file |
| | |
| | | // 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 |
| New file |
| | |
| | | string $url = "https://jsonplaceholder.typicode.com/todos/1"; |
| | | |
| | | string $response = curlGet($url); |
| | | |
| | | printnl("Response: ", $response); |
| New file |
| | |
| | | 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); |
| | |
| | | #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[]) { |
| | |
| | | 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"; |
| | |
| | | |
| | | 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(); |
| | | } |
| | |
| | | "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) { |
| | |
| | | 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_); |
| | | } |
| | |
| | | |
| | | 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). |
| | |
| | | } |
| | | |
| | | 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: |
| | |
| | | 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 += '\\'; |
| | |
| | | } |
| | | } |
| | | |
| | | // 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 } |
| | | }; |
| | |
| | | |
| | | [[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) { |
| | |
| | | #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. |
| | |
| | | * @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. |
| | |
| | | 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. |
| | |
| | | /** |
| | | * @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 |
| | |
| | | for (const auto &v : args) { |
| | | std::cout << Symbols::Value::to_string(v); |
| | | } |
| | | std::cout << std::endl; |
| | | return Symbols::Value(); |
| | | }); |
| | | } |
| New file |
| | |
| | | // 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 |
| | |
| | | // 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)) { |
| | |
| | | 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(); |
| | |
| | | |
| | | #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; |
| | |
| | | } |
| | | |
| | | 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); |
| | |
| | | |
| | | 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(); |
| | |
| | | |
| | | 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; |
| New file |
| | |
| | | string $variable = "This is an escaped \"string\" "; |
| | | print($variable); |
| New file |
| | |
| | | # 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"); |
| New file |
| | |
| | | # 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"); |
| New file |
| | |
| | | # 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"); |