| src/Interpreter/AssignmentStatementNode.hpp | ●●●●● patch | view | raw | blame | history | |
| src/Parser/Parser.cpp | ●●●●● patch | view | raw | blame | history | |
| src/Parser/Parser.hpp | ●●●●● patch | view | raw | blame | history | |
| temp_test.vs | ●●●●● patch | view | raw | blame | history | |
| test_scripts/object.vs | ●●●●● patch | view | raw | blame | history | |
| tmp.vs | ●●●●● patch | view | raw | blame | history | |
| tmp2.vs | ●●●●● patch | view | raw | blame | history | |
| tmp_invalid.vs | ●●●●● patch | view | raw | blame | history | |
| tmp_test.vs | ●●●●● patch | view | raw | blame | history | |
| tmp_test2.vs | ●●●●● patch | view | raw | blame | history | |
| tmp_test3.vs | ●●●●● patch | view | raw | blame | history | |
| tmp_test4.vs | ●●●●● patch | view | raw | blame | history | |
| tmp_valid.vs | ●●●●● patch | view | raw | blame | history |
src/Interpreter/AssignmentStatementNode.hpp
New file @@ -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 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 == "(") { 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 temp_test.vs
File was deleted 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); tmp.vs
File was deleted tmp2.vs
File was deleted tmp_invalid.vs
File was deleted tmp_test.vs
File was deleted tmp_test2.vs
File was deleted tmp_test3.vs
File was deleted tmp_test4.vs
File was deleted tmp_valid.vs
File was deleted