A simple scripting language in C++
Ferenc Szontágh
2025-04-19 558e0191ba5a5b0ab99825de7d7d2219387e559e
constats variable type implementation
6 files modified
2 files added
157 ■■■■■ changed files
src/Interpreter/AssignmentStatementNode.hpp 10 ●●●● patch | view | raw | blame | history
src/Interpreter/DeclareVariableStatementNode.hpp 17 ●●●● patch | view | raw | blame | history
src/Interpreter/IdentifierExpressionNode.hpp 14 ●●●● patch | view | raw | blame | history
src/Interpreter/OperationsFactory.hpp 14 ●●●●● patch | view | raw | blame | history
src/Parser/Parser.cpp 63 ●●●●● patch | view | raw | blame | history
src/Parser/Parser.hpp 3 ●●●●● patch | view | raw | blame | history
test_scripts/constants.vs 19 ●●●●● patch | view | raw | blame | history
test_scripts/constants_object.vs 17 ●●●●● patch | view | raw | blame | history
src/Interpreter/AssignmentStatementNode.hpp
@@ -35,8 +35,14 @@
        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";
        const std::string base_ns   = symContainer->currentScopeName();
        const std::string var_ns    = base_ns + ".variables";
        const std::string const_ns  = base_ns + ".constants";
        // Prevent assignment to constants
        if (symContainer->exists(targetName_, const_ns)) {
            throw Exception(
                "Cannot assign to constant '" + targetName_ + "'", filename_, line_, column_);
        }
        if (!symContainer->exists(targetName_, var_ns)) {
            throw Exception(
                "Variable '" + targetName_ + "' does not exist in namespace: " + var_ns,
src/Interpreter/DeclareVariableStatementNode.hpp
@@ -20,17 +20,20 @@
    Symbols::Variables::Type        variableType_;
    std::unique_ptr<ExpressionNode> expression_;
    std::string                     ns;
    bool                            isConst_;
  public:
    // isConst: if true, declares a constant; otherwise a mutable variable
    DeclareVariableStatementNode(std::string name, const std::string & ns, Symbols::Variables::Type type,
                                 std::unique_ptr<ExpressionNode> expr, const std::string & file_name, int file_line,
                                 size_t line_column) :
                                 std::unique_ptr<ExpressionNode> expr, const std::string & file_name,
                                 int file_line, size_t line_column, bool isConst = false) :
        StatementNode(file_name, file_line, line_column),
        variableName_(std::move(name)),
        variableType_(type),
        expression_(std::move(expr)),
        ns(ns) {}
        ns(ns),
        isConst_(isConst) {}
    void interpret(Interpreter & interpreter) const override {
        try {
@@ -47,7 +50,13 @@
                    "': expected '" + expected + "' but got '" + actual + "'",
                    filename_, line_, column_);
            }
            const auto variable = Symbols::SymbolFactory::createVariable(variableName_, value, ns, variableType_);
            // Create a constant or variable symbol
            std::shared_ptr<Symbols::Symbol> variable;
            if (isConst_) {
                variable = Symbols::SymbolFactory::createConstant(variableName_, value, ns);
            } else {
                variable = Symbols::SymbolFactory::createVariable(variableName_, value, ns, variableType_);
            }
            Symbols::SymbolContainer::instance()->add(variable);
        } catch (const Exception &) {
            throw;
src/Interpreter/IdentifierExpressionNode.hpp
@@ -14,11 +14,17 @@
    explicit IdentifierExpressionNode(std::string name) : name_(std::move(name)) {}
    Symbols::Value evaluate(Interpreter & /*interpreter*/) const override {
        const auto ns = Symbols::SymbolContainer::instance()->currentScopeName() + ".variables";
        if (Symbols::SymbolContainer::instance()->exists(name_, ns)) {
            return Symbols::SymbolContainer::instance()->get(ns, name_)->getValue();
        auto * sc = Symbols::SymbolContainer::instance();
        const std::string base_ns  = sc->currentScopeName();
        const std::string var_ns   = base_ns + ".variables";
        if (sc->exists(name_, var_ns)) {
            return sc->get(var_ns, name_)->getValue();
        }
        throw std::runtime_error("Variable " + name_ + " does not exist in ns: " + ns);
        const std::string const_ns = base_ns + ".constants";
        if (sc->exists(name_, const_ns)) {
            return sc->get(const_ns, name_)->getValue();
        }
        throw std::runtime_error("Identifier '" + name_ + "' not found in namespace: " + base_ns);
    }
    std::string toString() const override { return name_; }
src/Interpreter/OperationsFactory.hpp
@@ -54,6 +54,20 @@
                                              Operations::Container::instance()->add(
                                                  ns, Operations::Operation{Operations::Type::Declaration, varName, std::move(stmt)});
                                          }
    /**
     * @brief Record a constant declaration operation with an initializer expression.
     */
    static void defineConstantWithExpression(const std::string & varName, Symbols::Variables::Type type,
                                             const Parser::ParsedExpressionPtr pexpr, const std::string & ns,
                                             const std::string & filename, int line, size_t column) {
        // Build initializer expression
        std::unique_ptr<ExpressionNode> expr = buildExpressionFromParsed(pexpr);
        // Create declaration node with const flag
        std::unique_ptr<DeclareVariableStatementNode> stmt = std::make_unique<DeclareVariableStatementNode>(
            varName, ns, type, std::move(expr), filename, line, column, /* isConst */ true);
        Operations::Container::instance()->add(
            ns, Operations::Operation{Operations::Type::Declaration, varName, std::move(stmt)});
    }
    
    /**
     * @brief Record a function call operation with argument expressions.
src/Parser/Parser.cpp
@@ -53,6 +53,35 @@
    { Lexer::Tokens::Type::KEYWORD_OBJECT,  Symbols::Variables::Type::OBJECT    },
};
// Parse a top-level constant variable definition: const <type> $name = expr;
void Parser::parseConstVariableDefinition() {
    // 'const'
    auto constTok = expect(Lexer::Tokens::Type::KEYWORD, "const");
    // Parse type
    Symbols::Variables::Type var_type = parseType();
    // Variable name
    Lexer::Tokens::Token id_token;
    if (currentToken().type == Lexer::Tokens::Type::VARIABLE_IDENTIFIER ||
        currentToken().type == Lexer::Tokens::Type::IDENTIFIER) {
        id_token = consumeToken();
    } else {
        reportError("Expected variable name after 'const'", currentToken());
    }
    std::string var_name = id_token.value;
    if (!var_name.empty() && var_name[0] == '$') {
        var_name = var_name.substr(1);
    }
    const auto ns = Symbols::SymbolContainer::instance()->currentScopeName();
    // Expect assignment
    expect(Lexer::Tokens::Type::OPERATOR_ASSIGNMENT, "=");
    // Parse initializer expression
    auto expr = parseParsedExpression(var_type);
    // Record constant definition
    Interpreter::OperationsFactory::defineConstantWithExpression(
        var_name, var_type, std::move(expr), ns, current_filename_, id_token.line_number, id_token.column_number);
    expect(Lexer::Tokens::Type::PUNCTUATION, ";");
}
void Parser::parseVariableDefinition() {
    Symbols::Variables::Type var_type = parseType();
@@ -365,11 +394,30 @@
        return std::make_unique<Interpreter::CallStatementNode>(funcName, std::move(exprs), this->current_filename_,
                                                                idTok.line_number, idTok.column_number);
    }
    // Constant variable declaration in blocks
    if (currentToken().type == Lexer::Tokens::Type::KEYWORD && currentToken().value == "const") {
        // 'const'
        auto constTok = expect(Lexer::Tokens::Type::KEYWORD, "const");
        // Parse type
        Symbols::Variables::Type type = parseType();
        // Variable name
        auto idTok = expect(Lexer::Tokens::Type::VARIABLE_IDENTIFIER);
        std::string name = idTok.value;
        if (!name.empty() && name[0] == '$') name = name.substr(1);
        // Assignment
        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, /* isConst */ true);
    }
    // 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;
        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);
        }
@@ -381,9 +429,13 @@
            name, Symbols::SymbolContainer::instance()->currentScopeName(), type, std::move(exprNode),
            this->current_filename_, idTok.line_number, idTok.column_number);
    }
    // Unexpected token in block
    reportError("Unexpected token in block");
    return nullptr;
}
// End of parseStatementNode
// Parse next definition or statement
// (Subsequent methods follow)
void Parser::parseFunctionDefinition() {
    expect(Lexer::Tokens::Type::KEYWORD_FUNCTION_DECLARATION);
@@ -1015,6 +1067,11 @@
        return;
    }
    // Constant variable definition
    if (token_type == Lexer::Tokens::Type::KEYWORD && currentToken().value == "const") {
        parseConstVariableDefinition();
        return;
    }
    // Variable definition if leading token matches a type keyword
    if (Parser::variable_types.find(token_type) != Parser::variable_types.end()) {
        parseVariableDefinition();
src/Parser/Parser.hpp
@@ -100,6 +100,9 @@
    // parseStatement (updated to handle return)
    void                                        parseStatement();
    // Parse a top-level constant variable definition (e.g., const <type> $name = expr;)
    void                                        parseConstVariableDefinition();
    // Parse a top-level variable definition (e.g., <type> $name = expr;)
    void                                        parseVariableDefinition();
    void                                        parseFunctionDefinition();
    // Parse a top-level function call statement (e.g., foo(arg1, arg2);)
test_scripts/constants.vs
New file
@@ -0,0 +1,19 @@
# Constants Feature Test
# Test declaration of immutable constants and verify re-assignment errors
# Declare constant string and print
const string $name = "Alice";
printnl($name);
# Declare constant integer and print
const int $x = 100;
printnl($x);
# Declare mutable variable and modify
string $y = "mutable";
printnl($y);
$y = "changed";
printnl($y);
# Attempt to modify constant (should produce runtime error)
$name = "Bob";
test_scripts/constants_object.vs
New file
@@ -0,0 +1,17 @@
# Constants Object Feature Test
# Test declaration of immutable object constants and verify property modification errors
# Declare constant object and print its properties
const object $person = {
    string name: "Bruce Wayne",
    int age: 42,
    object address: {
        string city: "Gotham",
        int zip: 12345
    }
};
printnl($person->name, " is ", $person->age, " years old.");
printnl("City: ", $person->address->city, ", ZIP: ", $person->address->zip);
# Attempt to modify a property of the constant object (should produce runtime error)
$person->age = 43;