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