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