From d092df5264f6e48f9d59650c092b8297382b1316 Mon Sep 17 00:00:00 2001
From: Ferenc Szontágh <szf@fsociety.hu>
Date: Fri, 18 Apr 2025 21:16:43 +0000
Subject: [PATCH] some built-in io module
---
src/VoidScript.hpp | 3
temp_test_io.txt | 1
src/Interpreter/CallStatementNode.hpp | 98 +++++++--------
src/Interpreter/ForStatementNode.hpp | 68 +++++-----
src/Interpreter/ConditionalStatementNode.hpp | 28 ++--
test_scripts/file_io.vs | 9 +
src/Interpreter/DeclareFunctionStatementNode.hpp | 15 +
src/Modules/BaseModule.hpp | 3
temp_test_io2.txt | 1
src/Modules/FileModule.hpp | 93 +++++++++++++++
src/Interpreter/DeclareVariableStatementNode.hpp | 38 +++---
11 files changed, 233 insertions(+), 124 deletions(-)
diff --git a/src/Interpreter/CallStatementNode.hpp b/src/Interpreter/CallStatementNode.hpp
index 5724318..6eac546 100644
--- a/src/Interpreter/CallStatementNode.hpp
+++ b/src/Interpreter/CallStatementNode.hpp
@@ -34,60 +34,54 @@
args_(std::move(args)) {}
void interpret(Interpreter & interpreter) const override {
- using namespace Symbols;
- // Evaluate argument expressions
- std::vector<Value> argValues;
- argValues.reserve(args_.size());
- for (const auto & expr : args_) {
- argValues.push_back(expr->evaluate(interpreter));
- }
-
- // Handle built-in function callbacks
- {
- auto &mgr = Modules::ModuleManager::instance();
- if (mgr.hasFunction(functionName_)) {
- mgr.callFunction(functionName_, argValues);
- return;
+ try {
+ using namespace Symbols;
+ std::vector<Value> argValues;
+ argValues.reserve(args_.size());
+ for (const auto & expr : args_) {
+ argValues.push_back(expr->evaluate(interpreter));
}
+ {
+ auto &mgr = Modules::ModuleManager::instance();
+ if (mgr.hasFunction(functionName_)) {
+ mgr.callFunction(functionName_, argValues);
+ return;
+ }
+ }
+ SymbolContainer * sc = SymbolContainer::instance();
+ const std::string currentNs = sc->currentScopeName();
+ const std::string fnSymNs = currentNs + ".functions";
+ auto sym = sc->get(fnSymNs, functionName_);
+ if (!sym || sym->getKind() != Kind::Function) {
+ throw Exception("Function not found: " + functionName_, filename_, line_, column_);
+ }
+ auto funcSym = std::static_pointer_cast<FunctionSymbol>(sym);
+ const auto & params = funcSym->parameters();
+ if (params.size() != argValues.size()) {
+ throw Exception(
+ "Function '" + functionName_ + "' expects " + std::to_string(params.size()) +
+ " args, got " + std::to_string(argValues.size()),
+ filename_, line_, column_);
+ }
+ const std::string fnOpNs = currentNs + "." + functionName_;
+ sc->enter(fnOpNs);
+ for (size_t i = 0; i < params.size(); ++i) {
+ const auto & p = params[i];
+ const Value & v = argValues[i];
+ auto varSym = SymbolFactory::createVariable(p.name, v, fnOpNs);
+ sc->add(varSym);
+ }
+ auto ops = Operations::Container::instance()->getAll(fnOpNs);
+ auto it = ops.begin();
+ for (; it != ops.end(); ++it) {
+ interpreter.runOperation(*(*it));
+ }
+ sc->enterPreviousScope();
+ } catch (const Exception &) {
+ throw;
+ } catch (const std::exception &e) {
+ throw Exception(e.what(), filename_, line_, column_);
}
- // Lookup function symbol in functions namespace
- SymbolContainer * sc = SymbolContainer::instance();
- const std::string currentNs = sc->currentScopeName();
- const std::string fnSymNs = currentNs + ".functions";
- auto sym = sc->get(fnSymNs, functionName_);
- if (!sym || sym->getKind() != Kind::Function) {
- throw Exception("Function not found: " + functionName_, filename_, line_, column_);
- }
- auto funcSym = std::static_pointer_cast<FunctionSymbol>(sym);
-
- // Check parameter count
- const auto & params = funcSym->parameters();
- if (params.size() != argValues.size()) {
- throw Exception(
- "Function '" + functionName_ + "' expects " + std::to_string(params.size()) +
- " args, got " + std::to_string(argValues.size()),
- filename_, line_, column_);
- }
-
- // Enter function scope to bind parameters and execute body
- const std::string fnOpNs = currentNs + "." + functionName_;
- sc->enter(fnOpNs);
- // Bind parameters as local variables
- for (size_t i = 0; i < params.size(); ++i) {
- const auto & p = params[i];
- const Value & v = argValues[i];
-
- auto varSym = SymbolFactory::createVariable(p.name, v, fnOpNs);
- sc->add(varSym);
- }
- // Execute function body operations
- auto ops = Operations::Container::instance()->getAll(fnOpNs);
- auto it = ops.begin();
- for (; it != ops.end(); ++it) {
- interpreter.runOperation(*(*it));
- }
- // Exit function scope
- sc->enterPreviousScope();
}
std::string toString() const override {
diff --git a/src/Interpreter/ConditionalStatementNode.hpp b/src/Interpreter/ConditionalStatementNode.hpp
index efb0730..969bb8c 100644
--- a/src/Interpreter/ConditionalStatementNode.hpp
+++ b/src/Interpreter/ConditionalStatementNode.hpp
@@ -33,18 +33,22 @@
elseBranch_(std::move(elseBranch)) {}
void interpret(class Interpreter & interpreter) const override {
- // Evaluate condition
- auto val = condition_->evaluate(interpreter);
- bool cond = false;
- if (val.getType() == Symbols::Variables::Type::BOOLEAN) {
- cond = val.get<bool>();
- } else {
- throw Exception("Condition did not evaluate to boolean", filename_, line_, column_);
- }
- // Execute appropriate branch
- const auto & branch = cond ? thenBranch_ : elseBranch_;
- for (const auto & stmt : branch) {
- stmt->interpret(interpreter);
+ try {
+ auto val = condition_->evaluate(interpreter);
+ bool cond = false;
+ if (val.getType() == Symbols::Variables::Type::BOOLEAN) {
+ cond = val.get<bool>();
+ } else {
+ throw Exception("Condition did not evaluate to boolean", filename_, line_, column_);
+ }
+ const auto & branch = cond ? thenBranch_ : elseBranch_;
+ for (const auto & stmt : branch) {
+ stmt->interpret(interpreter);
+ }
+ } catch (const Exception &) {
+ throw;
+ } catch (const std::exception &e) {
+ throw Exception(e.what(), filename_, line_, column_);
}
}
diff --git a/src/Interpreter/DeclareFunctionStatementNode.hpp b/src/Interpreter/DeclareFunctionStatementNode.hpp
index f2ddb04..9290de5 100644
--- a/src/Interpreter/DeclareFunctionStatementNode.hpp
+++ b/src/Interpreter/DeclareFunctionStatementNode.hpp
@@ -37,12 +37,17 @@
ns(ns) {}
void interpret(Interpreter & /*interpreter*/) const override {
- //Symbols::Value value = expression_->evaluate(interpreter);
- if (Symbols::SymbolContainer::instance()->exists(functionName_)) {
- throw Exception("Function already declared: " + functionName_, filename_, line_, column_);
+ try {
+ if (Symbols::SymbolContainer::instance()->exists(functionName_)) {
+ throw Exception("Function already declared: " + functionName_, filename_, line_, column_);
+ }
+ const auto func = Symbols::SymbolFactory::createFunction(functionName_, ns, params_, "", returnType_);
+ Symbols::SymbolContainer::instance()->add(func);
+ } catch (const Exception &) {
+ throw;
+ } catch (const std::exception &e) {
+ throw Exception(e.what(), filename_, line_, column_);
}
- const auto func = Symbols::SymbolFactory::createFunction(functionName_, ns, params_, "", returnType_);
- Symbols::SymbolContainer::instance()->add(func);
}
std::string toString() const override {
diff --git a/src/Interpreter/DeclareVariableStatementNode.hpp b/src/Interpreter/DeclareVariableStatementNode.hpp
index 1ee7874..4b4fe5d 100644
--- a/src/Interpreter/DeclareVariableStatementNode.hpp
+++ b/src/Interpreter/DeclareVariableStatementNode.hpp
@@ -33,25 +33,27 @@
ns(ns) {}
void interpret(Interpreter & interpreter) const override {
- // Evaluate the expression and enforce declared type matches actual value type
- Symbols::Value value = expression_->evaluate(interpreter);
- // Check for duplicate declaration
- if (Symbols::SymbolContainer::instance()->exists(variableName_)) {
- throw Exception("Variable already declared: " + variableName_, filename_, line_, column_);
+ try {
+ Symbols::Value value = expression_->evaluate(interpreter);
+ if (Symbols::SymbolContainer::instance()->exists(variableName_)) {
+ throw Exception("Variable already declared: " + variableName_, filename_, line_, column_);
+ }
+ if (value.getType() != variableType_) {
+ using namespace Symbols::Variables;
+ std::string expected = TypeToString(variableType_);
+ std::string actual = TypeToString(value.getType());
+ throw Exception(
+ "Type mismatch for variable '" + variableName_ +
+ "': expected '" + expected + "' but got '" + actual + "'",
+ filename_, line_, column_);
+ }
+ const auto variable = Symbols::SymbolFactory::createVariable(variableName_, value, ns, variableType_);
+ Symbols::SymbolContainer::instance()->add(variable);
+ } catch (const Exception &) {
+ throw;
+ } catch (const std::exception &e) {
+ throw Exception(e.what(), filename_, line_, column_);
}
- // Enforce type correctness: the evaluated value must match the declared type
- if (value.getType() != variableType_) {
- using namespace Symbols::Variables;
- std::string expected = TypeToString(variableType_);
- std::string actual = TypeToString(value.getType());
- throw Exception(
- "Type mismatch for variable '" + variableName_ +
- "': expected '" + expected + "' but got '" + actual + "'",
- filename_, line_, column_);
- }
- // Create and add the variable symbol
- const auto variable = Symbols::SymbolFactory::createVariable(variableName_, value, ns, variableType_);
- Symbols::SymbolContainer::instance()->add(variable);
}
std::string toString() const override {
diff --git a/src/Interpreter/ForStatementNode.hpp b/src/Interpreter/ForStatementNode.hpp
index 0a1092e..01eefb3 100644
--- a/src/Interpreter/ForStatementNode.hpp
+++ b/src/Interpreter/ForStatementNode.hpp
@@ -43,42 +43,42 @@
body_(std::move(body)) {}
void interpret(Interpreter & interpreter) const override {
- using namespace Symbols;
- // Evaluate iterable expression
- auto iterableVal = iterableExpr_->evaluate(interpreter);
- if (iterableVal.getType() != Variables::Type::OBJECT) {
- throw Exception("For-in loop applied to non-object", filename_, line_, column_);
- }
- // Access underlying object map
- const auto & objMap = std::get<Value::ObjectMap>(iterableVal.get());
- auto * symContainer = SymbolContainer::instance();
- const std::string base_ns = symContainer->currentScopeName();
- const std::string var_ns = base_ns + ".variables";
- // Iterate through object entries
- for (const auto & entry : objMap) {
- // Key binding
- const std::string & key = entry.first;
- Value keyVal(key);
- if (!symContainer->exists(keyName_, var_ns)) {
- auto sym = SymbolFactory::createVariable(keyName_, keyVal, base_ns);
- symContainer->add(sym);
- } else {
- auto sym = symContainer->get(var_ns, keyName_);
- sym->setValue(keyVal);
+ try {
+ using namespace Symbols;
+ auto iterableVal = iterableExpr_->evaluate(interpreter);
+ if (iterableVal.getType() != Variables::Type::OBJECT) {
+ throw Exception("For-in loop applied to non-object", filename_, line_, column_);
}
- // Value binding
- Value valVal = entry.second;
- if (!symContainer->exists(valueName_, var_ns)) {
- auto sym = SymbolFactory::createVariable(valueName_, valVal, base_ns);
- symContainer->add(sym);
- } else {
- auto sym = symContainer->get(var_ns, valueName_);
- sym->setValue(valVal);
+ const auto & objMap = std::get<Value::ObjectMap>(iterableVal.get());
+ auto * symContainer = SymbolContainer::instance();
+ const std::string base_ns = symContainer->currentScopeName();
+ const std::string var_ns = base_ns + ".variables";
+ for (const auto & entry : objMap) {
+ const std::string & key = entry.first;
+ Value keyVal(key);
+ if (!symContainer->exists(keyName_, var_ns)) {
+ auto sym = SymbolFactory::createVariable(keyName_, keyVal, base_ns);
+ symContainer->add(sym);
+ } else {
+ auto sym = symContainer->get(var_ns, keyName_);
+ sym->setValue(keyVal);
+ }
+ Value valVal = entry.second;
+ if (!symContainer->exists(valueName_, var_ns)) {
+ auto sym = SymbolFactory::createVariable(valueName_, valVal, base_ns);
+ symContainer->add(sym);
+ } else {
+ auto sym = symContainer->get(var_ns, valueName_);
+ sym->setValue(valVal);
+ }
+ for (const auto & stmt : body_) {
+ stmt->interpret(interpreter);
+ }
}
- // Execute loop body
- for (const auto & stmt : body_) {
- stmt->interpret(interpreter);
- }
+ } catch (const Exception &) {
+ throw;
+ } catch (const std::exception &e) {
+ throw Exception(e.what(), filename_, line_, column_);
}
}
diff --git a/src/Modules/BaseModule.hpp b/src/Modules/BaseModule.hpp
index 27475bb..a629851 100644
--- a/src/Modules/BaseModule.hpp
+++ b/src/Modules/BaseModule.hpp
@@ -2,9 +2,6 @@
#ifndef MODULES_BASEMODULE_HPP
#define MODULES_BASEMODULE_HPP
-#include <string>
-#include "Symbols/SymbolContainer.hpp"
-#include "Symbols/SymbolFactory.hpp"
namespace Modules {
diff --git a/src/Modules/FileModule.hpp b/src/Modules/FileModule.hpp
new file mode 100644
index 0000000..e3da909
--- /dev/null
+++ b/src/Modules/FileModule.hpp
@@ -0,0 +1,93 @@
+// FileModule.hpp
+#ifndef MODULES_FILEMODULE_HPP
+#define MODULES_FILEMODULE_HPP
+
+#include <filesystem>
+#include <fstream>
+#include <iterator>
+#include <stdexcept>
+#include <vector>
+#include <string>
+#include "BaseModule.hpp"
+#include "ModuleManager.hpp"
+#include "Symbols/Value.hpp"
+
+namespace Modules {
+
+/**
+ * @brief Module providing simple file I/O functions:
+ * file_get_contents(filename) -> string content
+ * file_put_contents(filename, content, overwrite) -> undefined, throws on error
+ * file_exists(filename) -> bool
+ */
+class FileModule : public BaseModule {
+ public:
+ void registerModule() override {
+ auto &mgr = ModuleManager::instance();
+ // Read entire file content
+ mgr.registerFunction("file_get_contents", [](const std::vector<Symbols::Value> &args) {
+ using namespace Symbols;
+ if (args.size() != 1) {
+ throw std::runtime_error("file_get_contents expects 1 argument");
+ }
+ if (args[0].getType() != Variables::Type::STRING) {
+ throw std::runtime_error("file_get_contents expects string filename");
+ }
+ const std::string filename = args[0].get<std::string>();
+ if (!std::filesystem::exists(filename)) {
+ throw std::runtime_error("File does not exist: " + filename);
+ }
+ std::ifstream input(filename, std::ios::in | std::ios::binary);
+ if (!input.is_open()) {
+ throw std::runtime_error("Could not open file: " + filename);
+ }
+ std::string content((std::istreambuf_iterator<char>(input)), std::istreambuf_iterator<char>());
+ input.close();
+ return Value(content);
+ });
+ // Write content to file, with optional overwrite
+ mgr.registerFunction("file_put_contents", [](const std::vector<Symbols::Value> &args) {
+ using namespace Symbols;
+ if (args.size() != 3) {
+ throw std::runtime_error("file_put_contents expects 3 arguments");
+ }
+ if (args[0].getType() != Variables::Type::STRING ||
+ args[1].getType() != Variables::Type::STRING ||
+ args[2].getType() != Variables::Type::BOOLEAN) {
+ throw std::runtime_error("file_put_contents expects (string, string, bool)");
+ }
+ const std::string filename = args[0].get<std::string>();
+ const std::string content = args[1].get<std::string>();
+ const bool overwrite = args[2].get<bool>();
+ if (!overwrite && std::filesystem::exists(filename)) {
+ throw std::runtime_error("File already exists: " + filename);
+ }
+ std::ofstream output(filename, std::ios::out | std::ios::binary | std::ios::trunc);
+ if (!output.is_open()) {
+ throw std::runtime_error("Could not open file for writing: " + filename);
+ }
+ output << content;
+ if (!output) {
+ throw std::runtime_error("Failed to write to file: " + filename);
+ }
+ output.close();
+ return Value();
+ });
+ // Check if file exists
+ mgr.registerFunction("file_exists", [](const std::vector<Symbols::Value> &args) {
+ using namespace Symbols;
+ if (args.size() != 1) {
+ throw std::runtime_error("file_exists expects 1 argument");
+ }
+ if (args[0].getType() != Variables::Type::STRING) {
+ throw std::runtime_error("file_exists expects string filename");
+ }
+ const std::string filename = args[0].get<std::string>();
+ bool exists = std::filesystem::exists(filename);
+ return Value(exists);
+ });
+ }
+};
+
+} // namespace Modules
+#endif // MODULES_FILEMODULE_HPP
\ No newline at end of file
diff --git a/src/VoidScript.hpp b/src/VoidScript.hpp
index 8bbf24d..af39b5f 100644
--- a/src/VoidScript.hpp
+++ b/src/VoidScript.hpp
@@ -10,6 +10,7 @@
#include "Modules/PrintNlModule.hpp"
#include "Modules/PrintModule.hpp"
#include "Modules/TypeofModule.hpp"
+#include "Modules/FileModule.hpp"
#include "Parser/Parser.hpp"
class VoidScript {
@@ -57,6 +58,8 @@
Modules::ModuleManager::instance().addModule(std::make_unique<Modules::PrintNlModule>());
// typeof() builtin
Modules::ModuleManager::instance().addModule(std::make_unique<Modules::TypeofModule>());
+ // file I/O builtin
+ Modules::ModuleManager::instance().addModule(std::make_unique<Modules::FileModule>());
this->files.emplace(this->files.begin(), file);
lexer->setKeyWords(Parser::Parser::keywords);
diff --git a/temp_test_io.txt b/temp_test_io.txt
new file mode 100644
index 0000000..b6fc4c6
--- /dev/null
+++ b/temp_test_io.txt
@@ -0,0 +1 @@
+hello
\ No newline at end of file
diff --git a/temp_test_io2.txt b/temp_test_io2.txt
new file mode 100644
index 0000000..2147e41
--- /dev/null
+++ b/temp_test_io2.txt
@@ -0,0 +1 @@
+second
\ No newline at end of file
diff --git a/test_scripts/file_io.vs b/test_scripts/file_io.vs
new file mode 100644
index 0000000..7088fa2
--- /dev/null
+++ b/test_scripts/file_io.vs
@@ -0,0 +1,9 @@
+# File I/O Feature Test
+string $f = "test_file_io.txt";
+print("file_exists before create: ", file_exists($f), "\n");
+file_put_contents($f, "Hello from VoidScript!", false);
+print("file_exists after create: ", file_exists($f), "\n");
+print("file_get_contents: ", file_get_contents($f), "\n");
+# Overwrite with permission
+file_put_contents($f, "Overwritten content", true);
+print("file_get_contents after overwrite: ", file_get_contents($f), "\n");
\ No newline at end of file
--
Gitblit v1.9.3