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