From 6cc2945c1d1e6ca7bad0542c79de423df5e2db8b Mon Sep 17 00:00:00 2001
From: Ferenc Szontágh <szf@fsociety.hu>
Date: Fri, 18 Apr 2025 18:13:29 +0000
Subject: [PATCH] implements object type

---
 src/Parser/Parser.cpp                    |   48 ++++++++++++++++
 src/Symbols/VariableTypes.hpp            |    6 +
 src/Interpreter/ExpressionBuilder.hpp    |   10 +++
 src/Parser/ParsedExpression.hpp          |   12 +++
 src/Interpreter/ObjectExpressionNode.hpp |   35 +++++++++++
 src/Lexer/Operators.cpp                  |    2 
 src/Parser/Parser.hpp                    |    2 
 test_scripts/object.vs                   |   36 +++++++++++
 src/Lexer/TokenType.hpp                  |    3 +
 src/Symbols/Value.hpp                    |   10 +++
 10 files changed, 157 insertions(+), 7 deletions(-)

diff --git a/src/Interpreter/ExpressionBuilder.hpp b/src/Interpreter/ExpressionBuilder.hpp
index 602f603..f5f4323 100644
--- a/src/Interpreter/ExpressionBuilder.hpp
+++ b/src/Interpreter/ExpressionBuilder.hpp
@@ -10,6 +10,7 @@
 #include "Interpreter/LiteralExpressionNode.hpp"
 #include "Interpreter/UnaryExpressionNode.hpp"  // <-- új include
 #include "Interpreter/CallExpressionNode.hpp"
+#include "Interpreter/ObjectExpressionNode.hpp"
 #include "Parser/ParsedExpression.hpp"
 
 namespace Parser {
@@ -46,6 +47,15 @@
                 }
                 return std::make_unique<Interpreter::CallExpressionNode>(expr->name, std::move(callArgs));
             }
+        case Kind::Object:
+            {
+                std::vector<std::pair<std::string, std::unique_ptr<Interpreter::ExpressionNode>>> members;
+                members.reserve(expr->objectMembers.size());
+                for (const auto &p : expr->objectMembers) {
+                    members.emplace_back(p.first, buildExpressionFromParsed(p.second));
+                }
+                return std::make_unique<Interpreter::ObjectExpressionNode>(std::move(members));
+            }
     }
 
     throw std::runtime_error("Unknown ParsedExpression kind");
diff --git a/src/Interpreter/ObjectExpressionNode.hpp b/src/Interpreter/ObjectExpressionNode.hpp
new file mode 100644
index 0000000..885b4d0
--- /dev/null
+++ b/src/Interpreter/ObjectExpressionNode.hpp
@@ -0,0 +1,35 @@
+#ifndef INTERPRETER_OBJECT_EXPRESSION_NODE_HPP
+#define INTERPRETER_OBJECT_EXPRESSION_NODE_HPP
+
+#include "ExpressionNode.hpp"
+#include "Symbols/Value.hpp"
+#include <vector>
+#include <string>
+#include <memory>
+
+namespace Interpreter {
+
+class ObjectExpressionNode : public ExpressionNode {
+  public:
+    using ObjectMap = Symbols::Value::ObjectMap;
+
+    explicit ObjectExpressionNode(std::vector<std::pair<std::string, std::unique_ptr<ExpressionNode>>> members)
+        : members_(std::move(members)) {}
+
+    Symbols::Value evaluate(Interpreter & interpreter) const override {
+        ObjectMap obj;
+        for (const auto &kv : members_) {
+            obj[kv.first] = kv.second->evaluate(interpreter);
+        }
+        return Symbols::Value(obj);
+    }
+
+    std::string toString() const override { return "[object]"; }
+
+  private:
+    std::vector<std::pair<std::string, std::unique_ptr<ExpressionNode>>> members_;
+};
+
+} // namespace Interpreter
+
+#endif // INTERPRETER_OBJECT_EXPRESSION_NODE_HPP
\ No newline at end of file
diff --git a/src/Lexer/Operators.cpp b/src/Lexer/Operators.cpp
index 5fd9f81..0a34433 100644
--- a/src/Lexer/Operators.cpp
+++ b/src/Lexer/Operators.cpp
@@ -8,7 +8,7 @@
 const std::vector<std::string> OPERATOR_LOGICAL    = { "&&", "||" };
 
 const std::vector<std::string> OPERATOR_ARITHMETIC = { "+", "-", "*", "/", "%", "!" };
-const std::vector<std::string> PUNCTUATION         = { "(", ")", "{", "}", "[", "]", ",", ";" };
+const std::vector<std::string> PUNCTUATION         = { "(", ")", "{", "}", "[", "]", ",", ";", ":" };
 
 bool contains(const std::vector<std::string> & vec, const std::string & value) {
     return std::find(vec.begin(), vec.end(), value) != vec.end();
diff --git a/src/Lexer/TokenType.hpp b/src/Lexer/TokenType.hpp
index e29ba04..386964b 100644
--- a/src/Lexer/TokenType.hpp
+++ b/src/Lexer/TokenType.hpp
@@ -25,6 +25,7 @@
     KEYWORD_DOUBLE,
     KEYWORD_FLOAT,
     KEYWORD_BOOLEAN,
+    KEYWORD_OBJECT,
     KEYWORD_FUNCTION_DECLARATION,
     KEYWORD_RETURN,
     KEYWORD_NULL,
@@ -69,6 +70,8 @@
             return "KEYWORD_FLOAT";
         case Lexer::Tokens::Type::KEYWORD_BOOLEAN:
             return "KEYWORD_BOOLEAN";
+        case Lexer::Tokens::Type::KEYWORD_OBJECT:
+            return "KEYWORD_OBJECT";
         case Lexer::Tokens::Type::KEYWORD_FUNCTION_DECLARATION:
             return "KEYWORD_FUNCTION";
         case Lexer::Tokens::Type::KEYWORD_RETURN:
diff --git a/src/Parser/ParsedExpression.hpp b/src/Parser/ParsedExpression.hpp
index 06a462b..4acff85 100644
--- a/src/Parser/ParsedExpression.hpp
+++ b/src/Parser/ParsedExpression.hpp
@@ -16,7 +16,7 @@
 using ParsedExpressionPtr = std::unique_ptr<ParsedExpression>;
 
 struct ParsedExpression {
-    enum class Kind : std::uint8_t { Literal, Variable, Binary, Unary, Call };
+    enum class Kind : std::uint8_t { Literal, Variable, Binary, Unary, Call, Object };
 
     Kind kind;
 
@@ -29,6 +29,7 @@
     ParsedExpressionPtr rhs;
     // For function call arguments
     std::vector<ParsedExpressionPtr> args;
+    std::vector<std::pair<std::string, ParsedExpressionPtr>> objectMembers;
 
     // Constructor for literal
     static ParsedExpressionPtr makeLiteral(const Symbols::Value & val) {
@@ -70,6 +71,13 @@
         expr->kind       = Kind::Call;
         expr->name       = name;
         expr->args       = std::move(arguments);
+        return expr;
+    }
+    // Constructor for object literal
+    static ParsedExpressionPtr makeObject(std::vector<std::pair<std::string, ParsedExpressionPtr>> members) {
+        auto expr = std::make_unique<ParsedExpression>();
+        expr->kind = Kind::Object;
+        expr->objectMembers = std::move(members);
         return expr;
     }
 
@@ -116,6 +124,8 @@
                     auto funcSym = std::static_pointer_cast<Symbols::FunctionSymbol>(symbol);
                     return funcSym->returnType();
                 }
+            case Kind::Object:
+                return Symbols::Variables::Type::OBJECT;
 
             default:
                 throw std::runtime_error("Unknown expression kind");
diff --git a/src/Parser/Parser.cpp b/src/Parser/Parser.cpp
index e1e9776..1739a20 100644
--- a/src/Parser/Parser.cpp
+++ b/src/Parser/Parser.cpp
@@ -6,6 +6,7 @@
 #include "Lexer/Operators.hpp"
 // Statements and expression building for conditional and block parsing
 #include "Interpreter/ConditionalStatementNode.hpp"
+// #include "Interpreter/ForStatementNode.hpp"  // removed until for-in loops are implemented
 #include "Interpreter/CallStatementNode.hpp"
 #include "Interpreter/DeclareVariableStatementNode.hpp"
 #include "Interpreter/ReturnStatementNode.hpp"
@@ -34,6 +35,7 @@
     { "string",   Lexer::Tokens::Type::KEYWORD_STRING               },
     { "boolean",  Lexer::Tokens::Type::KEYWORD_BOOLEAN              },
     { "bool",     Lexer::Tokens::Type::KEYWORD_BOOLEAN              },
+    { "object",   Lexer::Tokens::Type::KEYWORD_OBJECT               },
     // ... other keywords ...
 };
 
@@ -44,6 +46,7 @@
     { Lexer::Tokens::Type::KEYWORD_STRING,  Symbols::Variables::Type::STRING    },
     { Lexer::Tokens::Type::KEYWORD_NULL,    Symbols::Variables::Type::NULL_TYPE },
     { Lexer::Tokens::Type::KEYWORD_BOOLEAN, Symbols::Variables::Type::BOOLEAN   },
+    { Lexer::Tokens::Type::KEYWORD_OBJECT,  Symbols::Variables::Type::OBJECT    },
 };
 
 void Parser::parseVariableDefinition() {
@@ -357,6 +360,51 @@
 
     while (true) {
         auto token = currentToken();
+        // Object literal: { key: value, ... }
+        if (token.type == Lexer::Tokens::Type::PUNCTUATION && token.value == "{") {
+            // Consume '{'
+            consumeToken();
+            std::vector<std::pair<std::string, ParsedExpressionPtr>> members;
+            // Parse members until '}'
+            if (!(currentToken().type == Lexer::Tokens::Type::PUNCTUATION && currentToken().value == "}")) {
+                while (true) {
+                    // Optional type tag before key
+                    Symbols::Variables::Type memberType = Symbols::Variables::Type::UNDEFINED_TYPE;
+                    if (Parser::variable_types.find(currentToken().type) != Parser::variable_types.end()) {
+                        memberType = parseType();
+                    }
+                    // Key must be an identifier or variable identifier
+                    if (currentToken().type != Lexer::Tokens::Type::IDENTIFIER &&
+                        currentToken().type != Lexer::Tokens::Type::VARIABLE_IDENTIFIER) {
+                        reportError("Expected identifier for object key");
+                    }
+                    std::string key = currentToken().value;
+                    // Strip '$' if present
+                    if (!key.empty() && key[0] == '$') {
+                        key = key.substr(1);
+                    }
+                    consumeToken();
+                    // Expect ':' delimiter
+                    expect(Lexer::Tokens::Type::PUNCTUATION, ":");
+                    // Parse value expression (pass tag type if provided)
+                    Symbols::Variables::Type expectType = (memberType == Symbols::Variables::Type::UNDEFINED_TYPE)
+                                                            ? Symbols::Variables::Type::NULL_TYPE
+                                                            : memberType;
+                    auto valueExpr = parseParsedExpression(expectType);
+                    members.emplace_back(key, std::move(valueExpr));
+                    if (match(Lexer::Tokens::Type::PUNCTUATION, ",")) {
+                        continue;
+                    }
+                    break;
+                }
+            }
+            // Expect closing '}'
+            expect(Lexer::Tokens::Type::PUNCTUATION, "}");
+            // Create object literal parsed expression
+            output_queue.push_back(ParsedExpression::makeObject(std::move(members)));
+            expect_unary = false;
+            continue;
+        }
 
         if (token.type == Lexer::Tokens::Type::PUNCTUATION && token.lexeme == "(") {
             operator_stack.push("(");
diff --git a/src/Parser/Parser.hpp b/src/Parser/Parser.hpp
index d2c826c..56839fa 100644
--- a/src/Parser/Parser.hpp
+++ b/src/Parser/Parser.hpp
@@ -209,6 +209,8 @@
     void parseReturnStatement();
     // Parse an if-else conditional statement
     void parseIfStatement();
+    // Parse a for-in loop over object members
+    void parseForStatement();
     // Parse a statement node for use inside blocks (not added to operation container)
     std::unique_ptr<Interpreter::StatementNode> parseStatementNode();
 
diff --git a/src/Symbols/Value.hpp b/src/Symbols/Value.hpp
index f7040c0..4985d5b 100644
--- a/src/Symbols/Value.hpp
+++ b/src/Symbols/Value.hpp
@@ -4,6 +4,7 @@
 #include <algorithm>
 #include <stdexcept>
 #include <string>
+#include <map>
 #include <variant>
 
 #include "VariableTypes.hpp"
@@ -12,7 +13,8 @@
 
 class Value {
   public:
-    using Variant = std::variant<int, double, float, std::string, bool>;
+    using ObjectMap = std::map<std::string, Value>;
+    using Variant = std::variant<int, double, float, std::string, bool, ObjectMap>;
 
     Value() = default;
 
@@ -27,6 +29,10 @@
     Value(const char * v) : value_(std::string(v)) { type_ = Symbols::Variables::Type::STRING; }
 
     Value(bool v) : value_(v) { type_ = Symbols::Variables::Type::BOOLEAN; }
+    /**
+     * @brief Construct an object value from a map of member names to Values.
+     */
+    Value(const ObjectMap & v) : value_(v) { type_ = Symbols::Variables::Type::OBJECT; }
 
     Value(const std::string & str, bool autoDetectType) { *this = fromString(str, autoDetectType); }
 
@@ -70,6 +76,8 @@
                     return val ? "true" : "false";
                 } else if constexpr (std::is_same_v<T, std::string>) {
                     return val;
+                } else if constexpr (std::is_same_v<T, ObjectMap>) {
+                    return "[object]";
                 } else {
                     return std::to_string(val);
                 }
diff --git a/src/Symbols/VariableTypes.hpp b/src/Symbols/VariableTypes.hpp
index 99c4802..e389b57 100644
--- a/src/Symbols/VariableTypes.hpp
+++ b/src/Symbols/VariableTypes.hpp
@@ -7,7 +7,7 @@
 
 namespace Symbols::Variables {
 
-enum class Type : std::uint8_t { INTEGER, DOUBLE, FLOAT, STRING, BOOLEAN, NULL_TYPE, UNDEFINED_TYPE };
+enum class Type : std::uint8_t { INTEGER, DOUBLE, FLOAT, STRING, BOOLEAN, OBJECT, NULL_TYPE, UNDEFINED_TYPE };
 
 const std::unordered_map<std::string, Type> StringToTypeMap = {
     { "int",       Type::INTEGER        },
@@ -17,6 +17,7 @@
     { "bool",      Type::BOOLEAN        },
     { "boolean",   Type::BOOLEAN        },
     { "null",      Type::NULL_TYPE      },
+    { "object",    Type::OBJECT         },
 
     { "undefined", Type::UNDEFINED_TYPE },
 };
@@ -26,8 +27,9 @@
     { Type::FLOAT,          "float"      },
     { Type::STRING,         "string"     },
     { Type::BOOLEAN,        "bool"       },
+    { Type::OBJECT,         "object"     },
     { Type::NULL_TYPE,      "null"       },
-    { Type::UNDEFINED_TYPE, "undeffined" },
+    { Type::UNDEFINED_TYPE, "undefined"  },
 };
 
 inline static std::string TypeToString(Symbols::Variables::Type type) {
diff --git a/test_scripts/object.vs b/test_scripts/object.vs
index fa6d218..9b9ce5e 100644
--- a/test_scripts/object.vs
+++ b/test_scripts/object.vs
@@ -1,4 +1,36 @@
+
 object $person = {
     name: "Szoni",
-    age: 37
-};
\ No newline at end of file
+    double age: 37.6
+};
+
+
+object $person2 = {
+    string name: "Not Szoni",
+    int age: 37,
+    object $children: {
+        string name: "Child1",
+        int age: 10
+    }
+};
+
+object $children2 = {
+     string name: "Child2",
+        int age: 15
+};
+
+object $family = {
+           $children: $children, // this is valid too
+    object $children: $children2
+};
+
+object $family3 = {
+    string age: 10  // this is invalid, drops error
+};
+
+
+
+printnl("Hello, ", $person->name);              // prints out: Hello, Szoni
+println("Type: ", typeof($person);              // prints out: Type: object
+println("Type: ", typeof($person2->name));      // prints out: Type: string
+println("Type: ", typeof($person2->age));       // prints out: Type: double

--
Gitblit v1.9.3