A simple scripting language in C++
Ferenc Szontágh
2025-04-18 17b3739f756a1e713704b22cce89307308cea2d8
add else if to the if
4 files modified
1 files added
736 ■■■■■ changed files
src/Interpreter/ForStatementNode.hpp 91 ●●●●● patch | view | raw | blame | history
src/Interpreter/Interpreter.hpp 10 ●●●●● patch | view | raw | blame | history
src/Parser/Parser.cpp 421 ●●●● patch | view | raw | blame | history
src/Parser/Parser.hpp 205 ●●●●● patch | view | raw | blame | history
test_scripts/object.vs 9 ●●●●● patch | view | raw | blame | history
src/Interpreter/ForStatementNode.hpp
New file
@@ -0,0 +1,91 @@
 #ifndef INTERPRETER_FOR_STATEMENT_NODE_HPP
 #define INTERPRETER_FOR_STATEMENT_NODE_HPP
 #include <vector>
 #include <memory>
 #include <string>
 #include <stdexcept>
 #include "Interpreter/StatementNode.hpp"
 #include "Interpreter/ExpressionNode.hpp"
 #include "Symbols/Value.hpp"
 #include "Symbols/SymbolContainer.hpp"
 #include "Symbols/SymbolFactory.hpp"
 namespace Interpreter {
 /**
  * @brief Statement node representing a for-in loop over object members.
  */
 class ForStatementNode : public StatementNode {
   private:
    Symbols::Variables::Type keyType_;
    std::string keyName_;
    std::string valueName_;
    std::unique_ptr<ExpressionNode> iterableExpr_;
    std::vector<std::unique_ptr<StatementNode>> body_;
   public:
    ForStatementNode(Symbols::Variables::Type keyType,
                     std::string keyName,
                     std::string valueName,
                     std::unique_ptr<ExpressionNode> iterableExpr,
                     std::vector<std::unique_ptr<StatementNode>> body,
                     const std::string & file_name,
                     int line,
                     size_t column)
      : StatementNode(file_name, line, column),
        keyType_(keyType),
        keyName_(std::move(keyName)),
        valueName_(std::move(valueName)),
        iterableExpr_(std::move(iterableExpr)),
        body_(std::move(body)) {}
    void interpret(Interpreter & interpreter) const override {
        using namespace Symbols;
        // Evaluate iterable expression
        auto iterableVal = iterableExpr_->evaluate(interpreter);
        if (iterableVal.getType() != Variables::Type::OBJECT) {
            throw std::runtime_error("For-in loop applied to non-object at " + filename_ + ":" + std::to_string(line_) +
                                     "," + std::to_string(column_));
        }
        // Access underlying object map
        const auto & objMap = std::get<Value::ObjectMap>(iterableVal.get());
        auto * symContainer = SymbolContainer::instance();
        const std::string base_ns = symContainer->currentScopeName();
        const std::string var_ns  = base_ns + ".variables";
        // Iterate through object entries
        for (const auto & entry : objMap) {
            // Key binding
            const std::string & key = entry.first;
            Value keyVal(key);
            if (!symContainer->exists(keyName_, var_ns)) {
                auto sym = SymbolFactory::createVariable(keyName_, keyVal, base_ns);
                symContainer->add(sym);
            } else {
                auto sym = symContainer->get(var_ns, keyName_);
                sym->setValue(keyVal);
            }
            // Value binding
            Value valVal = entry.second;
            if (!symContainer->exists(valueName_, var_ns)) {
                auto sym = SymbolFactory::createVariable(valueName_, valVal, base_ns);
                symContainer->add(sym);
            } else {
                auto sym = symContainer->get(var_ns, valueName_);
                sym->setValue(valVal);
            }
            // Execute loop body
            for (const auto & stmt : body_) {
                stmt->interpret(interpreter);
            }
        }
    }
    std::string toString() const override {
        return "ForStatementNode at " + filename_ + ":" + std::to_string(line_);
    }
 };
} // namespace Interpreter
#endif // INTERPRETER_FOR_STATEMENT_NODE_HPP
src/Interpreter/Interpreter.hpp
@@ -72,7 +72,17 @@
                }
                break;
            case Operations::Type::Return:
                // return statement
                if (op.statement) {
                    op.statement->interpret(*this);
                }
                break;
            case Operations::Type::Loop:
                // for-in or while loop
                if (op.statement) {
                    op.statement->interpret(*this);
                }
                break;
            case Operations::Type::Break:
            case Operations::Type::Continue:
            case Operations::Type::Block:
src/Parser/Parser.cpp
@@ -5,14 +5,14 @@
#include "Interpreter/OperationsFactory.hpp"
#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/AssignmentStatementNode.hpp"
#include "Interpreter/CallStatementNode.hpp"
#include "Interpreter/ConditionalStatementNode.hpp"
#include "Interpreter/DeclareVariableStatementNode.hpp"
#include "Interpreter/ExpressionBuilder.hpp"
#include "Interpreter/ForStatementNode.hpp"
#include "Interpreter/ReturnStatementNode.hpp"
#include "Symbols/SymbolContainer.hpp"
#include "Interpreter/ExpressionBuilder.hpp"
// Additional necessary includes, if needed
namespace Parser {
@@ -72,10 +72,8 @@
// 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)}
    );
    Operations::Container::instance()->add(Symbols::SymbolContainer::instance()->currentScopeName(),
                                           Operations::Operation{ Operations::Type::Assignment, "", std::move(stmt) });
}
// Parse an if-else conditional statement
@@ -94,28 +92,108 @@
        thenBranch.push_back(parseStatementNode());
    }
    expect(Lexer::Tokens::Type::PUNCTUATION, "}");
    // else branch
    // else / else-if branch
    std::vector<std::unique_ptr<Interpreter::StatementNode>> elseBranch;
    if (match(Lexer::Tokens::Type::KEYWORD, "else")) {
        // else-if: nested conditional
        if (currentToken().type == Lexer::Tokens::Type::KEYWORD && currentToken().value == "if") {
            elseBranch.push_back(parseIfStatementNode());
        } else {
        expect(Lexer::Tokens::Type::PUNCTUATION, "{");
        while (!(currentToken().type == Lexer::Tokens::Type::PUNCTUATION && currentToken().value == "}")) {
            elseBranch.push_back(parseStatementNode());
        }
        expect(Lexer::Tokens::Type::PUNCTUATION, "}");
    }
    }
    // build condition node
    auto condNode = buildExpressionFromParsed(condExpr);
    auto stmt = std::make_unique<Interpreter::ConditionalStatementNode>(
        std::move(condNode), std::move(thenBranch), std::move(elseBranch),
        this->current_filename_, ifToken.line_number, ifToken.column_number);
    auto stmt     = std::make_unique<Interpreter::ConditionalStatementNode>(std::move(condNode), std::move(thenBranch),
                                                                            std::move(elseBranch), this->current_filename_,
                                                                            ifToken.line_number, ifToken.column_number);
    // add conditional operation
    Operations::Container::instance()->add(
        Symbols::SymbolContainer::instance()->currentScopeName(),
    Operations::Container::instance()->add(Symbols::SymbolContainer::instance()->currentScopeName(),
        Operations::Operation{Operations::Type::Conditional, "", std::move(stmt)});
}
// Parse an if-else conditional block and return a StatementNode (for nested blocks)
std::unique_ptr<Interpreter::StatementNode> Parser::parseIfStatementNode() {
    auto ifToken = expect(Lexer::Tokens::Type::KEYWORD, "if");
    expect(Lexer::Tokens::Type::PUNCTUATION, "(");
    auto condExpr = parseParsedExpression(Symbols::Variables::Type::NULL_TYPE);
    expect(Lexer::Tokens::Type::PUNCTUATION, ")");
    expect(Lexer::Tokens::Type::PUNCTUATION, "{");
    std::vector<std::unique_ptr<Interpreter::StatementNode>> thenBranch;
    while (!(currentToken().type == Lexer::Tokens::Type::PUNCTUATION && currentToken().value == "}")) {
        thenBranch.push_back(parseStatementNode());
    }
    expect(Lexer::Tokens::Type::PUNCTUATION, "}");
    std::vector<std::unique_ptr<Interpreter::StatementNode>> elseBranch;
    if (match(Lexer::Tokens::Type::KEYWORD, "else")) {
        // else-if: nested conditional
        if (currentToken().type == Lexer::Tokens::Type::KEYWORD && currentToken().value == "if") {
            elseBranch.push_back(parseIfStatementNode());
        } else {
            expect(Lexer::Tokens::Type::PUNCTUATION, "{");
            while (!(currentToken().type == Lexer::Tokens::Type::PUNCTUATION && currentToken().value == "}")) {
                elseBranch.push_back(parseStatementNode());
            }
            expect(Lexer::Tokens::Type::PUNCTUATION, "}");
        }
    }
    auto   condNode = buildExpressionFromParsed(condExpr);
    auto * node =
        new Interpreter::ConditionalStatementNode(std::move(condNode), std::move(thenBranch), std::move(elseBranch),
                                                  this->current_filename_, ifToken.line_number, ifToken.column_number);
    return std::unique_ptr<Interpreter::StatementNode>(node);
}
// Parse a for-in loop over object members and return a StatementNode (for nested blocks)
std::unique_ptr<Interpreter::StatementNode> Parser::parseForStatementNode() {
    auto forToken = expect(Lexer::Tokens::Type::KEYWORD, "for");
    expect(Lexer::Tokens::Type::PUNCTUATION, "(");
    Symbols::Variables::Type keyType = parseType();
    auto                     keyTok  = expect(Lexer::Tokens::Type::VARIABLE_IDENTIFIER);
    std::string              keyName = keyTok.value;
    if (!keyName.empty() && keyName[0] == '$') {
        keyName = keyName.substr(1);
    }
    expect(Lexer::Tokens::Type::PUNCTUATION, ",");
    if (!(currentToken().type == Lexer::Tokens::Type::IDENTIFIER && currentToken().value == "auto")) {
        reportError("Expected 'auto' in for-in loop");
    }
    consumeToken();
    auto        valTok  = expect(Lexer::Tokens::Type::VARIABLE_IDENTIFIER);
    std::string valName = valTok.value;
    if (!valName.empty() && valName[0] == '$') {
        valName = valName.substr(1);
    }
    expect(Lexer::Tokens::Type::PUNCTUATION, ":");
    auto iterableExpr = parseParsedExpression(Symbols::Variables::Type::NULL_TYPE);
    expect(Lexer::Tokens::Type::PUNCTUATION, ")");
    expect(Lexer::Tokens::Type::PUNCTUATION, "{");
    std::vector<std::unique_ptr<Interpreter::StatementNode>> body;
    while (!(currentToken().type == Lexer::Tokens::Type::PUNCTUATION && currentToken().value == "}")) {
        body.push_back(parseStatementNode());
    }
    expect(Lexer::Tokens::Type::PUNCTUATION, "}");
    auto   iterableExprNode = buildExpressionFromParsed(iterableExpr);
    auto * node =
        new Interpreter::ForStatementNode(keyType, keyName, valName, std::move(iterableExprNode), std::move(body),
                                          this->current_filename_, forToken.line_number, forToken.column_number);
    return std::unique_ptr<Interpreter::StatementNode>(node);
}
// Parse a single statement and return its StatementNode (for use in blocks)
std::unique_ptr<Interpreter::StatementNode> Parser::parseStatementNode() {
    // Handle nested if statements in blocks
    if (currentToken().type == Lexer::Tokens::Type::KEYWORD && currentToken().value == "if") {
        return parseIfStatementNode();
    }
    // Handle nested for loops in blocks
    if (currentToken().type == Lexer::Tokens::Type::KEYWORD && currentToken().value == "for") {
        return parseForStatementNode();
    }
    // Return statement
    if (currentToken().type == Lexer::Tokens::Type::KEYWORD_RETURN) {
        auto tok = expect(Lexer::Tokens::Type::KEYWORD_RETURN);
@@ -125,8 +203,8 @@
        }
        expect(Lexer::Tokens::Type::PUNCTUATION, ";");
        auto exprNode = expr ? buildExpressionFromParsed(expr) : nullptr;
        return std::make_unique<Interpreter::ReturnStatementNode>(
            std::move(exprNode), this->current_filename_, tok.line_number, tok.column_number);
        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) {
@@ -141,7 +219,9 @@
            // 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);
            if (!baseName.empty() && baseName[0] == '$') {
                baseName = baseName.substr(1);
            }
            // Collect member path keys
            std::vector<std::string> propertyPath;
            while (match(Lexer::Tokens::Type::PUNCTUATION, "->")) {
@@ -154,7 +234,9 @@
                    reportError("Expected property name after '->'");
                }
                std::string propName = propTok.value;
                if (!propName.empty() && propName[0] == '$') propName = propName.substr(1);
                if (!propName.empty() && propName[0] == '$') {
                    propName = propName.substr(1);
                }
                propertyPath.push_back(propName);
            }
            // Consume '='
@@ -164,9 +246,9 @@
            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);
            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
@@ -179,7 +261,9 @@
        if (!(currentToken().type == Lexer::Tokens::Type::PUNCTUATION && currentToken().value == ")")) {
            while (true) {
                args.push_back(parseParsedExpression(Symbols::Variables::Type::NULL_TYPE));
                if (match(Lexer::Tokens::Type::PUNCTUATION, ",")) continue;
                if (match(Lexer::Tokens::Type::PUNCTUATION, ",")) {
                    continue;
                }
                break;
            }
        }
@@ -190,22 +274,24 @@
        for (auto &p : args) {
            exprs.push_back(buildExpressionFromParsed(p));
        }
        return std::make_unique<Interpreter::CallStatementNode>(
            funcName, std::move(exprs), this->current_filename_, idTok.line_number, idTok.column_number);
        return std::make_unique<Interpreter::CallStatementNode>(funcName, std::move(exprs), this->current_filename_,
                                                                idTok.line_number, idTok.column_number);
    }
    // Variable declaration
    if (Parser::variable_types.find(currentToken().type) != Parser::variable_types.end()) {
        auto type = parseType();
        auto idTok = expect(Lexer::Tokens::Type::VARIABLE_IDENTIFIER);
        std::string name = idTok.value;
        if (!name.empty() && name[0] == '$') name = name.substr(1);
        if (!name.empty() && name[0] == '$') {
            name = name.substr(1);
        }
        expect(Lexer::Tokens::Type::OPERATOR_ASSIGNMENT, "=");
        auto valExpr = parseParsedExpression(type);
        expect(Lexer::Tokens::Type::PUNCTUATION, ";");
        auto exprNode = buildExpressionFromParsed(valExpr);
        return std::make_unique<Interpreter::DeclareVariableStatementNode>(
            name, Symbols::SymbolContainer::instance()->currentScopeName(), type,
            std::move(exprNode), this->current_filename_, idTok.line_number, idTok.column_number);
            name, Symbols::SymbolContainer::instance()->currentScopeName(), type, std::move(exprNode),
            this->current_filename_, idTok.line_number, idTok.column_number);
    }
    reportError("Unexpected token in block");
    return nullptr;
@@ -286,13 +372,9 @@
    expect(Lexer::Tokens::Type::PUNCTUATION, ")");
    expect(Lexer::Tokens::Type::PUNCTUATION, ";");
    // Record the function call operation
    Interpreter::OperationsFactory::callFunction(
        func_name,
        std::move(args),
    Interpreter::OperationsFactory::callFunction(func_name, std::move(args),
        Symbols::SymbolContainer::instance()->currentScopeName(),
        this->current_filename_,
        id_token.line_number,
        id_token.column_number);
                                                 this->current_filename_, id_token.line_number, id_token.column_number);
}
// Parse a return statement, e.g., return; or return expression;
@@ -305,14 +387,55 @@
        expr = parseParsedExpression(Symbols::Variables::Type::NULL_TYPE);
    }
    // Record return operation
    Interpreter::OperationsFactory::callReturn(
        expr,
        Symbols::SymbolContainer::instance()->currentScopeName(),
        this->current_filename_,
        returnToken.line_number,
    Interpreter::OperationsFactory::callReturn(expr, Symbols::SymbolContainer::instance()->currentScopeName(),
                                               this->current_filename_, returnToken.line_number,
        returnToken.column_number);
    // Consume terminating semicolon
    expect(Lexer::Tokens::Type::PUNCTUATION, ";");
}
// Parse a for-in loop over object members
void Parser::parseForStatement() {
    // 'for'
    auto forToken = expect(Lexer::Tokens::Type::KEYWORD, "for");
    expect(Lexer::Tokens::Type::PUNCTUATION, "(");
    // Parse key type and name
    Symbols::Variables::Type keyType = parseType();
    auto                     keyTok  = expect(Lexer::Tokens::Type::VARIABLE_IDENTIFIER);
    std::string              keyName = keyTok.value;
    if (!keyName.empty() && keyName[0] == '$') {
        keyName = keyName.substr(1);
    }
    expect(Lexer::Tokens::Type::PUNCTUATION, ",");
    // Parse 'auto' keyword for value
    if (!(currentToken().type == Lexer::Tokens::Type::IDENTIFIER && currentToken().value == "auto")) {
        reportError("Expected 'auto' in for-in loop");
    }
    consumeToken();
    auto        valTok  = expect(Lexer::Tokens::Type::VARIABLE_IDENTIFIER);
    std::string valName = valTok.value;
    if (!valName.empty() && valName[0] == '$') {
        valName = valName.substr(1);
    }
    expect(Lexer::Tokens::Type::PUNCTUATION, ":");
    // Parse iterable expression
    auto iterableExpr = parseParsedExpression(Symbols::Variables::Type::NULL_TYPE);
    expect(Lexer::Tokens::Type::PUNCTUATION, ")");
    expect(Lexer::Tokens::Type::PUNCTUATION, "{");
    // Parse loop body
    std::vector<std::unique_ptr<Interpreter::StatementNode>> body;
    while (!(currentToken().type == Lexer::Tokens::Type::PUNCTUATION && currentToken().value == "}")) {
        body.push_back(parseStatementNode());
    }
    expect(Lexer::Tokens::Type::PUNCTUATION, "}");
    // Build expression and statement node
    auto iterableExprNode = buildExpressionFromParsed(iterableExpr);
    auto stmt = std::make_unique<Interpreter::ForStatementNode>(keyType, keyName, valName, std::move(iterableExprNode),
                                                                std::move(body), this->current_filename_,
                                                                forToken.line_number, forToken.column_number);
    // Record loop operation
    Operations::Container::instance()->add(Symbols::SymbolContainer::instance()->currentScopeName(),
                                           Operations::Operation{ Operations::Type::Loop, "", std::move(stmt) });
}
// Continue with numeric literal parsing
@@ -440,9 +563,9 @@
                    // 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;
                    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, ",")) {
@@ -472,8 +595,10 @@
                    if (output_queue.size() < 2) {
                        Parser::reportError("Malformed expression", token);
                    }
                    auto rhs = std::move(output_queue.back()); output_queue.pop_back();
                    auto lhs = std::move(output_queue.back()); output_queue.pop_back();
                    auto rhs = std::move(output_queue.back());
                    output_queue.pop_back();
                    auto lhs = std::move(output_queue.back());
                    output_queue.pop_back();
                    output_queue.push_back(Lexer::applyOperator(op, std::move(rhs), std::move(lhs)));
                } else {
                    break;
@@ -558,9 +683,9 @@
            // Create call expression node
            output_queue.push_back(ParsedExpression::makeCall(func_name, std::move(call_args)));
            expect_unary = false;
        } else if (token.type == Lexer::Tokens::Type::OPERATOR_ARITHMETIC
                   || token.type == Lexer::Tokens::Type::OPERATOR_RELATIONAL
                   || token.type == Lexer::Tokens::Type::OPERATOR_LOGICAL) {
        } else if (token.type == Lexer::Tokens::Type::OPERATOR_ARITHMETIC ||
                   token.type == Lexer::Tokens::Type::OPERATOR_RELATIONAL ||
                   token.type == Lexer::Tokens::Type::OPERATOR_LOGICAL) {
            std::string op = std::string(token.lexeme);
            if (expect_unary && Lexer::isUnaryOperator(op)) {
@@ -598,11 +723,10 @@
            operator_stack.push(op);
            consumeToken();
            expect_unary = true;
        } else if (token.type == Lexer::Tokens::Type::NUMBER
                   || token.type == Lexer::Tokens::Type::STRING_LITERAL
                   || token.type == Lexer::Tokens::Type::KEYWORD
                   || token.type == Lexer::Tokens::Type::VARIABLE_IDENTIFIER
                   || token.type == Lexer::Tokens::Type::IDENTIFIER) {
        } else if (token.type == Lexer::Tokens::Type::NUMBER || token.type == Lexer::Tokens::Type::STRING_LITERAL ||
                   token.type == Lexer::Tokens::Type::KEYWORD ||
                   token.type == Lexer::Tokens::Type::VARIABLE_IDENTIFIER ||
                   token.type == Lexer::Tokens::Type::IDENTIFIER) {
            if (token.type == Lexer::Tokens::Type::IDENTIFIER) {
                // Treat bare identifiers as variable references for member access
                output_queue.push_back(ParsedExpression::makeVariable(token.value));
@@ -669,4 +793,199 @@
        reportError("Unexpected tokens after program end");
    }
}
void Parser::parseStatement() {
    const auto & token_type = currentToken().type;
    // if-else conditional
    if (token_type == Lexer::Tokens::Type::KEYWORD && currentToken().value == "if") {
        parseIfStatement();
        return;
    }
    if (token_type == Lexer::Tokens::Type::KEYWORD_FUNCTION_DECLARATION) {
        parseFunctionDefinition();
        return;
    }
    // Return statement
    if (token_type == Lexer::Tokens::Type::KEYWORD_RETURN) {
        parseReturnStatement();
        return;
    }
    // For-in loop over object members
    if (token_type == Lexer::Tokens::Type::KEYWORD && currentToken().value == "for") {
        parseForStatement();
        return;
    }
    // Variable definition if leading token matches a type keyword
    if (Parser::variable_types.find(token_type) != Parser::variable_types.end()) {
        parseVariableDefinition();
        return;
    }
    // Function call if identifier followed by '('
    if (currentToken().type == Lexer::Tokens::Type::IDENTIFIER &&
        peekToken().type == Lexer::Tokens::Type::PUNCTUATION && peekToken().value == "(") {
        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");
}
Symbols::Variables::Type Parser::parseType() {
    const auto & token = currentToken();
    // Direct lookup for type keyword
    auto         it    = Parser::variable_types.find(token.type);
    if (it != Parser::variable_types.end()) {
        consumeToken();
        return it->second;
    }
    reportError("Expected type keyword (string, int, double, float)");
}
Symbols::Value Parser::parseValue(Symbols::Variables::Type expected_var_type) {
    Lexer::Tokens::Token token       = currentToken();
    bool                 is_negative = false;
    // Handle unary sign
    if (token.type == Lexer::Tokens::Type::OPERATOR_ARITHMETIC && (token.lexeme == "-" || token.lexeme == "+") &&
        peekToken().type == Lexer::Tokens::Type::NUMBER) {
        is_negative = (token.lexeme == "-");
        token       = peekToken();
        consumeToken();  // consumed the sign
    }
    // STRING type
    if (expected_var_type == Symbols::Variables::Type::STRING) {
        if (token.type == Lexer::Tokens::Type::STRING_LITERAL) {
            consumeToken();
            return Symbols::Value(token.value);
        }
        reportError("Expected string literal value");
    }
    // BOOLEAN type
    if (expected_var_type == Symbols::Variables::Type::BOOLEAN) {
        if (token.type == Lexer::Tokens::Type::KEYWORD && (token.value == "true" || token.value == "false")) {
            consumeToken();
            return Symbols::Value(token.value == "true");
        }
        reportError("Expected boolean literal value (true or false)");
    }
    // NUMERIC types
    if (expected_var_type == Symbols::Variables::Type::INTEGER ||
        expected_var_type == Symbols::Variables::Type::DOUBLE || expected_var_type == Symbols::Variables::Type::FLOAT) {
        if (token.type == Lexer::Tokens::Type::NUMBER) {
            Symbols::Value val = parseNumericLiteral(token.value, is_negative, expected_var_type);
            consumeToken();
            return val;
        }
        reportError("Expected numeric literal value");
    }
    reportError("Unsupported variable type encountered during value parsing");
    return Symbols::Value();  // compiler happy
}
bool Parser::isAtEnd() const {
    // We're at the end if the index equals the number of tokens,
    // or if only the EOF token remains (as the last element)
    return current_token_index_ >= tokens_.size() ||
           (current_token_index_ == tokens_.size() - 1 && tokens_.back().type == Lexer::Tokens::Type::END_OF_FILE);
}
Lexer::Tokens::Token Parser::expect(Lexer::Tokens::Type expected_type, const std::string & expected_value) {
    if (isAtEnd()) {
        reportError("Unexpected end of file, expected token: " + Lexer::Tokens::TypeToString(expected_type) +
                    " with value '" + expected_value + "'");
    }
    const auto & token = currentToken();
    if (token.type == expected_type && token.value == expected_value) {
        return consumeToken();
    }
    reportError("Expected token " + Lexer::Tokens::TypeToString(expected_type) + " with value '" + expected_value +
                "'");
    return token;  // reportError throws
}
Lexer::Tokens::Token Parser::expect(Lexer::Tokens::Type expected_type) {
    if (isAtEnd()) {
        reportError("Unexpected end of file, expected token type: " + Lexer::Tokens::TypeToString(expected_type));
    }
    const auto & token = currentToken();
    if (token.type == expected_type) {
        return consumeToken();
    }
    reportError("Expected token type " + Lexer::Tokens::TypeToString(expected_type));
    // reportError throws; this return is never reached, but may satisfy the compiler
    return token;  // or let reportError throw
}
bool Parser::match(Lexer::Tokens::Type expected_type, const std::string & expected_value) {
    if (isAtEnd()) {
        return false;
    }
    const auto & token = currentToken();
    if (token.type == expected_type && token.value == expected_value) {
        consumeToken();
        return true;
    }
    return false;
}
bool Parser::match(Lexer::Tokens::Type expected_type) {
    if (isAtEnd()) {
        return false;
    }
    if (currentToken().type == expected_type) {
        consumeToken();
        return true;
    }
    return false;
}
Lexer::Tokens::Token Parser::consumeToken() {
    if (isAtEnd()) {
        throw std::runtime_error("Cannot consume token at end of stream.");
    }
    return tokens_[current_token_index_++];
}
const Lexer::Tokens::Token & Parser::peekToken(size_t offset) const {
    if (current_token_index_ + offset >= tokens_.size()) {
        // If at or beyond EOF, return the last token (should be EOF)
        if (!tokens_.empty()) {
            return tokens_.back();
        }
        throw std::runtime_error("Cannot peek beyond end of token stream.");
    }
    return tokens_[current_token_index_ + offset];
}
const Lexer::Tokens::Token & Parser::currentToken() const {
    if (isAtEnd()) {
        // Technically we should never reach this if parseScript's loop is correct
        // But it's useful as a safety check
        if (!tokens_.empty() && tokens_.back().type == Lexer::Tokens::Type::END_OF_FILE) {
            return tokens_.back();  // return the EOF token
        }
        throw std::runtime_error("Unexpected end of token stream reached.");
    }
    return tokens_[current_token_index_];
}
}  // namespace Parser
src/Parser/Parser.hpp
@@ -47,7 +47,6 @@
    void parseScript(const std::vector<Lexer::Tokens::Token> & tokens, std::string_view input_string,
                     const std::string & filename);
    static const std::unordered_map<std::string, Lexer::Tokens::Type>              keywords;
    static const std::unordered_map<Lexer::Tokens::Type, Symbols::Variables::Type> variable_types;
@@ -58,100 +57,29 @@
    std::string                       current_filename_;
    // Token stream handling and error-reporting helper functions (unchanged)
    const Lexer::Tokens::Token & currentToken() const {
        if (isAtEnd()) {
            // Technically we should never reach this if parseScript's loop is correct
            // But it's useful as a safety check
            if (!tokens_.empty() && tokens_.back().type == Lexer::Tokens::Type::END_OF_FILE) {
                return tokens_.back();  // return the EOF token
            }
            throw std::runtime_error("Unexpected end of token stream reached.");
        }
        return tokens_[current_token_index_];
    }
    const Lexer::Tokens::Token & currentToken() const;
    // Look ahead in the token stream
    const Lexer::Tokens::Token & peekToken(size_t offset = 1) const {
        if (current_token_index_ + offset >= tokens_.size()) {
            // If at or beyond EOF, return the last token (should be EOF)
            if (!tokens_.empty()) {
                return tokens_.back();
            }
            throw std::runtime_error("Cannot peek beyond end of token stream.");
        }
        return tokens_[current_token_index_ + offset];
    }
    const Lexer::Tokens::Token & peekToken(size_t offset = 1) const;
    // Consume (advance past) the current token and return it
    Lexer::Tokens::Token consumeToken() {
        if (isAtEnd()) {
            throw std::runtime_error("Cannot consume token at end of stream.");
        }
        return tokens_[current_token_index_++];
    }
    Lexer::Tokens::Token consumeToken();
    // Check if current token type matches the expected type
    // If so, consume it and return true; otherwise return false
    bool match(Lexer::Tokens::Type expected_type) {
        if (isAtEnd()) {
            return false;
        }
        if (currentToken().type == expected_type) {
            consumeToken();
            return true;
        }
        return false;
    }
    bool match(Lexer::Tokens::Type expected_type);
    // Check if current token type and value match the expected ones
    // Only use value checking for operators and punctuation
    bool match(Lexer::Tokens::Type expected_type, const std::string & expected_value) {
        if (isAtEnd()) {
            return false;
        }
        const auto & token = currentToken();
        if (token.type == expected_type && token.value == expected_value) {
            consumeToken();
            return true;
        }
        return false;
    }
    bool match(Lexer::Tokens::Type expected_type, const std::string & expected_value);
    Lexer::Tokens::Token expect(Lexer::Tokens::Type expected_type) {
        if (isAtEnd()) {
            reportError("Unexpected end of file, expected token type: " + Lexer::Tokens::TypeToString(expected_type));
        }
        const auto & token = currentToken();
        if (token.type == expected_type) {
            return consumeToken();
        }
        reportError("Expected token type " + Lexer::Tokens::TypeToString(expected_type));
        // reportError throws; this return is never reached, but may satisfy the compiler
        return token;  // or let reportError throw
    }
    Lexer::Tokens::Token expect(Lexer::Tokens::Type expected_type);
    // Like expect, but also checks the token's value
    Lexer::Tokens::Token expect(Lexer::Tokens::Type expected_type, const std::string & expected_value) {
        if (isAtEnd()) {
            reportError("Unexpected end of file, expected token: " + Lexer::Tokens::TypeToString(expected_type) +
                        " with value '" + expected_value + "'");
        }
        const auto & token = currentToken();
        if (token.type == expected_type && token.value == expected_value) {
            return consumeToken();
        }
        reportError("Expected token " + Lexer::Tokens::TypeToString(expected_type) + " with value '" + expected_value +
                    "'");
        return token;  // reportError throws
    }
    Lexer::Tokens::Token expect(Lexer::Tokens::Type expected_type, const std::string & expected_value);
    // Check if we've reached the end of relevant tokens (just before EOF)
    bool isAtEnd() const {
        // We're at the end if the index equals the number of tokens,
        // or if only the EOF token remains (as the last element)
        return current_token_index_ >= tokens_.size() ||
               (current_token_index_ == tokens_.size() - 1 && tokens_.back().type == Lexer::Tokens::Type::END_OF_FILE);
    }
    bool isAtEnd() const;
    [[noreturn]] void reportError(const std::string & message, const std::string & expected = "") {
        if (current_token_index_ < tokens_.size()) {
@@ -168,52 +96,7 @@
    }
    // parseStatement (updated to handle return)
    void parseStatement() {
        const auto & token_type = currentToken().type;
        // if-else conditional
        if (token_type == Lexer::Tokens::Type::KEYWORD && currentToken().value == "if") {
            parseIfStatement();
            return;
        }
        if (token_type == Lexer::Tokens::Type::KEYWORD_FUNCTION_DECLARATION) {
            parseFunctionDefinition();
            return;
        }
        // Return statement
        if (token_type == Lexer::Tokens::Type::KEYWORD_RETURN) {
            parseReturnStatement();
            return;
        }
        // Variable definition if leading token matches a type keyword
        if (Parser::variable_types.find(token_type) != Parser::variable_types.end()) {
            parseVariableDefinition();
            return;
        }
        // Function call if identifier followed by '('
        if (currentToken().type == Lexer::Tokens::Type::IDENTIFIER &&
            peekToken().type == Lexer::Tokens::Type::PUNCTUATION && peekToken().value == "(") {
            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");
    }
    void                                        parseStatement();
    void parseVariableDefinition();
    void parseFunctionDefinition();
    // Parse a top-level function call statement (e.g., foo(arg1, arg2);)
@@ -222,10 +105,14 @@
    void parseAssignmentStatement();
    // Parse a return statement (e.g., return; or return expr;)
    void parseReturnStatement();
    // Parse an if-else conditional statement
    // Parse an if-else conditional statement (at top-level)
    void parseIfStatement();
    // Parse a for-in loop over object members
    // Parse a for-in loop over object members (at top-level)
    void parseForStatement();
    // Parse an if-else conditional block and return a StatementNode (for nested blocks)
    std::unique_ptr<Interpreter::StatementNode> parseIfStatementNode();
    // Parse a for-in loop over object members and return a StatementNode (for nested blocks)
    std::unique_ptr<Interpreter::StatementNode> parseForStatementNode();
    // Parse a statement node for use inside blocks (not added to operation container)
    std::unique_ptr<Interpreter::StatementNode> parseStatementNode();
@@ -233,69 +120,11 @@
    // type : KEYWORD_STRING | KEYWORD_INT | KEYWORD_DOUBLE
    // Returns the corresponding Symbols::Variables::Type enum and consumes the token
    Symbols::Variables::Type parseType() {
        const auto & token = currentToken();
        // Direct lookup for type keyword
        auto         it    = Parser::variable_types.find(token.type);
        if (it != Parser::variable_types.end()) {
            consumeToken();
            return it->second;
        }
        reportError("Expected type keyword (string, int, double, float)");
    }
    Symbols::Value parseValue(Symbols::Variables::Type expected_var_type) {
        Lexer::Tokens::Token token       = currentToken();
        bool                 is_negative = false;
        // Handle unary sign
        if (token.type == Lexer::Tokens::Type::OPERATOR_ARITHMETIC && (token.lexeme == "-" || token.lexeme == "+") &&
            peekToken().type == Lexer::Tokens::Type::NUMBER) {
            is_negative = (token.lexeme == "-");
            token       = peekToken();
            consumeToken();  // consumed the sign
        }
        // STRING type
        if (expected_var_type == Symbols::Variables::Type::STRING) {
            if (token.type == Lexer::Tokens::Type::STRING_LITERAL) {
                consumeToken();
                return Symbols::Value(token.value);
            }
            reportError("Expected string literal value");
        }
        // BOOLEAN type
        if (expected_var_type == Symbols::Variables::Type::BOOLEAN) {
            if (token.type == Lexer::Tokens::Type::KEYWORD && (token.value == "true" || token.value == "false")) {
                consumeToken();
                return Symbols::Value(token.value == "true");
            }
            reportError("Expected boolean literal value (true or false)");
        }
        // NUMERIC types
        if (expected_var_type == Symbols::Variables::Type::INTEGER ||
            expected_var_type == Symbols::Variables::Type::DOUBLE ||
            expected_var_type == Symbols::Variables::Type::FLOAT) {
            if (token.type == Lexer::Tokens::Type::NUMBER) {
                Symbols::Value val = parseNumericLiteral(token.value, is_negative, expected_var_type);
                consumeToken();
                return val;
            }
            reportError("Expected numeric literal value");
        }
        reportError("Unsupported variable type encountered during value parsing");
        return Symbols::Value();  // compiler happy
    }
    Symbols::Variables::Type parseType();
    Symbols::Value           parseValue(Symbols::Variables::Type expected_var_type);
    Symbols::Value parseNumericLiteral(const std::string & value, bool is_negative, Symbols::Variables::Type type);
    void parseFunctionBody(const Lexer::Tokens::Token & opening_brace, const std::string & function_name,
                           Symbols::Variables::Type return_type, const Symbols::FunctionParameterInfo & params);
    ParsedExpressionPtr parseParsedExpression(const Symbols::Variables::Type & expected_var_type);
};  // class Parser
test_scripts/object.vs
@@ -34,3 +34,12 @@
} else {
    printnl("Child1 is too young to go to school.");
}
for (string $key, auto $value : $person2) {
    if (typeof($value,"object") == false) {
        printnl("Key: ", $key, " Value: ", $value);
    }else {
        printnl("Key: ", $key, " is an object");
    }
}