From ea9af87ba4399d094180f06be59c878d864a17e0 Mon Sep 17 00:00:00 2001
From: Ferenc Szontágh <szf@fsociety.hu>
Date: Sat, 19 Apr 2025 17:06:48 +0000
Subject: [PATCH] implemented array, added some new builtin functions

---
 src/Parser/Parser.cpp                         |  155 ++++++++--
 src/Interpreter/ExpressionBuilder.hpp         |   10 
 src/Modules/BuiltIn/StringModule.hpp          |  101 ++++++
 test_scripts/test_string.vs                   |    3 
 src/Modules/BuiltIn/FileModule.hpp            |    4 
 src/Modules/BuiltIn/VariableHelpersModule.hpp |   48 +++
 test_scripts/array.vs                         |   18 +
 src/Interpreter/ArrayAccessExpressionNode.hpp |   55 +++
 test_scripts/test_substr.vs                   |    4 
 /dev/null                                     |   45 ---
 src/VoidScript.hpp                            |   26 +
 src/Modules/BuiltIn/ArrayModule.hpp           |   44 ++
 src/Modules/BuiltIn/JsonModule.hpp            |  328 +++++++++++++++++++++
 src/Modules/BuiltIn/PrintModule.hpp           |   44 ++
 test_scripts/test_array.vs                    |    2 
 15 files changed, 794 insertions(+), 93 deletions(-)

diff --git a/src/Interpreter/ArrayAccessExpressionNode.hpp b/src/Interpreter/ArrayAccessExpressionNode.hpp
new file mode 100644
index 0000000..2312f45
--- /dev/null
+++ b/src/Interpreter/ArrayAccessExpressionNode.hpp
@@ -0,0 +1,55 @@
+ #ifndef INTERPRETER_ARRAY_ACCESS_EXPRESSION_NODE_HPP
+ #define INTERPRETER_ARRAY_ACCESS_EXPRESSION_NODE_HPP
+
+ #include "ExpressionNode.hpp"
+ #include "Symbols/Value.hpp"
+ #include <stdexcept>
+ #include <string>
+
+ namespace Interpreter {
+
+ /**
+  * @brief Expression node for dynamic array/object indexing: expr[index]
+  */
+ class ArrayAccessExpressionNode : public ExpressionNode {
+   private:
+    std::unique_ptr<ExpressionNode> arrayExpr_;
+    std::unique_ptr<ExpressionNode> indexExpr_;
+
+   public:
+    ArrayAccessExpressionNode(std::unique_ptr<ExpressionNode> arrayExpr,
+                              std::unique_ptr<ExpressionNode> indexExpr)
+        : arrayExpr_(std::move(arrayExpr)), indexExpr_(std::move(indexExpr)) {}
+
+    Symbols::Value evaluate(Interpreter &interpreter) const override {
+        // Evaluate the container (object or array)
+        Symbols::Value container = arrayExpr_->evaluate(interpreter);
+        if (container.getType() != Symbols::Variables::Type::OBJECT) {
+            throw std::runtime_error("Attempted to index non-array");
+        }
+        const auto & map = std::get<Symbols::Value::ObjectMap>(container.get());
+        // Evaluate the index
+        Symbols::Value idxVal = indexExpr_->evaluate(interpreter);
+        std::string key;
+        if (idxVal.getType() == Symbols::Variables::Type::INTEGER) {
+            key = std::to_string(idxVal.get<int>());
+        } else if (idxVal.getType() == Symbols::Variables::Type::STRING) {
+            key = idxVal.get<std::string>();
+        } else {
+            throw std::runtime_error("Array index must be integer or string");
+        }
+        auto it = map.find(key);
+        if (it == map.end()) {
+            throw std::runtime_error("Index not found: " + key);
+        }
+        return it->second;
+    }
+
+    std::string toString() const override {
+        return arrayExpr_->toString() + "[" + indexExpr_->toString() + "]";
+    }
+};
+
+} // namespace Interpreter
+
+ #endif // INTERPRETER_ARRAY_ACCESS_EXPRESSION_NODE_HPP
\ No newline at end of file
diff --git a/src/Interpreter/ExpressionBuilder.hpp b/src/Interpreter/ExpressionBuilder.hpp
index 778dbf3..bb5fefa 100644
--- a/src/Interpreter/ExpressionBuilder.hpp
+++ b/src/Interpreter/ExpressionBuilder.hpp
@@ -11,6 +11,7 @@
 #include "Interpreter/UnaryExpressionNode.hpp"  // <-- új include
 #include "Interpreter/CallExpressionNode.hpp"
 #include "Interpreter/MemberExpressionNode.hpp"
+#include "Interpreter/ArrayAccessExpressionNode.hpp"
 #include "Interpreter/ObjectExpressionNode.hpp"
 #include "Interpreter/ObjectExpressionNode.hpp"
 #include "Parser/ParsedExpression.hpp"
@@ -29,10 +30,16 @@
 
         case Kind::Binary:
             {
+                // Array/object dynamic indexing: operator []
+                if (expr->op == "[]") {
+                    auto arrExpr = buildExpressionFromParsed(expr->lhs);
+                    auto idxExpr = buildExpressionFromParsed(expr->rhs);
+                    return std::make_unique<Interpreter::ArrayAccessExpressionNode>(std::move(arrExpr), std::move(idxExpr));
+                }
+                // Member access for object properties: '->'
                 if (expr->op == "->") {
                     auto objExpr = buildExpressionFromParsed(expr->lhs);
                     std::string propName;
-                    // RHS parsed expression should be a literal string or variable parser node
                     if (expr->rhs->kind == ParsedExpression::Kind::Literal &&
                         expr->rhs->value.getType() == Symbols::Variables::Type::STRING) {
                         propName = expr->rhs->value.get<std::string>();
@@ -43,6 +50,7 @@
                     }
                     return std::make_unique<Interpreter::MemberExpressionNode>(std::move(objExpr), propName);
                 }
+                // Default binary operator
                 auto lhs = buildExpressionFromParsed(expr->lhs);
                 auto rhs = buildExpressionFromParsed(expr->rhs);
                 return std::make_unique<Interpreter::BinaryExpressionNode>(std::move(lhs), expr->op, std::move(rhs));
diff --git a/src/Modules/BuiltIn/ArrayModule.hpp b/src/Modules/BuiltIn/ArrayModule.hpp
new file mode 100644
index 0000000..1127157
--- /dev/null
+++ b/src/Modules/BuiltIn/ArrayModule.hpp
@@ -0,0 +1,44 @@
+// ArrayModule.hpp
+#ifndef MODULES_ARRAYMODULE_HPP
+#define MODULES_ARRAYMODULE_HPP
+
+#include <stdexcept>
+#include <string>
+#include <vector>
+
+#include "Modules/BaseModule.hpp"
+#include "Modules/ModuleManager.hpp"
+#include "Symbols/Value.hpp"
+#include "Symbols/VariableTypes.hpp"
+
+namespace Modules {
+
+/**
+ * @brief Module providing a sizeof() function for array variables.
+ * Usage:
+ *   sizeof($array)   -> returns number of elements in the array
+ */
+class ArrayModule : public BaseModule {
+  public:
+    void registerModule() override {
+        auto & mgr = ModuleManager::instance();
+        mgr.registerFunction("sizeof", [](const std::vector<Symbols::Value> & args) {
+            using namespace Symbols;
+            if (args.size() != 1) {
+                throw std::runtime_error("sizeof expects exactly one argument");
+            }
+            const auto & val  = args[0];
+            auto   type = val.getType();
+            // Only allow array types (OBJECT)
+            if (type == Variables::Type::OBJECT) {
+                const auto & map = std::get<Value::ObjectMap>(val.get());
+                return Value(static_cast<int>(map.size()));
+            }
+            throw std::runtime_error("sizeof expects an array variable");
+        });
+    }
+};
+
+}  // namespace Modules
+
+#endif  // MODULES_ARRAYMODULE_HPP
diff --git a/src/Modules/FileModule.hpp b/src/Modules/BuiltIn/FileModule.hpp
similarity index 97%
rename from src/Modules/FileModule.hpp
rename to src/Modules/BuiltIn/FileModule.hpp
index e3da909..b08ab97 100644
--- a/src/Modules/FileModule.hpp
+++ b/src/Modules/BuiltIn/FileModule.hpp
@@ -8,8 +8,8 @@
 #include <stdexcept>
 #include <vector>
 #include <string>
-#include "BaseModule.hpp"
-#include "ModuleManager.hpp"
+#include "Modules/BaseModule.hpp"
+#include "Modules/ModuleManager.hpp"
 #include "Symbols/Value.hpp"
 
 namespace Modules {
diff --git a/src/Modules/BuiltIn/JsonModule.hpp b/src/Modules/BuiltIn/JsonModule.hpp
new file mode 100644
index 0000000..cd94ea5
--- /dev/null
+++ b/src/Modules/BuiltIn/JsonModule.hpp
@@ -0,0 +1,328 @@
+// JsonModule.hpp
+#ifndef MODULES_JSONMODULE_HPP
+#define MODULES_JSONMODULE_HPP
+
+#include <cctype>
+#include <stdexcept>
+#include <string>
+#include <variant>
+
+#include "Modules/BaseModule.hpp"
+#include "Modules/ModuleManager.hpp"
+#include "Symbols/Value.hpp"
+#include "Symbols/VariableTypes.hpp"
+
+namespace Modules {
+
+/**
+ * @brief Module providing JSON encode/decode functions.
+ *   json_encode(value) -> string
+ *   json_decode(string) -> object/value
+ */
+class JsonModule : public BaseModule {
+  public:
+    void registerModule() override {
+        auto & mgr = ModuleManager::instance();
+        // json_encode: serialize a Value to JSON string
+        mgr.registerFunction("json_encode", [](const std::vector<Symbols::Value> & args) {
+            using namespace Symbols;
+            if (args.size() != 1) {
+                throw std::runtime_error("json_encode expects 1 argument");
+            }
+            // forward to encoder
+            std::function<std::string(const Value &)> encode;
+            encode = [&](const Value & v) -> std::string {
+                const auto & var = v.get();
+                return std::visit(
+                    [&](auto && x) -> std::string {
+                        using T = std::decay_t<decltype(x)>;
+                        if constexpr (std::is_same_v<T, bool>) {
+                            return x ? "true" : "false";
+                        } else if constexpr (std::is_same_v<T, int> || std::is_same_v<T, double> ||
+                                             std::is_same_v<T, float>) {
+                            return std::to_string(x);
+                        } else if constexpr (std::is_same_v<T, std::string>) {
+                            // escape string
+                            std::string out = "\"";
+                            for (char c : x) {
+                                switch (c) {
+                                    case '"':
+                                        out += "\\\"";
+                                        break;
+                                    case '\\':
+                                        out += "\\\\";
+                                        break;
+                                    case '\b':
+                                        out += "\\b";
+                                        break;
+                                    case '\f':
+                                        out += "\\f";
+                                        break;
+                                    case '\n':
+                                        out += "\\n";
+                                        break;
+                                    case '\r':
+                                        out += "\\r";
+                                        break;
+                                    case '\t':
+                                        out += "\\t";
+                                        break;
+                                    default:
+                                        if (static_cast<unsigned char>(c) < 0x20) {
+                                            // control character
+                                            char buf[7];
+                                            std::snprintf(buf, sizeof(buf), "\\u%04x", c);
+                                            out += buf;
+                                        } else {
+                                            out += c;
+                                        }
+                                }
+                            }
+                            out += "\"";
+                            return out;
+                        } else if constexpr (std::is_same_v<T, Value::ObjectMap>) {
+                            std::string out   = "{";
+                            bool        first = true;
+                            for (const auto & kv : x) {
+                                if (!first) {
+                                    out += ",";
+                                }
+                                first = false;
+                                // key
+                                out += '"';
+                                // escape key string
+                                for (char c : kv.first) {
+                                    switch (c) {
+                                        case '"':
+                                            out += "\\\"";
+                                            break;
+                                        case '\\':
+                                            out += "\\\\";
+                                            break;
+                                        case '\b':
+                                            out += "\\b";
+                                            break;
+                                        case '\f':
+                                            out += "\\f";
+                                            break;
+                                        case '\n':
+                                            out += "\\n";
+                                            break;
+                                        case '\r':
+                                            out += "\\r";
+                                            break;
+                                        case '\t':
+                                            out += "\\t";
+                                            break;
+                                        default:
+                                            if (static_cast<unsigned char>(c) < 0x20) {
+                                                char buf[7];
+                                                std::snprintf(buf, sizeof(buf), "\\u%04x", c);
+                                                out += buf;
+                                            } else {
+                                                out += c;
+                                            }
+                                    }
+                                }
+                                out += '"';
+                                out += ':';
+                                out += encode(kv.second);
+                            }
+                            out += "}";
+                            return out;
+                        } else {
+                            return "null";
+                        }
+                    },
+                    var);
+            };
+            std::string result = encode(args[0]);
+            return Symbols::Value(result);
+        });
+        // json_decode: parse JSON string to Value (object/value)
+        mgr.registerFunction("json_decode", [](const std::vector<Symbols::Value> & args) {
+            using namespace Symbols;
+            if (args.size() != 1) {
+                throw std::runtime_error("json_decode expects 1 argument");
+            }
+            if (args[0].getType() != Variables::Type::STRING) {
+                throw std::runtime_error("json_decode expects a JSON string");
+            }
+            const std::string s = args[0].get<std::string>();
+            struct Parser {
+                const std::string & s;
+                size_t              pos = 0;
+                Parser(const std::string & str) : s(str), pos(0) {}
+                void skip() {
+                    while (pos < s.size() && std::isspace(static_cast<unsigned char>(s[pos]))) {
+                        pos++;
+                    }
+                }
+                std::string parseString() {
+                    skip();
+                    if (s[pos] != '"') {
+                        throw std::runtime_error("Invalid JSON string");
+                    }
+                    pos++;
+                    std::string out;
+                    while (pos < s.size()) {
+                        char c = s[pos++];
+                        if (c == '"') {
+                            break;
+                        }
+                        if (c == '\\') {
+                            if (pos >= s.size()) {
+                                break;
+                            }
+                            char e = s[pos++];
+                            switch (e) {
+                                case '"':
+                                    out += '"';
+                                    break;
+                                case '\\':
+                                    out += '\\';
+                                    break;
+                                case '/':
+                                    out += '/';
+                                    break;
+                                case 'b':
+                                    out += '\b';
+                                    break;
+                                case 'f':
+                                    out += '\f';
+                                    break;
+                                case 'n':
+                                    out += '\n';
+                                    break;
+                                case 'r':
+                                    out += '\r';
+                                    break;
+                                case 't':
+                                    out += '\t';
+                                    break;
+                                default:
+                                    out += e;
+                                    break;
+                            }
+                        } else {
+                            out += c;
+                        }
+                    }
+                    return out;
+                }
+                Value parseNumber() {
+                    skip();
+                    size_t start = pos;
+                    if (s[pos] == '-') {
+                        pos++;
+                    }
+                    while (pos < s.size() && std::isdigit(static_cast<unsigned char>(s[pos]))) {
+                        pos++;
+                    }
+                    bool isDouble = false;
+                    if (pos < s.size() && s[pos] == '.') {
+                        isDouble = true;
+                        pos++;
+                        while (pos < s.size() && std::isdigit(static_cast<unsigned char>(s[pos]))) {
+                            pos++;
+                        }
+                    }
+                    std::string num = s.substr(start, pos - start);
+                    try {
+                        if (isDouble) {
+                            return Value(std::stod(num));
+                        }
+                        return Value(std::stoi(num));
+                    } catch (...) {
+                        throw std::runtime_error("Invalid JSON number: " + num);
+                    }
+                }
+                Value parseBool() {
+                    skip();
+                    if (s.compare(pos, 4, "true") == 0) {
+                        pos += 4;
+                        return Value(true);
+                    }
+                    if (s.compare(pos, 5, "false") == 0) {
+                        pos += 5;
+                        return Value(false);
+                    }
+                    throw std::runtime_error("Invalid JSON boolean");
+                }
+                Value parseNull() {
+                    skip();
+                    if (s.compare(pos, 4, "null") == 0) {
+                        pos += 4;
+                        return Value::makeNull();
+                    }
+                    throw std::runtime_error("Invalid JSON null");
+                }
+                Value parseObject() {
+                    skip();
+                    if (s[pos] != '{') {
+                        throw std::runtime_error("Invalid JSON object");
+                    }
+                    pos++;
+                    skip();
+                    Value::ObjectMap obj;
+                    if (s[pos] == '}') {
+                        pos++;
+                        return Value(obj);
+                    }
+                    while (pos < s.size()) {
+                        skip();
+                        std::string key = parseString();
+                        skip();
+                        if (s[pos] != ':') {
+                            throw std::runtime_error("Expected ':' in object");
+                        }
+                        pos++;
+                        skip();
+                        Value val = parseValue();
+                        obj.emplace(key, val);
+                        skip();
+                        if (s[pos] == ',') {
+                            pos++;
+                            continue;
+                        }
+                        if (s[pos] == '}') {
+                            pos++;
+                            break;
+                        }
+                        throw std::runtime_error("Expected ',' or '}' in object");
+                    }
+                    return Value(obj);
+                }
+                Value parseValue() {
+                    skip();
+                    if (pos >= s.size()) {
+                        throw std::runtime_error("Empty JSON");
+                    }
+                    char c = s[pos];
+                    if (c == '{') {
+                        return parseObject();
+                    }
+                    if (c == '"') {
+                        std::string str = parseString();
+                        return Value(str);
+                    }
+                    if (c == 't' || c == 'f') {
+                        return parseBool();
+                    }
+                    if (c == 'n') {
+                        return parseNull();
+                    }
+                    if (c == '-' || std::isdigit(static_cast<unsigned char>(c))) {
+                        return parseNumber();
+                    }
+                    throw std::runtime_error(std::string("Invalid JSON value at pos ") + std::to_string(pos));
+                }
+            } parser(s);
+            Value result = parser.parseValue();
+            return result;
+        });
+    }
+};
+
+}  // namespace Modules
+#endif  // MODULES_JSONMODULE_HPP
diff --git a/src/Modules/BuiltIn/PrintModule.hpp b/src/Modules/BuiltIn/PrintModule.hpp
new file mode 100644
index 0000000..e27564a
--- /dev/null
+++ b/src/Modules/BuiltIn/PrintModule.hpp
@@ -0,0 +1,44 @@
+// PrintModule.hpp
+#ifndef MODULES_PRINTMODULE_HPP
+#define MODULES_PRINTMODULE_HPP
+
+#include <iostream>
+
+#include "Modules/BaseModule.hpp"
+#include "Modules/ModuleManager.hpp"
+#include "Symbols/Value.hpp"
+
+namespace Modules {
+
+/**
+ * @brief Module that provides a built-in print function.
+ */
+class PrintModule : public BaseModule {
+  public:
+    void registerModule() override {
+        auto & mgr = ModuleManager::instance();
+        mgr.registerFunction("print", [](const std::vector<Symbols::Value> & args) {
+            for (const auto & v : args) {
+                std::cout << Symbols::Value::to_string(v);
+            }
+            return Symbols::Value();
+        });
+        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();
+        });
+        mgr.registerFunction("error", [](const std::vector<Symbols::Value> & args) {
+            for (const auto & v : args) {
+                std::cerr << Symbols::Value::to_string(v);
+            }
+            std::cerr << "\n";
+            return Symbols::Value();
+        });
+    }
+};
+
+}  // namespace Modules
+#endif  // MODULES_PRINTMODULE_HPP
diff --git a/src/Modules/BuiltIn/StringModule.hpp b/src/Modules/BuiltIn/StringModule.hpp
new file mode 100644
index 0000000..d6d85dd
--- /dev/null
+++ b/src/Modules/BuiltIn/StringModule.hpp
@@ -0,0 +1,101 @@
+// StringModule.hpp
+#ifndef MODULES_STRINGMODULE_HPP
+#define MODULES_STRINGMODULE_HPP
+
+#include <string>
+#include <vector>
+#include <stdexcept>
+#include "Modules/BaseModule.hpp"
+#include "Modules/ModuleManager.hpp"
+#include "Symbols/Value.hpp"
+#include "Symbols/VariableTypes.hpp"
+
+namespace Modules {
+
+/**
+ * @brief Module providing string helper functions.
+ * Functions:
+ *   string_length(string $in) -> length of the string
+ *   string_replace(string $in, string $from, string $to, bool $replace_all)
+ *   string_substr(string $in, int from, int length) -> substring
+ */
+class StringModule : public BaseModule {
+ public:
+    void registerModule() override {
+        auto &mgr = ModuleManager::instance();
+        // string_length
+        mgr.registerFunction("string_length", [](const std::vector<Symbols::Value> &args) {
+            using namespace Symbols;
+            if (args.size() != 1) {
+                throw std::runtime_error("string_length expects exactly one argument");
+            }
+            if (args[0].getType() != Variables::Type::STRING) {
+                throw std::runtime_error("string_length expects a string argument");
+            }
+            const std::string &s = args[0].get<std::string>();
+            return Value(static_cast<int>(s.size()));
+        });
+        // string_replace
+        mgr.registerFunction("string_replace", [](const std::vector<Symbols::Value> &args) {
+            using namespace Symbols;
+            if (args.size() != 4) {
+                throw std::runtime_error("string_replace expects 4 arguments");
+            }
+            if (args[0].getType() != Variables::Type::STRING ||
+                args[1].getType() != Variables::Type::STRING ||
+                args[2].getType() != Variables::Type::STRING ||
+                args[3].getType() != Variables::Type::BOOLEAN) {
+                throw std::runtime_error("string_replace argument types must be (string, string, string, boolean)");
+            }
+            std::string in = args[0].get<std::string>();
+            const std::string &from = args[1].get<std::string>();
+            const std::string &to = args[2].get<std::string>();
+            bool replace_all = args[3].get<bool>();
+            if (from.empty()) {
+                throw std::runtime_error("string_replace: 'from' cannot be empty");
+            }
+            size_t pos = 0;
+            if (replace_all) {
+                while ((pos = in.find(from, pos)) != std::string::npos) {
+                    in.replace(pos, from.length(), to);
+                    pos += to.length();
+                }
+            } else {
+                pos = in.find(from);
+                if (pos != std::string::npos) {
+                    in.replace(pos, from.length(), to);
+                }
+            }
+            return Value(in);
+        });
+        // string_substr
+        mgr.registerFunction("string_substr", [](const std::vector<Symbols::Value> &args) {
+            using namespace Symbols;
+            if (args.size() != 3) {
+                throw std::runtime_error("string_substr expects 3 arguments");
+            }
+            if (args[0].getType() != Variables::Type::STRING ||
+                args[1].getType() != Variables::Type::INTEGER ||
+                args[2].getType() != Variables::Type::INTEGER) {
+                throw std::runtime_error("string_substr argument types must be (string, int, int)");
+            }
+            const std::string &s = args[0].get<std::string>();
+            int from = args[1].get<int>();
+            int length = args[2].get<int>();
+            if (from < 0 || length < 0) {
+                throw std::runtime_error("string_substr: 'from' and 'length' must be non-negative");
+            }
+            size_t pos = static_cast<size_t>(from);
+            if (pos > s.size()) {
+                throw std::runtime_error("string_substr: 'from' out of range");
+            }
+            size_t len = static_cast<size_t>(length);
+            std::string sub = s.substr(pos, len);
+            return Value(sub);
+        });
+    }
+};
+
+} // namespace Modules
+
+#endif // MODULES_STRINGMODULE_HPP
\ No newline at end of file
diff --git a/src/Modules/BuiltIn/VariableHelpersModule.hpp b/src/Modules/BuiltIn/VariableHelpersModule.hpp
new file mode 100644
index 0000000..72ed8fe
--- /dev/null
+++ b/src/Modules/BuiltIn/VariableHelpersModule.hpp
@@ -0,0 +1,48 @@
+// VariableHelpersModule.hpp
+#ifndef MODULES_VARIABLEHELPERSMODULE_HPP
+#define MODULES_VARIABLEHELPERSMODULE_HPP
+
+#include <stdexcept>
+#include <string>
+#include <vector>
+
+#include "Modules/BaseModule.hpp"
+#include "Modules/ModuleManager.hpp"
+#include "Symbols/Value.hpp"
+#include "Symbols/VariableTypes.hpp"
+
+namespace Modules {
+
+/**
+ * @brief Module providing helper functions for variables.
+ * Currently supports:
+ *   typeof($var)            -> returns string name of type
+ *   typeof($var, "int")   -> returns bool if type matches
+ */
+class VariableHelpersModule : public BaseModule {
+  public:
+    void registerModule() override {
+        auto & mgr = ModuleManager::instance();
+        mgr.registerFunction("typeof", [](const std::vector<Symbols::Value> & args) {
+            using namespace Symbols;
+            if (args.size() == 1) {
+                auto t = args[0].getType();
+                return Value(Variables::TypeToString(t));
+            }
+            if (args.size() == 2) {
+                auto        t    = args[0].getType();
+                std::string name = Variables::TypeToString(t);
+                if (args[1].getType() != Variables::Type::STRING) {
+                    throw std::runtime_error("Second argument to typeof must be string");
+                }
+                bool match = (name == args[1].get<std::string>());
+                return Value(match);
+            }
+            throw std::runtime_error("typeof expects 1 or 2 arguments");
+        });
+    }
+};
+
+}  // namespace Modules
+
+#endif  // MODULES_VARIABLEHELPERSMODULE_HPP
diff --git a/src/Modules/JsonModule.hpp b/src/Modules/JsonModule.hpp
deleted file mode 100644
index 604b92e..0000000
--- a/src/Modules/JsonModule.hpp
+++ /dev/null
@@ -1,239 +0,0 @@
-// JsonModule.hpp
-#ifndef MODULES_JSONMODULE_HPP
-#define MODULES_JSONMODULE_HPP
-
-#include <string>
-#include <map>
-#include <variant>
-#include <cctype>
-#include <stdexcept>
-#include <sstream>
-#include "BaseModule.hpp"
-#include "ModuleManager.hpp"
-#include "Symbols/Value.hpp"
-#include "Symbols/VariableTypes.hpp"
-
-namespace Modules {
-
-/**
- * @brief Module providing JSON encode/decode functions.
- *   json_encode(value) -> string
- *   json_decode(string) -> object/value
- */
-class JsonModule : public BaseModule {
-  public:
-    void registerModule() override {
-        auto &mgr = ModuleManager::instance();
-        // json_encode: serialize a Value to JSON string
-        mgr.registerFunction("json_encode", [](const std::vector<Symbols::Value> &args) {
-            using namespace Symbols;
-            if (args.size() != 1) {
-                throw std::runtime_error("json_encode expects 1 argument");
-            }
-            // forward to encoder
-            std::function<std::string(const Value &)> encode;
-            encode = [&](const Value &v) -> std::string {
-                const auto &var = v.get();
-                return std::visit(
-                    [&](auto &&x) -> std::string {
-                        using T = std::decay_t<decltype(x)>;
-                        if constexpr (std::is_same_v<T, bool>) {
-                            return x ? "true" : "false";
-                        } else if constexpr (std::is_same_v<T, int> || std::is_same_v<T, double> || std::is_same_v<T, float>) {
-                            return std::to_string(x);
-                        } else if constexpr (std::is_same_v<T, std::string>) {
-                            // escape string
-                            std::string out = "\"";
-                            for (char c : x) {
-                                switch (c) {
-                                    case '"': out += "\\\""; break;
-                                    case '\\': out += "\\\\"; break;
-                                    case '\b': out += "\\b"; break;
-                                    case '\f': out += "\\f"; break;
-                                    case '\n': out += "\\n"; break;
-                                    case '\r': out += "\\r"; break;
-                                    case '\t': out += "\\t"; break;
-                                    default:
-                                        if (static_cast<unsigned char>(c) < 0x20) {
-                                            // control character
-                                            char buf[7];
-                                            std::snprintf(buf, sizeof(buf), "\\u%04x", c);
-                                            out += buf;
-                                        } else {
-                                            out += c;
-                                        }
-                                }
-                            }
-                            out += "\"";
-                            return out;
-                        } else if constexpr (std::is_same_v<T, Value::ObjectMap>) {
-                            std::string out = "{";
-                            bool first = true;
-                            for (const auto &kv : x) {
-                                if (!first) out += ",";
-                                first = false;
-                                // key
-                                out += '"';
-                                // escape key string
-                                for (char c : kv.first) {
-                                    switch (c) {
-                                        case '"': out += "\\\""; break;
-                                        case '\\': out += "\\\\"; break;
-                                        case '\b': out += "\\b"; break;
-                                        case '\f': out += "\\f"; break;
-                                        case '\n': out += "\\n"; break;
-                                        case '\r': out += "\\r"; break;
-                                        case '\t': out += "\\t"; break;
-                                        default:
-                                            if (static_cast<unsigned char>(c) < 0x20) {
-                                                char buf[7];
-                                                std::snprintf(buf, sizeof(buf), "\\u%04x", c);
-                                                out += buf;
-                                            } else {
-                                                out += c;
-                                            }
-                                    }
-                                }
-                                out += '"';
-                                out += ':';
-                                out += encode(kv.second);
-                            }
-                            out += "}";
-                            return out;
-                        } else {
-                            return "null";
-                        }
-                    },
-                    var);
-            };
-            std::string result = encode(args[0]);
-            return Symbols::Value(result);
-        });
-        // json_decode: parse JSON string to Value (object/value)
-        mgr.registerFunction("json_decode", [](const std::vector<Symbols::Value> &args) {
-            using namespace Symbols;
-            if (args.size() != 1) {
-                throw std::runtime_error("json_decode expects 1 argument");
-            }
-            if (args[0].getType() != Variables::Type::STRING) {
-                throw std::runtime_error("json_decode expects a JSON string");
-            }
-            const std::string s = args[0].get<std::string>();
-            struct Parser {
-                const std::string &s;
-                size_t pos = 0;
-                Parser(const std::string &str) : s(str), pos(0) {}
-                void skip() {
-                    while (pos < s.size() && std::isspace(static_cast<unsigned char>(s[pos]))) pos++;
-                }
-                std::string parseString() {
-                    skip();
-                    if (s[pos] != '"') throw std::runtime_error("Invalid JSON string");
-                    pos++;
-                    std::string out;
-                    while (pos < s.size()) {
-                        char c = s[pos++];
-                        if (c == '"') break;
-                        if (c == '\\') {
-                            if (pos >= s.size()) break;
-                            char e = s[pos++];
-                            switch (e) {
-                                case '"': out += '"'; break;
-                                case '\\': out += '\\'; break;
-                                case '/': out += '/'; break;
-                                case 'b': out += '\b'; break;
-                                case 'f': out += '\f'; break;
-                                case 'n': out += '\n'; break;
-                                case 'r': out += '\r'; break;
-                                case 't': out += '\t'; break;
-                                default: out += e; break;
-                            }
-                        } else {
-                            out += c;
-                        }
-                    }
-                    return out;
-                }
-                Value parseNumber() {
-                    skip();
-                    size_t start = pos;
-                    if (s[pos] == '-') pos++;
-                    while (pos < s.size() && std::isdigit(static_cast<unsigned char>(s[pos]))) pos++;
-                    bool isDouble = false;
-                    if (pos < s.size() && s[pos] == '.') {
-                        isDouble = true;
-                        pos++;
-                        while (pos < s.size() && std::isdigit(static_cast<unsigned char>(s[pos]))) pos++;
-                    }
-                    std::string num = s.substr(start, pos - start);
-                    try {
-                        if (isDouble) {
-                            return Value(std::stod(num));
-                        }
-                        return Value(std::stoi(num));
-                    } catch (...) {
-                        throw std::runtime_error("Invalid JSON number: " + num);
-                    }
-                }
-                Value parseBool() {
-                    skip();
-                    if (s.compare(pos, 4, "true") == 0) {
-                        pos += 4;
-                        return Value(true);
-                    } else if (s.compare(pos, 5, "false") == 0) {
-                        pos += 5;
-                        return Value(false);
-                    }
-                    throw std::runtime_error("Invalid JSON boolean");
-                }
-                Value parseNull() {
-                    skip();
-                    if (s.compare(pos, 4, "null") == 0) {
-                        pos += 4;
-                        return Value::makeNull();
-                    }
-                    throw std::runtime_error("Invalid JSON null");
-                }
-                Value parseObject() {
-                    skip();
-                    if (s[pos] != '{') throw std::runtime_error("Invalid JSON object");
-                    pos++;
-                    skip();
-                    Value::ObjectMap obj;
-                    if (s[pos] == '}') { pos++; return Value(obj); }
-                    while (pos < s.size()) {
-                        skip();
-                        std::string key = parseString();
-                        skip();
-                        if (s[pos] != ':') throw std::runtime_error("Expected ':' in object");
-                        pos++;
-                        skip();
-                        Value val = parseValue();
-                        obj.emplace(key, val);
-                        skip();
-                        if (s[pos] == ',') { pos++; continue; }
-                        if (s[pos] == '}') { pos++; break; }
-                        throw std::runtime_error("Expected ',' or '}' in object");
-                    }
-                    return Value(obj);
-                }
-                Value parseValue() {
-                    skip();
-                    if (pos >= s.size()) throw std::runtime_error("Empty JSON");
-                    char c = s[pos];
-                    if (c == '{') return parseObject();
-                    if (c == '"') { std::string str = parseString(); return Value(str); }
-                    if (c == 't' || c == 'f') return parseBool();
-                    if (c == 'n') return parseNull();
-                    if (c == '-' || std::isdigit(static_cast<unsigned char>(c))) return parseNumber();
-                    throw std::runtime_error(std::string("Invalid JSON value at pos ") + std::to_string(pos));
-                }
-            } parser(s);
-            Value result = parser.parseValue();
-            return result;
-        });
-    }
-};
-
-} // namespace Modules
-#endif // MODULES_JSONMODULE_HPP
\ No newline at end of file
diff --git a/src/Modules/PrintModule.hpp b/src/Modules/PrintModule.hpp
deleted file mode 100644
index bc26b95..0000000
--- a/src/Modules/PrintModule.hpp
+++ /dev/null
@@ -1,29 +0,0 @@
-// PrintModule.hpp
-#ifndef MODULES_PRINTMODULE_HPP
-#define MODULES_PRINTMODULE_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 PrintModule : public BaseModule {
-  public:
-    void registerModule() override {
-        auto &mgr = ModuleManager::instance();
-        mgr.registerFunction("print", [](const std::vector<Symbols::Value> &args) {
-            for (const auto &v : args) {
-                std::cout << Symbols::Value::to_string(v);
-            }
-            return Symbols::Value();
-        });
-    }
-};
-
-} // namespace Modules
-#endif // MODULES_PRINTMODULE_HPP
\ No newline at end of file
diff --git a/src/Modules/PrintNlModule.hpp b/src/Modules/PrintNlModule.hpp
deleted file mode 100644
index 509006e..0000000
--- a/src/Modules/PrintNlModule.hpp
+++ /dev/null
@@ -1,31 +0,0 @@
-// 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/Modules/TypeofModule.hpp b/src/Modules/TypeofModule.hpp
deleted file mode 100644
index f38cd54..0000000
--- a/src/Modules/TypeofModule.hpp
+++ /dev/null
@@ -1,45 +0,0 @@
-// TypeofModule.hpp
-#ifndef MODULES_TYPEOFMODULE_HPP
-#define MODULES_TYPEOFMODULE_HPP
-
-#include <string>
-#include <vector>
-#include "BaseModule.hpp"
-#include "ModuleManager.hpp"
-#include "Symbols/Value.hpp"
-#include "Symbols/VariableTypes.hpp"
-
-namespace Modules {
-
-/**
- * @brief Module providing a typeof() function.
- * Usage:
- *   typeof($var)            -> returns string name of type ("int", "string", etc.)
- *   typeof($var, "int")   -> returns bool indicating if type matches
- */
-class TypeofModule : public BaseModule {
-  public:
-    void registerModule() override {
-        auto &mgr = ModuleManager::instance();
-        mgr.registerFunction("typeof", [](const std::vector<Symbols::Value> &args) {
-            using namespace Symbols;
-            if (args.size() == 1) {
-                auto t = args[0].getType();
-                return Value(Variables::TypeToString(t));
-            } else if (args.size() == 2) {
-                auto t = args[0].getType();
-                std::string name = Variables::TypeToString(t);
-                if (args[1].getType() != Variables::Type::STRING) {
-                    throw std::runtime_error("Second argument to typeof must be string");
-                }
-                bool match = (name == args[1].get<std::string>());
-                return Value(match);
-            }
-            throw std::runtime_error("typeof expects 1 or 2 arguments");
-        });
-    }
-};
-
-} // namespace Modules
-
-#endif // MODULES_TYPEOFMODULE_HPP
\ No newline at end of file
diff --git a/src/Parser/Parser.cpp b/src/Parser/Parser.cpp
index baff197..1ec0322 100644
--- a/src/Parser/Parser.cpp
+++ b/src/Parser/Parser.cpp
@@ -55,8 +55,15 @@
 void Parser::parseVariableDefinition() {
     Symbols::Variables::Type var_type = parseType();
 
-    Lexer::Tokens::Token id_token = expect(Lexer::Tokens::Type::VARIABLE_IDENTIFIER);
-    std::string          var_name = id_token.value;
+    // Variable name: allow names with or without leading '$'
+    Lexer::Tokens::Token id_token;
+    if (currentToken().type == Lexer::Tokens::Type::VARIABLE_IDENTIFIER ||
+        currentToken().type == Lexer::Tokens::Type::IDENTIFIER) {
+        id_token = consumeToken();
+    } else {
+        reportError("Expected variable name", currentToken());
+    }
+    std::string var_name = id_token.value;
 
     if (!var_name.empty() && var_name[0] == '$') {
         var_name = var_name.substr(1);
@@ -154,23 +161,39 @@
 std::unique_ptr<Interpreter::StatementNode> Parser::parseForStatementNode() {
     auto forToken = expect(Lexer::Tokens::Type::KEYWORD, "for");
     expect(Lexer::Tokens::Type::PUNCTUATION, "(");
-    Symbols::Variables::Type keyType = parseType();
-    auto                     keyTok  = expect(Lexer::Tokens::Type::VARIABLE_IDENTIFIER);
-    std::string              keyName = keyTok.value;
-    if (!keyName.empty() && keyName[0] == '$') {
-        keyName = keyName.substr(1);
+    // Parse element type and variable name
+    Symbols::Variables::Type elemType = parseType();
+    auto firstTok = expect(Lexer::Tokens::Type::VARIABLE_IDENTIFIER);
+    std::string firstName = firstTok.value;
+    if (!firstName.empty() && firstName[0] == '$') {
+        firstName = firstName.substr(1);
     }
-    expect(Lexer::Tokens::Type::PUNCTUATION, ",");
-    if (!(currentToken().type == Lexer::Tokens::Type::IDENTIFIER && currentToken().value == "auto")) {
-        reportError("Expected 'auto' in for-in loop");
+    // Determine loop form: key,value or simple element loop
+    std::string keyName, valName;
+    Symbols::Variables::Type keyType;
+    if (match(Lexer::Tokens::Type::PUNCTUATION, ",")) {
+        // Key, value syntax
+        keyType = elemType;
+        keyName = firstName;
+        // Expect 'auto' for value variable
+        if (!(currentToken().type == Lexer::Tokens::Type::IDENTIFIER && currentToken().value == "auto")) {
+            reportError("Expected 'auto' in for-in loop");
+        }
+        consumeToken();
+        auto valTok = expect(Lexer::Tokens::Type::VARIABLE_IDENTIFIER);
+        valName = valTok.value;
+        if (!valName.empty() && valName[0] == '$') {
+            valName = valName.substr(1);
+        }
+        expect(Lexer::Tokens::Type::PUNCTUATION, ":");
+    } else if (match(Lexer::Tokens::Type::PUNCTUATION, ":")) {
+        // Simple element loop
+        keyType = elemType;
+        keyName = firstName;
+        valName = firstName;
+    } else {
+        reportError("Expected ',' or ':' in for-in loop");
     }
-    consumeToken();
-    auto        valTok  = expect(Lexer::Tokens::Type::VARIABLE_IDENTIFIER);
-    std::string valName = valTok.value;
-    if (!valName.empty() && valName[0] == '$') {
-        valName = valName.substr(1);
-    }
-    expect(Lexer::Tokens::Type::PUNCTUATION, ":");
     auto iterableExpr = parseParsedExpression(Symbols::Variables::Type::NULL_TYPE);
     expect(Lexer::Tokens::Type::PUNCTUATION, ")");
     expect(Lexer::Tokens::Type::PUNCTUATION, "{");
@@ -401,25 +424,39 @@
     // 'for'
     auto forToken = expect(Lexer::Tokens::Type::KEYWORD, "for");
     expect(Lexer::Tokens::Type::PUNCTUATION, "(");
-    // Parse key type and name
-    Symbols::Variables::Type keyType = parseType();
-    auto                     keyTok  = expect(Lexer::Tokens::Type::VARIABLE_IDENTIFIER);
-    std::string              keyName = keyTok.value;
-    if (!keyName.empty() && keyName[0] == '$') {
-        keyName = keyName.substr(1);
+    // Parse element type and variable name
+    Symbols::Variables::Type elemType = parseType();
+    auto firstTok = expect(Lexer::Tokens::Type::VARIABLE_IDENTIFIER);
+    std::string firstName = firstTok.value;
+    if (!firstName.empty() && firstName[0] == '$') {
+        firstName = firstName.substr(1);
     }
-    expect(Lexer::Tokens::Type::PUNCTUATION, ",");
-    // Parse 'auto' keyword for value
-    if (!(currentToken().type == Lexer::Tokens::Type::IDENTIFIER && currentToken().value == "auto")) {
-        reportError("Expected 'auto' in for-in loop");
+    // Determine loop form: key,value or simple element loop
+    std::string keyName, valName;
+    Symbols::Variables::Type keyType;
+    if (match(Lexer::Tokens::Type::PUNCTUATION, ",")) {
+        // Key, value syntax
+        keyType = elemType;
+        keyName = firstName;
+        // Expect 'auto' for value variable
+        if (!(currentToken().type == Lexer::Tokens::Type::IDENTIFIER && currentToken().value == "auto")) {
+            reportError("Expected 'auto' in for-in loop");
+        }
+        consumeToken();
+        auto valTok = expect(Lexer::Tokens::Type::VARIABLE_IDENTIFIER);
+        valName = valTok.value;
+        if (!valName.empty() && valName[0] == '$') {
+            valName = valName.substr(1);
+        }
+        expect(Lexer::Tokens::Type::PUNCTUATION, ":");
+    } else if (match(Lexer::Tokens::Type::PUNCTUATION, ":")) {
+        // Simple element loop
+        keyType = elemType;
+        keyName = firstName;
+        valName = firstName;
+    } else {
+        reportError("Expected ',' or ':' in for-in loop");
     }
-    consumeToken();
-    auto        valTok  = expect(Lexer::Tokens::Type::VARIABLE_IDENTIFIER);
-    std::string valName = valTok.value;
-    if (!valName.empty() && valName[0] == '$') {
-        valName = valName.substr(1);
-    }
-    expect(Lexer::Tokens::Type::PUNCTUATION, ":");
     // Parse iterable expression
     auto iterableExpr = parseParsedExpression(Symbols::Variables::Type::NULL_TYPE);
     expect(Lexer::Tokens::Type::PUNCTUATION, ")");
@@ -535,9 +572,52 @@
     }
 
     bool expect_unary = true;
+    // Track if at start of expression (to distinguish array literal vs indexing)
+    bool atStart = true;
 
     while (true) {
         auto token = currentToken();
+        // Array literal (at start) or dynamic indexing (postfix)
+        if (token.type == Lexer::Tokens::Type::PUNCTUATION && token.value == "[") {
+            if (atStart) {
+                // Parse array literal as object with numeric keys
+                consumeToken(); // consume '['
+                std::vector<std::pair<std::string, ParsedExpressionPtr>> members;
+                size_t idx = 0;
+                // Elements until ']'
+                if (!(currentToken().type == Lexer::Tokens::Type::PUNCTUATION && currentToken().value == "]")) {
+                    while (true) {
+                        auto elem = parseParsedExpression(Symbols::Variables::Type::NULL_TYPE);
+                        members.emplace_back(std::to_string(idx++), std::move(elem));
+                        if (match(Lexer::Tokens::Type::PUNCTUATION, ",")) {
+                            continue;
+                        }
+                        break;
+                    }
+                }
+                expect(Lexer::Tokens::Type::PUNCTUATION, "]");
+                // Build as object literal
+                output_queue.push_back(ParsedExpression::makeObject(std::move(members)));
+                expect_unary = false;
+                atStart = false;
+                continue;
+            } else {
+                // Parse dynamic array/object indexing: lhs[index]
+                consumeToken(); // consume '['
+                auto indexExpr = parseParsedExpression(Symbols::Variables::Type::NULL_TYPE);
+                expect(Lexer::Tokens::Type::PUNCTUATION, "]");
+                if (output_queue.empty()) {
+                    reportError("Missing array/object for indexing");
+                }
+                auto lhsExpr = std::move(output_queue.back());
+                output_queue.pop_back();
+                auto accessExpr = ParsedExpression::makeBinary("[]", std::move(lhsExpr), std::move(indexExpr));
+                output_queue.push_back(std::move(accessExpr));
+                expect_unary = false;
+                atStart = false;
+                continue;
+            }
+        }
         // Object literal: { key: value, ... }
         if (token.type == Lexer::Tokens::Type::PUNCTUATION && token.value == "{") {
             // Consume '{'
@@ -737,8 +817,10 @@
                     Parser::reportError("Invalid type", token, "literal or variable");
                 }
             }
+            // Consume operand and mark that expression start has passed
             consumeToken();
             expect_unary = false;
+            atStart = false;
         } else {
             break;
         }
@@ -853,7 +935,12 @@
     // Direct lookup for type keyword
     auto         it    = Parser::variable_types.find(token.type);
     if (it != Parser::variable_types.end()) {
+        // Base type
         consumeToken();
+        // Array type syntax: baseType[] -> treat as OBJECT/array map
+        if (match(Lexer::Tokens::Type::PUNCTUATION, "[") && match(Lexer::Tokens::Type::PUNCTUATION, "]")) {
+            return Symbols::Variables::Type::OBJECT;
+        }
         return it->second;
     }
     reportError("Expected type keyword (string, int, double, float)");
diff --git a/src/VoidScript.hpp b/src/VoidScript.hpp
index 077cd50..ab472bb 100644
--- a/src/VoidScript.hpp
+++ b/src/VoidScript.hpp
@@ -6,12 +6,18 @@
 
 #include "Interpreter/Interpreter.hpp"
 #include "Lexer/Lexer.hpp"
+#include "Modules/BuiltIn/PrintModule.hpp"
 #include "Modules/ModuleManager.hpp"
-#include "Modules/PrintNlModule.hpp"
-#include "Modules/PrintModule.hpp"
-#include "Modules/TypeofModule.hpp"
-#include "Modules/FileModule.hpp"
-#include "Modules/JsonModule.hpp"
+// Variable helper functions (typeof)
+#include "Modules/BuiltIn/VariableHelpersModule.hpp"
+// String helper functions
+#include "Modules/BuiltIn/StringModule.hpp"
+// Array helper functions (sizeof)
+#include "Modules/BuiltIn/ArrayModule.hpp"
+// File I/O
+#include "Modules/BuiltIn/FileModule.hpp"
+// JSON encode/decode
+#include "Modules/BuiltIn/JsonModule.hpp"
 #include "Parser/Parser.hpp"
 
 class VoidScript {
@@ -55,10 +61,14 @@
         lexer(std::make_shared<Lexer::Lexer>()),
         parser(std::make_shared<Parser::Parser>()) {
         // Register built-in modules (print, etc.)
+        // print functions
         Modules::ModuleManager::instance().addModule(std::make_unique<Modules::PrintModule>());
-        Modules::ModuleManager::instance().addModule(std::make_unique<Modules::PrintNlModule>());
-        // typeof() builtin
-        Modules::ModuleManager::instance().addModule(std::make_unique<Modules::TypeofModule>());
+        // variable helpers (typeof)
+        Modules::ModuleManager::instance().addModule(std::make_unique<Modules::VariableHelpersModule>());
+        // string helper functions
+        Modules::ModuleManager::instance().addModule(std::make_unique<Modules::StringModule>());
+        // array helper functions (sizeof)
+        Modules::ModuleManager::instance().addModule(std::make_unique<Modules::ArrayModule>());
         // file I/O builtin
         Modules::ModuleManager::instance().addModule(std::make_unique<Modules::FileModule>());
         // JSON encode/decode builtin
diff --git a/test_scripts/array.vs b/test_scripts/array.vs
index bf7235d..10d1b27 100644
--- a/test_scripts/array.vs
+++ b/test_scripts/array.vs
@@ -1,8 +1,20 @@
-array[string] $array = ["cat", "dog", "girafe"];
+string[] $array = ["cat", "dog", "girafe"];
 
-array[int] $intArray = [10,11,0,1,2,44,2];
+int[] $intArray = [10,11,0,1,2,44,2];
 
-array[int] $emptyIntArray = [];
+int[] $emptyIntArray = [];
 
 
 
+printnl("First key: ", $array[0]);
+
+int index = 0;
+for (string $value : $array) {
+    printnl("$value =",$value, " ?= $array[$index] =", $array[$index]);
+    $index = $index + 1;
+}
+
+
+int $size = sizeof($array);
+
+printnl("The size of the $array: ", $size);
\ No newline at end of file
diff --git a/test_scripts/test_array.vs b/test_scripts/test_array.vs
new file mode 100644
index 0000000..24dada5
--- /dev/null
+++ b/test_scripts/test_array.vs
@@ -0,0 +1,2 @@
+int[] $arr = [0,1,2,3];
+printnl(sizeof($arr));
diff --git a/test_scripts/test_string.vs b/test_scripts/test_string.vs
new file mode 100644
index 0000000..47710b7
--- /dev/null
+++ b/test_scripts/test_string.vs
@@ -0,0 +1,3 @@
+string $s = "hello";
+printnl(sizeof($s));
+printnl(sizeof("abc"));
diff --git a/test_scripts/test_substr.vs b/test_scripts/test_substr.vs
new file mode 100644
index 0000000..4e5673f
--- /dev/null
+++ b/test_scripts/test_substr.vs
@@ -0,0 +1,4 @@
+string $s = "hello_world";
+printnl(string_substr($s, 6, 5));
+printnl(string_substr($s, 0, 5));
+printnl(string_substr($s, 0, 50));

--
Gitblit v1.9.3