From 3d9e8a26930930a4b63143f800bfa28e5d3caaf6 Mon Sep 17 00:00:00 2001
From: Ferenc Szontágh <szf@fsociety.hu>
Date: Fri, 18 Apr 2025 19:35:21 +0000
Subject: [PATCH] fix object variable assigment

---
 src/Parser/Parser.cpp                       |   51 ++++++++++++
 /dev/null                                   |    4 -
 src/Parser/Parser.hpp                       |   15 +++
 test_scripts/object.vs                      |    1 
 src/Interpreter/AssignmentStatementNode.hpp |  110 +++++++++++++++++++++++++++
 5 files changed, 177 insertions(+), 4 deletions(-)

diff --git a/src/Interpreter/AssignmentStatementNode.hpp b/src/Interpreter/AssignmentStatementNode.hpp
new file mode 100644
index 0000000..5f4a969
--- /dev/null
+++ b/src/Interpreter/AssignmentStatementNode.hpp
@@ -0,0 +1,110 @@
+#ifndef INTERPRETER_ASSIGNMENT_STATEMENT_NODE_HPP
+#define INTERPRETER_ASSIGNMENT_STATEMENT_NODE_HPP
+
+#include "StatementNode.hpp"
+#include "ExpressionNode.hpp"
+#include "Symbols/SymbolContainer.hpp"
+#include "Symbols/Value.hpp"
+
+namespace Interpreter {
+
+/**
+ * @brief Statement node for assignments: variable or nested object property.
+ *   e.g., $a = expr; or $obj->prop->sub = expr;
+ */
+class AssignmentStatementNode : public StatementNode {
+  private:
+    std::string targetName_;
+    std::vector<std::string> propertyPath_;
+    std::unique_ptr<ExpressionNode> rhs_;
+  public:
+    AssignmentStatementNode(std::string targetName,
+                            std::vector<std::string> propertyPath,
+                            std::unique_ptr<ExpressionNode> rhs,
+                            const std::string & file,
+                            int line,
+                            size_t column)
+        : StatementNode(file, line, column),
+          targetName_(std::move(targetName)),
+          propertyPath_(std::move(propertyPath)),
+          rhs_(std::move(rhs)) {}
+
+    void interpret(Interpreter & interpreter) const override {
+        using namespace Symbols;
+        auto * symContainer = SymbolContainer::instance();
+        // Variables are stored under <scope>.variables
+        const std::string base_ns = symContainer->currentScopeName();
+        const std::string var_ns = base_ns + ".variables";
+        if (!symContainer->exists(targetName_, var_ns)) {
+            throw std::runtime_error("Variable '" + targetName_ + "' does not exist in namespace: " + var_ns +
+                                     " File: " + filename_ + ", Line: " + std::to_string(line_) +
+                                     ", Column: " + std::to_string(column_));
+        }
+        auto symbol = symContainer->get(var_ns, targetName_);
+        // Copy current value for potential nested updates
+        Value varValue = symbol->getValue();
+        // Evaluate RHS
+        Value newValue = rhs_->evaluate(interpreter);
+        // Simple variable assignment
+        if (propertyPath_.empty()) {
+            // Type check
+            if (newValue.getType() != varValue.getType()) {
+                using namespace Variables;
+                throw std::runtime_error("Type mismatch assigning to variable '" + targetName_ +
+                                         "': expected '" + TypeToString(varValue.getType()) +
+                                         "' but got '" + TypeToString(newValue.getType()) +
+                                         "' File: " + filename_ + ", Line: " + std::to_string(line_) +
+                                         ", Column: " + std::to_string(column_));
+            }
+            symbol->setValue(newValue);
+            return;
+        }
+        // Nested object property assignment
+        if (varValue.getType() != Variables::Type::OBJECT) {
+            throw std::runtime_error("Attempting to assign property on non-object variable '" + targetName_ + "'");
+        }
+        // Traverse into nested maps
+        using ObjectMap = Value::ObjectMap;
+        ObjectMap * currMap = &std::get<ObjectMap>(varValue.get());
+        // Iterate through all but last key
+        for (size_t i = 0; i + 1 < propertyPath_.size(); ++i) {
+            const auto & key = propertyPath_[i];
+            auto it = currMap->find(key);
+            if (it == currMap->end()) {
+                throw std::runtime_error("Property '" + key + "' not found on object '" + targetName_ + "'");
+            }
+            Value & child = it->second;
+            if (child.getType() != Variables::Type::OBJECT) {
+                throw std::runtime_error("Property '" + key + "' is not an object, cannot assign nested property");
+            }
+            currMap = &std::get<ObjectMap>(child.get());
+        }
+        // Last key
+        const std::string & lastKey = propertyPath_.back();
+        auto it = currMap->find(lastKey);
+        if (it == currMap->end()) {
+            throw std::runtime_error("Property '" + lastKey + "' not found on object '" + targetName_ + "'");
+        }
+        // Type check against existing property
+        if (newValue.getType() != it->second.getType()) {
+            using namespace Variables;
+            throw std::runtime_error("Type mismatch for property '" + lastKey + "': expected '" +
+                                     TypeToString(it->second.getType()) + "' but got '" +
+                                     TypeToString(newValue.getType()) + "' File: " + filename_ +
+                                     ", Line: " + std::to_string(line_) + ", Column: " + std::to_string(column_));
+        }
+        // Assign and write back to symbol
+        (*currMap)[lastKey] = newValue;
+        symbol->setValue(varValue);
+    }
+
+    std::string toString() const override {
+        std::string repr = "Assignment: " + targetName_;
+        for (const auto & key : propertyPath_) repr += "->" + key;
+        return repr;
+    }
+};
+
+} // namespace Interpreter
+
+#endif // INTERPRETER_ASSIGNMENT_STATEMENT_NODE_HPP
\ No newline at end of file
diff --git a/src/Parser/Parser.cpp b/src/Parser/Parser.cpp
index 6aa0348..a2fd154 100644
--- a/src/Parser/Parser.cpp
+++ b/src/Parser/Parser.cpp
@@ -8,6 +8,7 @@
 #include "Interpreter/ConditionalStatementNode.hpp"
 // #include "Interpreter/ForStatementNode.hpp"  // removed until for-in loops are implemented
 #include "Interpreter/CallStatementNode.hpp"
+#include "Interpreter/AssignmentStatementNode.hpp"
 #include "Interpreter/DeclareVariableStatementNode.hpp"
 #include "Interpreter/ReturnStatementNode.hpp"
 #include "Symbols/SymbolContainer.hpp"
@@ -68,6 +69,15 @@
     expect(Lexer::Tokens::Type::PUNCTUATION, ";");
 }
 
+// Parse a top-level assignment statement and record it
+void Parser::parseAssignmentStatement() {
+    auto stmt = parseStatementNode();
+    Operations::Container::instance()->add(
+        Symbols::SymbolContainer::instance()->currentScopeName(),
+        Operations::Operation{Operations::Type::Assignment, "", std::move(stmt)}
+    );
+}
+
 // Parse an if-else conditional statement
 void Parser::parseIfStatement() {
     // 'if'
@@ -116,6 +126,47 @@
         return std::make_unique<Interpreter::ReturnStatementNode>(
             std::move(exprNode), this->current_filename_, tok.line_number, tok.column_number);
     }
+    // Assignment statement: variable or object member assignment
+    if (currentToken().type == Lexer::Tokens::Type::VARIABLE_IDENTIFIER) {
+        // Lookahead to detect '=' after optional '->' chains
+        size_t offset = 1;
+        // Skip member access sequence
+        while (peekToken(offset).type == Lexer::Tokens::Type::PUNCTUATION && peekToken(offset).value == "->") {
+            offset += 2;  // skip '->' and following identifier
+        }
+        const auto & look = peekToken(offset);
+        if (look.type == Lexer::Tokens::Type::OPERATOR_ASSIGNMENT && look.value == "=") {
+            // Consume base variable
+            auto idTok = expect(Lexer::Tokens::Type::VARIABLE_IDENTIFIER);
+            std::string baseName = idTok.value;
+            if (!baseName.empty() && baseName[0] == '$') baseName = baseName.substr(1);
+            // Collect member path keys
+            std::vector<std::string> propertyPath;
+            while (match(Lexer::Tokens::Type::PUNCTUATION, "->")) {
+                // Next token must be identifier or variable identifier
+                Lexer::Tokens::Token propTok;
+                if (currentToken().type == Lexer::Tokens::Type::VARIABLE_IDENTIFIER ||
+                    currentToken().type == Lexer::Tokens::Type::IDENTIFIER) {
+                    propTok = consumeToken();
+                } else {
+                    reportError("Expected property name after '->'");
+                }
+                std::string propName = propTok.value;
+                if (!propName.empty() && propName[0] == '$') propName = propName.substr(1);
+                propertyPath.push_back(propName);
+            }
+            // Consume '='
+            auto eqTok = expect(Lexer::Tokens::Type::OPERATOR_ASSIGNMENT, "=");
+            // Parse RHS expression
+            auto rhsExpr = parseParsedExpression(Symbols::Variables::Type::NULL_TYPE);
+            expect(Lexer::Tokens::Type::PUNCTUATION, ";");
+            // Build RHS node
+            auto rhsNode = buildExpressionFromParsed(rhsExpr);
+            return std::make_unique<Interpreter::AssignmentStatementNode>(
+                baseName, std::move(propertyPath), std::move(rhsNode),
+                this->current_filename_, eqTok.line_number, eqTok.column_number);
+        }
+    }
     // Function call statement
     if (currentToken().type == Lexer::Tokens::Type::IDENTIFIER &&
         peekToken().type == Lexer::Tokens::Type::PUNCTUATION && peekToken().value == "(") {
diff --git a/src/Parser/Parser.hpp b/src/Parser/Parser.hpp
index 56839fa..b8656e5 100644
--- a/src/Parser/Parser.hpp
+++ b/src/Parser/Parser.hpp
@@ -197,6 +197,19 @@
             parseCallStatement();
             return;
         }
+        // Assignment statement at top-level
+        if (currentToken().type == Lexer::Tokens::Type::VARIABLE_IDENTIFIER) {
+            size_t offset = 1;
+            // Skip member access chain
+            while (peekToken(offset).type == Lexer::Tokens::Type::PUNCTUATION && peekToken(offset).value == "->") {
+                offset += 2;
+            }
+            const auto & look = peekToken(offset);
+            if (look.type == Lexer::Tokens::Type::OPERATOR_ASSIGNMENT && look.value == "=") {
+                parseAssignmentStatement();
+                return;
+            }
+        }
 
         reportError("Unexpected token at beginning of statement");
     }
@@ -205,6 +218,8 @@
     void parseFunctionDefinition();
     // Parse a top-level function call statement (e.g., foo(arg1, arg2);)
     void parseCallStatement();
+    // Parse a top-level assignment statement (variable or object member)
+    void parseAssignmentStatement();
     // Parse a return statement (e.g., return; or return expr;)
     void parseReturnStatement();
     // Parse an if-else conditional statement
diff --git a/temp_test.vs b/temp_test.vs
deleted file mode 100644
index fb45629..0000000
--- a/temp_test.vs
+++ /dev/null
@@ -1 +0,0 @@
-object $person = { name: "Test" }; printnl($person->name);
diff --git a/test_scripts/object.vs b/test_scripts/object.vs
index 63526fb..976c926 100644
--- a/test_scripts/object.vs
+++ b/test_scripts/object.vs
@@ -26,3 +26,4 @@
 
 printnl("Child1 old age: ",$person2->children->age);
 $person2->children->age = $person2->children->age + 2;
+printnl("Child1 new age: ",$person2->children->age);
\ No newline at end of file
diff --git a/tmp.vs b/tmp.vs
deleted file mode 100644
index b29f906..0000000
--- a/tmp.vs
+++ /dev/null
@@ -1,4 +0,0 @@
-int $x = 5;
-printnl(typeof($x));
-printnl(typeof($x, "int"));
-printnl(typeof($x, "string"));
diff --git a/tmp2.vs b/tmp2.vs
deleted file mode 100644
index d64dd76..0000000
--- a/tmp2.vs
+++ /dev/null
@@ -1 +0,0 @@
-object $o = 123;
diff --git a/tmp_invalid.vs b/tmp_invalid.vs
deleted file mode 100644
index b9ade37..0000000
--- a/tmp_invalid.vs
+++ /dev/null
@@ -1 +0,0 @@
-object $o = {int age: "abc"};
diff --git a/tmp_test.vs b/tmp_test.vs
deleted file mode 100644
index c113a19..0000000
--- a/tmp_test.vs
+++ /dev/null
@@ -1,6 +0,0 @@
-object $person = {
-    name: "Szoni",
-    age: 37
-};
-
-printnl("Hello, ", $person->name);
diff --git a/tmp_test2.vs b/tmp_test2.vs
deleted file mode 100644
index 9a23b13..0000000
--- a/tmp_test2.vs
+++ /dev/null
@@ -1 +0,0 @@
-object $child = { name: "Test", age: 5 }; object $parent = { child: $child }; printnl("Name: ",$parent->child->name, " Age: ", $parent->child->age);
diff --git a/tmp_test3.vs b/tmp_test3.vs
deleted file mode 100644
index 86943cf..0000000
--- a/tmp_test3.vs
+++ /dev/null
@@ -1,4 +0,0 @@
-object $child1 = { name: "Child1", age: 10 };
-object $child2 = { name: "Child2", age: 15 };
-object $family = { $children: $child1, object $children: $child2 };
-printnl($family->children->name);
diff --git a/tmp_test4.vs b/tmp_test4.vs
deleted file mode 100644
index 94515f9..0000000
--- a/tmp_test4.vs
+++ /dev/null
@@ -1,4 +0,0 @@
-object $person = { name: "Test", age: 20 };
-printnl(typeof($person));
-printnl(typeof($person->name));
-printnl(typeof($person->age));
diff --git a/tmp_valid.vs b/tmp_valid.vs
deleted file mode 100644
index 9ccf014..0000000
--- a/tmp_valid.vs
+++ /dev/null
@@ -1,4 +0,0 @@
-object $o = { int age: 11 };
-printnl("Age: ",$o->age);
-printnl("Type: ", typeof($o));
-printnl("Type: ", typeof($o->age));
\ No newline at end of file

--
Gitblit v1.9.3