A simple scripting language in C++
Ferenc Szontágh
2025-04-18 3d9e8a26930930a4b63143f800bfa28e5d3caaf6
fix object variable assigment
3 files modified
1 files added
9 files deleted
203 ■■■■ changed files
src/Interpreter/AssignmentStatementNode.hpp 110 ●●●●● patch | view | raw | blame | history
src/Parser/Parser.cpp 51 ●●●●● patch | view | raw | blame | history
src/Parser/Parser.hpp 15 ●●●●● patch | view | raw | blame | history
temp_test.vs 1 ●●●● patch | view | raw | blame | history
test_scripts/object.vs 1 ●●●● patch | view | raw | blame | history
tmp.vs 4 ●●●● patch | view | raw | blame | history
tmp2.vs 1 ●●●● patch | view | raw | blame | history
tmp_invalid.vs 1 ●●●● patch | view | raw | blame | history
tmp_test.vs 6 ●●●●● patch | view | raw | blame | history
tmp_test2.vs 1 ●●●● patch | view | raw | blame | history
tmp_test3.vs 4 ●●●● patch | view | raw | blame | history
tmp_test4.vs 4 ●●●● patch | view | raw | blame | history
tmp_valid.vs 4 ●●●● 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