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