A simple scripting language in C++
Ferenc Szontágh
2025-04-13 cb3065c34756a70cb6006fc25777ce3e720ff1a8
implement variable contexts, add function body store and parsing
8 files modified
1 files added
359 ■■■■■ changed files
.clangd 2 ●●●●● patch | view | raw | blame | history
CMakeLists.txt 1 ●●●● patch | view | raw | blame | history
cli/main.cpp 2 ●●● patch | view | raw | blame | history
src/Lexer.cpp 158 ●●●●● patch | view | raw | blame | history
src/Lexer.hpp 13 ●●●●● patch | view | raw | blame | history
src/ScriptExceptionMacros.h 3 ●●●●● patch | view | raw | blame | history
src/ScriptInterpreter.cpp 102 ●●●● patch | view | raw | blame | history
src/ScriptInterpreter.hpp 55 ●●●●● patch | view | raw | blame | history
src/ScriptInterpreterHelpers.hpp 23 ●●●●● patch | view | raw | blame | history
.clangd
New file
@@ -0,0 +1,2 @@
CompileFlags:
  Add: [-Wunused]
CMakeLists.txt
@@ -57,6 +57,7 @@
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g")
    set(DEBUG_BUILD ON)
    set(COMPILE_WARNING_AS_ERROR ON)
endif()
configure_file("cmake/options.h.in" "include/options.h" @ONLY)
cli/main.cpp
@@ -53,7 +53,7 @@
        ScriptInterpreter interp;
        interp.registerModule("print", std::make_shared<PrintFunction>());
        interp.registerModule("sleep", std::make_shared<SleepFunction>());
        interp.executeScript(content, filename, DEBUG);
        interp.executeScript(content, filename, "_default_", false);
    } catch (const std::exception & e) {
        std::cerr << "Parser error: " << e.what() << "\n";
        return 1;
src/Lexer.cpp
@@ -35,31 +35,62 @@
    return pos >= src.size();
}
Token Lexer::string() {
Token Lexer::createToken(TokenType type, const std::string & lexeme) const {
    size_t startChar = charNumber - lexeme.length();
    return {
        type, lexeme, filename, lineNumber, colNumber - lexeme.length(), { startChar, charNumber }
    };
}
Token Lexer::createSingleCharToken(TokenType type, const std::string & lexeme) {
    size_t startCol  = colNumber;
    size_t startChar = charNumber;
    advance();
    return {
        type, lexeme, filename, lineNumber, startCol, { startChar, charNumber }
    };
}
Token Lexer::createUnknownToken(const std::string & lexeme) const {
    size_t startChar = charNumber - lexeme.length();
    return {
        TokenType::Unknown, lexeme, filename, lineNumber, colNumber - lexeme.length(), { startChar, charNumber }
    };
}
Token Lexer::stringToken() {
    std::string result;
    size_t      startChar = charNumber;
    size_t      startCol = colNumber;
    advance();  // Skip opening quote
    while (!isAtEnd() && peek() != '"') {
        result += advance();
    }
    if (isAtEnd() || peek() != '"') {
        return { TokenType::Unknown, "Unterminated string", filename, lineNumber, startCol };
        return {
            TokenType::Unknown, "Unterminated string", filename, lineNumber, startCol, { startChar, pos }
        };
    }
    advance();  // Skip closing quote
    return { TokenType::StringLiteral, result, filename, lineNumber, startCol };
    return {
        TokenType::StringLiteral, result, filename, lineNumber, startCol, { startChar, pos }
    };
}
Token Lexer::number() {
Token Lexer::numberToken() {
    std::string result;
    std::string found;
    TokenType   type             = TokenType::Unknown;
    bool        decimalPointSeen = false;
    size_t      startChar        = charNumber;
    size_t      startCol         = colNumber;
    while (std::isdigit(peek()) || peek() == '.') {
        if (peek() == '.') {
            if (decimalPointSeen) {
                return { TokenType::Unknown, "Invalid number format", filename, lineNumber, startCol };
                return {
                    TokenType::Unknown, "Invalid number format", filename, lineNumber, startCol, { startChar, pos }
                };
            }
            decimalPointSeen = true;
        }
@@ -72,33 +103,45 @@
                result = found;
                type   = TokenType::IntLiteral;
            } else {
                return { TokenType::Unknown, "Invalid integer", filename, lineNumber, startCol };
                return {
                    TokenType::Unknown, "Invalid integer", filename, lineNumber, startCol, { startChar, pos }
                };
            }
        } else {
            if (is_number<double>(found)) {
                result = found;
                type   = TokenType::DoubleLiteral;
            } else {
                return { TokenType::Unknown, "Invalid double", filename, lineNumber, startCol };
                return {
                    TokenType::Unknown, "Invalid double", filename, lineNumber, startCol, { startChar, pos }
                };
            }
        }
    } else {
        return { TokenType::Unknown, "Expected number", filename, lineNumber, startCol };
        return {
            TokenType::Unknown, "Expected number", filename, lineNumber, startCol, { startChar, pos }
        };
    }
    return { type, result, filename, lineNumber, startCol };
    return {
        type, result, filename, lineNumber, startCol, { startChar, pos }
    };
}
Token Lexer::identifier() {
Token Lexer::identifierToken() {
    std::string result;
    size_t      startChar = charNumber;
    size_t      startCol = colNumber;
    while (isalnum(peek()) || peek() == '_') {
        result += advance();
    }
    return { TokenType::Identifier, result, filename, lineNumber, startCol };
    return {
        TokenType::Identifier, result, filename, lineNumber, startCol, { startChar, pos }
    };
}
Token Lexer::variable() {
Token Lexer::variableToken() {
    size_t startChar = charNumber;
    size_t startCol = colNumber;
    advance();  // Skip $
    std::string varName;
@@ -107,24 +150,30 @@
        while (isalnum(peek()) || peek() == '_') {
            varName += advance();
        }
        return { TokenType::Variable, varName, filename, lineNumber, startCol };
        return {
            TokenType::Variable, varName, filename, lineNumber, startCol, { startChar, pos }
        };
    }
    return { TokenType::Unknown, "$ followed by invalid character", filename, lineNumber, startCol };
    return {
        TokenType::Unknown, "$ followed by invalid character", filename, lineNumber, startCol, { startChar, pos }
    };
}
Token Lexer::comment() {
Token Lexer::commentToken() {
    size_t startChar = charNumber;
    size_t startCol = colNumber;
    advance();  // Skip #
    std::string commentText;
    while (!isAtEnd() && peek() != '\n') {
        commentText += advance();
    }
    return { TokenType::Comment, commentText, filename, lineNumber, startCol };
    return {
        TokenType::Comment, commentText, filename, lineNumber, startCol, { startChar, pos }
    };
}
Token Lexer::keywordOrIdentifier() {
Token Lexer::keywordOrIdentifierToken() {
    std::string lexeme;
    size_t      startCol = colNumber;
    while (isalpha(peek())) {
        lexeme += advance();
    }
@@ -135,16 +184,14 @@
            advance();
        }
        if (peek() == '$') {
            return this->variableDeclaration(type);
            return this->variableDeclarationToken(type);
        }
        return { TokenType::Identifier, lexeme, filename, lineNumber, startCol };
        return createToken(TokenType::Identifier, lexeme);
    }
    return createToken(TokenType::Identifier, lexeme);
    }
    return { TokenType::Identifier, lexeme, filename, lineNumber, startCol };
}
Token Lexer::variableDeclaration(Variables::Type type) {
    size_t startCol = colNumber;
Token Lexer::variableDeclarationToken(Variables::Type type) {
    advance();  // Skip $
    std::string varName;
    if (isalpha(peek()) || peek() == '_') {
@@ -154,40 +201,29 @@
        }
        for (auto it = Variables::StringToTypeMap.begin(); it != Variables::StringToTypeMap.end(); ++it) {
            if (it->second == type) {
                return { getTokenTypeFromValueDeclaration(it->second), varName, filename, lineNumber, startCol };
                return createToken(getTokenTypeFromValueDeclaration(it->second), varName);
            }
        }
        return { TokenType::Unknown, "Invalid variable type in declaration", filename, lineNumber, startCol };
        return createUnknownToken("Invalid variable type in declaration");
    }
    return { TokenType::Unknown, "$ followed by invalid character in declaration", filename, lineNumber, startCol };
}
Token Lexer::singleCharToken(TokenType type, const std::string & lexeme) {
    size_t startCol = colNumber;
    advance();
    return { type, lexeme, filename, lineNumber, startCol };
    return createUnknownToken("$ followed by invalid character in declaration");
}
bool Lexer::matchSequence(const std::string & sequence, bool caseSensitive) const {
    if (this->pos + sequence.size() > src.size()) {
        return false;
    }
    for (size_t i = 0; i < sequence.size(); ++i) {
        char srcChar = src[this->pos + i];
        char seqChar = sequence[i];
        if (!caseSensitive) {
            srcChar = std::tolower(static_cast<unsigned char>(srcChar));
            seqChar = std::tolower(static_cast<unsigned char>(seqChar));
        }
        if (srcChar != seqChar) {
            return false;
        }
    }
    return true;
}
@@ -205,84 +241,84 @@
    while (pos < src.size()) {
        char c = src[pos];
        if (isspace(c)) {
            advance();
            continue;
        }
        if (c == '\n') {
            tokens.push_back(singleCharToken(TokenType::EndOfLine, "\n"));
            tokens.push_back(createSingleCharToken(TokenType::EndOfLine, "\n"));
            continue;
        }
        if (c == COMMENT_CHARACTER) {
            tokens.push_back(comment());
            tokens.push_back(commentToken());
            advance();  // Skip newline after comment
            continue;
        }
        if (matchSequence(PARSER_OPEN_TAG)) {
            size_t startCol = colNumber;
            matchAndConsume(PARSER_OPEN_TAG);
            tokens.push_back({ TokenType::ParserOpenTag, PARSER_OPEN_TAG, filename, lineNumber, startCol });
            tokens.push_back(createToken(TokenType::ParserOpenTag, PARSER_OPEN_TAG));
            continue;
        }
        if (matchSequence(PARSER_CLOSE_TAG)) {
            size_t startCol = colNumber;
            matchAndConsume(PARSER_CLOSE_TAG);
            tokens.push_back({ TokenType::ParserCloseTag, PARSER_CLOSE_TAG, filename, lineNumber, startCol });
            tokens.push_back(createToken(TokenType::ParserCloseTag, PARSER_CLOSE_TAG));
            continue;
        }
        if (matchSequence("if")) {
            size_t startCol = colNumber;
            matchAndConsume("if");
            tokens.push_back({ TokenType::ParserIfStatement, "if", filename, lineNumber, startCol });
            tokens.push_back(createToken(TokenType::ParserIfStatement, "if"));
            continue;
        }
        switch (c) {
            case 'a' ... 'z':
            case 'A' ... 'Z':
                tokens.push_back(keywordOrIdentifier());
                tokens.push_back(keywordOrIdentifierToken());
                break;
            case '$':
                tokens.push_back(variable());
                tokens.push_back(variableToken());
                break;
            case '0' ... '9':
                tokens.push_back(number());
                tokens.push_back(numberToken());
                break;
            case '"':
            case '\'':
                tokens.push_back(string());
                tokens.push_back(stringToken());
                break;
            case '(':
                tokens.push_back(singleCharToken(TokenType::LeftParenthesis, "("));
                tokens.push_back(createSingleCharToken(TokenType::LeftParenthesis, "("));
                break;
            case ')':
                tokens.push_back(singleCharToken(TokenType::RightParenthesis, ")"));
                tokens.push_back(createSingleCharToken(TokenType::RightParenthesis, ")"));
                break;
            case ',':
                tokens.push_back(singleCharToken(TokenType::Comma, ","));
                tokens.push_back(createSingleCharToken(TokenType::Comma, ","));
                break;
            case ';':
                tokens.push_back(singleCharToken(TokenType::Semicolon, ";"));
                tokens.push_back(createSingleCharToken(TokenType::Semicolon, ";"));
                break;
            case '=':
                tokens.push_back(singleCharToken(TokenType::Equals, "="));
                tokens.push_back(createSingleCharToken(TokenType::Equals, "="));
                break;
            case '+':
                tokens.push_back(singleCharToken(TokenType::Plus, "+"));
                tokens.push_back(createSingleCharToken(TokenType::Plus, "+"));
                break;
            case '{':
                tokens.push_back(singleCharToken(TokenType::LeftCurlyBracket, "{"));
                tokens.push_back(createSingleCharToken(TokenType::LeftCurlyBracket, "{"));
                break;
            case '}':
                tokens.push_back(singleCharToken(TokenType::RightCurlyBracket, "}"));
                tokens.push_back(createSingleCharToken(TokenType::RightCurlyBracket, "}"));
                break;
            default:
                tokens.push_back({ TokenType::Unknown, std::string(1, c), filename, lineNumber, colNumber });
                tokens.push_back(createUnknownToken(std::string(1, c)));
                advance();
                break;
        }
    }
    tokens.push_back({ TokenType::EndOfFile, "", filename, lineNumber, colNumber });
    tokens.push_back({
        TokenType::EndOfFile, "", filename, lineNumber, colNumber, { charNumber, charNumber }
    });
    return tokens;
}
src/Lexer.hpp
@@ -38,6 +38,19 @@
    Token variableDeclaration(Variables::Type type);
    void  matchAndConsume(const std::string & sequence, bool caseSensitive = true);
    // create token methods
    Token createToken(TokenType type, const std::string & lexeme) const;
    Token createSingleCharToken(TokenType type, const std::string & lexeme);
    Token createUnknownToken(const std::string & lexeme) const;
    Token createErrorToken(const std::string & lexeme) const;
    Token stringToken();
    Token numberToken();
    Token identifierToken();
    Token variableToken();
    Token commentToken();
    Token keywordOrIdentifierToken();
    Token variableDeclarationToken(Variables::Type type);
    // validate number types from string
    template <typename Numeric> static bool is_number(const std::string & s) {
        Numeric n;
src/ScriptExceptionMacros.h
@@ -18,6 +18,9 @@
#define THROW_UNDEFINED_VARIABLE_ERROR(name, token) \
    throw ScriptException::makeUndefinedVariableError(name, token, __FILE__, __LINE__)
#define THROW_UNDEFINED_VARIABLE_ERROR_HELPER(name, token, file, line) \
    throw ScriptException::makeUndefinedVariableError(name, token, file, line)
// Unknown (undefined) function call
#define THROW_UNDEFINED_FUNCTION_ERROR(name, token) \
    throw ScriptException::makeUndefinedFunctionError(name, token, __FILE__, __LINE__)
src/ScriptInterpreter.cpp
@@ -35,8 +35,14 @@
            throw std::runtime_error("Double literal out of range: " + token.lexeme);
        }
    }
    if (token.type == TokenType::BooleanLiteral || token.type == TokenType::Identifier) {
        std::string lowered = token.lexeme;
        std::transform(lowered.begin(), lowered.end(), lowered.begin(),
                       [](unsigned char c) { return std::tolower(c); });
        return Value::fromBoolean(token, (lowered == "true" ? true : false));
    }
    if (token.type == TokenType::Variable) {
        return this->getVariable(token);
        return this->getVariable(token, this->contextPrefix, __FILE__, __LINE__);
    }
    THROW_UNEXPECTED_TOKEN_ERROR(token, "string, integer, double, or variable");
@@ -81,67 +87,79 @@
}
void ScriptInterpreter::handleBooleanDeclaration(const std::vector<Token> & tokens, std::size_t & i) {
    const auto varName = tokens[i].lexeme;
    const auto varType = tokens[i].variableType;
    const auto & varToken = tokens[i];
    i++;      // Skip variable name
    if (i < tokens.size() && tokens[i].type == TokenType::Equals) {
        i++;  // Skip '='
        if (i < tokens.size() && tokens[i].type == TokenType::Variable) {
            const auto variable = this->getVariable(tokens[i]);
            const auto variable = this->getVariable(tokens[i], this->contextPrefix, __FILE__, __LINE__);
            if (variable.type != Variables::Type::VT_BOOLEAN) {
                THROW_VARIABLE_TYPE_MISSMATCH_ERROR(varName, Variables::TypeToString(Variables::Type::VT_BOOLEAN),
                THROW_VARIABLE_TYPE_MISSMATCH_ERROR(varToken.lexeme,
                                                    Variables::TypeToString(Variables::Type::VT_BOOLEAN),
                                                    tokens[i].lexeme, variable.TypeToString(), tokens[i]);
            }
            this->setVariable(varName, variable);
            this->setVariable(varToken.lexeme, variable, this->contextPrefix, true);
            i++;  // Skip variable name
            EXPECT_SEMICOLON(tokens, i, "after bool variable declaration");
        } else if (i < tokens.size() && tokens[i].type == TokenType::Identifier) {
        } else if (i < tokens.size() &&
                   (tokens[i].type == TokenType::Identifier || tokens[i].type == TokenType::StringLiteral)) {
            std::string lowered = tokens[i].lexeme;
            std::transform(lowered.begin(), lowered.end(), lowered.begin(),
                           [](unsigned char c) { return std::tolower(c); });
            if (lowered == "true") {
                this->setVariable(varName, Value::fromBoolean(tokens[i], true));
                this->setVariable(varToken.lexeme, Value::fromBoolean(tokens[i], true), this->contextPrefix, true);
            } else if (lowered == "false") {
                this->setVariable(varName, Value::fromBoolean(tokens[i], false));
                this->setVariable(varToken.lexeme, Value::fromBoolean(tokens[i], false), this->contextPrefix, true);
            } else {
                THROW_UNEXPECTED_TOKEN_ERROR(tokens[i], "true or false after '='");
            }
            i++;  // Skip boolean literal
            EXPECT_SEMICOLON(tokens, i, "after bool declaration");
        } else if (i < tokens.size() && tokens[i].type == TokenType::IntLiteral) {
            const auto test = std::stoi(tokens[i].lexeme);
            if (test == 0) {
                this->setVariable(varToken.lexeme, Value::fromBoolean(tokens[i], false), this->contextPrefix, true);
            } else if (test > 0) {
                this->setVariable(varToken.lexeme, Value::fromBoolean(tokens[i], true), this->contextPrefix, true);
        } else {
            THROW_UNEXPECTED_TOKEN_ERROR(tokens[i], "bool literal after '='");
        }
            i++;
            EXPECT_SEMICOLON(tokens, i, "after bool declaration");
        } else {
            THROW_UNEXPECTED_TOKEN_ERROR(tokens[i], "bool literal after '='");
        }
    } else {
        THROW_UNEXPECTED_TOKEN_ERROR(tokens[i], "= after bool declaration");
    }
};
void ScriptInterpreter::handleStringDeclaration(const std::vector<Token> & tokens, std::size_t & i) {
    const auto varName = tokens[i].lexeme;
    const auto varType = tokens[i].variableType;
    const auto varToken = tokens[i];
    i++;      // Skip variable name
    if (i < tokens.size() && tokens[i].type == TokenType::Equals) {
        i++;  // Skip '='
        if (i < tokens.size() && tokens[i].type == TokenType::Variable) {
            const auto variable = this->getVariable(tokens[i]);
            const auto variable = this->getVariable(tokens[i], this->contextPrefix, __FILE__, __LINE__);
            if (variable.type != Variables::Type::VT_STRING) {
                THROW_VARIABLE_TYPE_MISSMATCH_ERROR(varName, Variables::TypeToString(Variables::Type::VT_STRING),
                THROW_VARIABLE_TYPE_MISSMATCH_ERROR(varToken.lexeme,
                                                    Variables::TypeToString(Variables::Type::VT_STRING),
                                                    tokens[i].lexeme, variable.TypeToString(), tokens[i]);
            }
            this->setVariable(varName, variable);
            //variables[varName] = variables[tokens[i].lexeme];
            this->setVariable(varToken.lexeme, variable, this->contextPrefix, true);
            i++;  // Skip variable name
            EXPECT_SEMICOLON(tokens, i, "after string variable declaration");
        } else if (i < tokens.size() && tokens[i].type == TokenType::StringLiteral) {
            this->setVariable(varName, Value::fromString(tokens[i]));
            this->setVariable(varToken.lexeme, Value::fromString(tokens[i]), this->contextPrefix, true);
            i++;  // Skip string literal
            EXPECT_SEMICOLON(tokens, i, "after string declaration");
        } else {
@@ -153,8 +171,7 @@
}
void ScriptInterpreter::handleNumberDeclaration(const std::vector<Token> & tokens, std::size_t & i, TokenType type) {
    const auto varName = tokens[i].lexeme;
    const auto varType = tokens[i].variableType;
    const auto & varToken = tokens[i];
    i++;      // Skip variable name
    if (i < tokens.size() && tokens[i].type == TokenType::Equals) {
@@ -162,12 +179,7 @@
        if (i < tokens.size()) {
            if (type == TokenType::IntDeclaration && tokens[i].type == TokenType::IntLiteral) {
                try {
                    const auto variable = this->getVariable(tokens[i]);
                    if (variable.type != Variables::Type::VT_INT) {
                        THROW_VARIABLE_TYPE_MISSMATCH_ERROR(varName, Variables::TypeToString(Variables::Type::VT_INT),
                                                            tokens[i].lexeme, variable.TypeToString(), tokens[i]);
                    }
                    this->setVariable(varName, Value::fromInt(tokens[i]));
                    this->setVariable(varToken.lexeme, Value::fromInt(tokens[i]), this->contextPrefix, true);
                    i++;  // Skip int literal
                } catch (const std::invalid_argument & e) {
                    throw std::runtime_error("Invalid integer literal in declaration: " + tokens[i].lexeme);
@@ -176,13 +188,7 @@
                }
            } else if (type == TokenType::DoubleDeclaration && tokens[i].type == TokenType::DoubleLiteral) {
                try {
                    const auto variable = this->getVariable(tokens[i]);
                    if (variable.type != Variables::Type::VT_DOUBLE) {
                        THROW_VARIABLE_TYPE_MISSMATCH_ERROR(varName,
                                                            Variables::TypeToString(Variables::Type::VT_DOUBLE),
                                                            tokens[i].lexeme, variable.TypeToString(), tokens[i]);
                    }
                    this->setVariable(varName, Value::fromDouble(tokens[i]));
                    this->setVariable(varToken.lexeme, Value::fromDouble(tokens[i]), this->contextPrefix, true);
                    i++;  // Skip double literal
                } catch (const std::invalid_argument & e) {
                    throw std::runtime_error("Invalid double literal in declaration: " + tokens[i].lexeme);
@@ -191,7 +197,7 @@
                }
            } else {
                const std::string expectedType = type == TokenType::IntDeclaration ? "int" : "double";
                THROW_VARIABLE_TYPE_MISSMATCH_ERROR(varName, expectedType, "",
                THROW_VARIABLE_TYPE_MISSMATCH_ERROR(varToken.lexeme, expectedType, "",
                                                    getVariableTypeFromTokenTypeAsString(tokens[i].type), tokens[i]);
            }
            EXPECT_SEMICOLON(tokens, i, "after variable declaration");
@@ -199,7 +205,7 @@
            THROW_UNEXPECTED_TOKEN_ERROR(tokens[i - 1], "literal after '='");
        }
    } else {
        THROW_UNEXPECTED_TOKEN_ERROR(tokens[i], "= after variable declaration, variable name: " + varName);
        THROW_UNEXPECTED_TOKEN_ERROR(tokens[i], "= after variable declaration, variable name: " + varToken.lexeme);
    }
}
@@ -224,24 +230,26 @@
    i++;
    // parse arg definitions
    const auto args = ScriptInterpreterHelpers::parseFunctionDeclarationArguments(tokens, i);
    const auto args = ScriptInterpreterHelpers::parseFunctionDeclarationArguments(tokens, i, __FILE__, __LINE__);
    std::cout << "args: " << args.size() << std::endl;
    for (const auto & arg : args) {
        std::cout << "arg name: " << arg.GetToken().lexeme << " type: " << arg.TypeToString() << std::endl;
    }
    this->functionParameters[varName].assign(args.begin(), args.end());
    const std::string body        = ScriptInterpreterHelpers::getFunctionBody(tokens, i);
    this->functionBodies[varName] = body;
    size_t start;
    size_t end;
    ScriptInterpreterHelpers::getFunctionBody(tokens, i, start, end);
    std::cout << "Body start:  " << start << " end: " << end << std::endl;
    const std::string function_body = ScriptInterpreterHelpers::extractSubstring(this->source, start, end);
    this->functionBodies[varName]   = function_body;
    // recheck the close curly brace
    if (i >= tokens.size() || tokens[i].type != TokenType::RightCurlyBracket) {
        THROW_UNEXPECTED_TOKEN_ERROR(tokens[i], "}");
    }
    i++;
    //this->functionBodies[varName] = std::make_shared<ScriptInterpreter>();
    //this->functionBodies[varName]->executeScript(body, this->filename, true);
    //EXPECT_SEMICOLON(tokens, i, "after function declaration");
    // there is no need semicolon to the end of the function declaration
    std::cout << "function body: \n\"" << body << "\"" << std::endl;
#if DEBUG_BUILD == 1
    std::cout << "function body: \n\"" << function_body << "\"" << std::endl;
#endif
}
void ScriptInterpreter::handleFunctionCall(const std::vector<Token> & tokens, std::size_t & i) {
@@ -272,16 +280,13 @@
}
void ScriptInterpreter::handleVariableReference(const std::vector<Token> & tokens, std::size_t & i) {
    //THROW_UNEXPECTED_TOKEN_ERROR(tokens[i], "function call or variable assignment (not yet implemented)");
//    const auto varName = tokens[i].lexeme;
//    const auto varType = tokens[i].variableType;
    const auto& varToken = tokens[i];
    i++;      // Skip variable token to avoid infinite loop
    if (i < tokens.size() && tokens[i].type == TokenType::Equals) {
        i++;  // Skip '='
        if (i < tokens.size()) {
            const auto variable = this->getVariable(varToken);
            this->setVariable(varToken.lexeme, evaluateExpression(tokens[i]));
            const auto variable = this->getVariable(varToken, this->contextPrefix, __FILE__, __LINE__);
            this->setVariable(varToken.lexeme, evaluateExpression(tokens[i]), this->contextPrefix, false);
            i++;  // Skip value
            EXPECT_SEMICOLON(tokens, i, "after variable assignment");
        } else {
@@ -300,8 +305,11 @@
    i++;  // Skip semicolon token
}
void ScriptInterpreter::executeScript(const std::string & source, const std::string & filename, bool ignore_tags) {
void ScriptInterpreter::executeScript(const std::string & source, const std::string & filename,
                                      const std::string & _namespace, bool ignore_tags) {
    this->filename = filename;
    this->source        = source;
    this->contextPrefix = filename + _namespace;
    Lexer lexer(source, filename);
    auto  tokens = lexer.tokenize();
src/ScriptInterpreter.hpp
@@ -1,7 +1,6 @@
#ifndef SSCRIPTINTERPRETER_HPP
#define SSCRIPTINTERPRETER_HPP
#include <functional>
#include <map>
#include <memory>
#include <string>
#include <unordered_map>
@@ -18,7 +17,8 @@
class ScriptInterpreter {
  public:
    void registerModule(const std::string & name, std::shared_ptr<BaseFunction> fn);
    void executeScript(const std::string & source, const std::string & filenaneame, bool ignore_tags = false);
    void executeScript(const std::string & source, const std::string & filename,
                       const std::string & _namespace = "_default_", bool ignore_tags = false);
  private:
    std::unordered_map<std::string, FunctionValidator>             functionValidators;
@@ -29,13 +29,19 @@
    std::unordered_map<std::string, std::vector<Value>>            functionParameters;
    std::unordered_map<std::string, std::string>                   functionBodies;
    std::string                                                    filename;
    std::string                                                    source;
    std::string                                                    contextPrefix;
    [[nodiscard]] std::vector<Value> parseFunctionArguments(const std::vector<Token> & tokens,
                                                            std::size_t &              index) const;
    [[nodiscard]] Value              evaluateExpression(const Token & token) const;
    // type handlers
    void setVariable(const std::string & name, const Value & value, const std::string & context = "default") {
    void setVariable(const std::string & name, const Value & value, const std::string & context = "default",
                     bool exception_if_exists = false) {
        if (exception_if_exists && variables[context].find(name) != variables[context].end()) {
            THROW_VARIABLE_REDEFINITION_ERROR(name, value.token);
        }
        this->variables[context][name] = value;
    }
@@ -51,7 +57,8 @@
        throw std::runtime_error("Variable not found: " + name);
    };
    [[nodiscard]] Value getVariable(const Token & token, const std::string & context = "default") const {
    [[nodiscard]] Value getVariable(const Token & token, const std::string & context = "default",
                                    const std::string & file = __FILE__, const int & line = __LINE__) const {
        for (auto it = variables.begin(); it != variables.end(); ++it) {
            if (it->first == context) {
                const auto & innerMap = it->second.find(token.lexeme);
@@ -60,9 +67,47 @@
                }
            }
        }
        THROW_UNDEFINED_VARIABLE_ERROR(token.lexeme, token);
        THROW_UNDEFINED_VARIABLE_ERROR_HELPER(token.lexeme, token, file, line);
    };
    /**
         * Checks if a variable exists within the specified context.
         *
         * @param name The name of the variable to check.
         * @param context The context in which to search for the variable (defaults to "default").
         * @param file The source file where the check is performed (for error reporting).
         * @param line The line number where the check is performed (for error reporting).
         * @return True if the variable exists in the specified context.
         * @throws Throws an undefined variable error if the variable is not found.
         */
    [[nodiscard]] bool variableExists(const std::string & name, const std::string & context = "default",
                                      const std::string & file = __FILE__, const int & line = __LINE__) const {
        for (auto it = variables.begin(); it != variables.end(); ++it) {
            if (it->first == context) {
                const auto & innerMap = it->second.find(name);
                if (innerMap != it->second.end()) {
                    return true;
                }
            }
        }
        THROW_UNDEFINED_VARIABLE_ERROR_HELPER(name, Token(TokenType::Variable, name, name, 0, 0), file, line);
    }
    /**
     * Checks if a variable exists within the specified context using a Token.
     *
     * @param token The token representing the variable to check.
     * @param context The context in which to search for the variable (defaults to "default").
     * @param file The source file where the check is performed (for error reporting).
     * @param line The line number where the check is performed (for error reporting).
     * @return True if the variable exists in the specified context.
     * @throws Throws an undefined variable error if the variable is not found.
     */
    [[nodiscard]] bool variableExists(const Token & token, const std::string & context = "default",
                                      const std::string & file = __FILE__, const int & line = __LINE__) const {
        return this->variableExists(token.lexeme, context, file, line);
    }
    void handleFunctionCall(const std::vector<Token> & tokens, std::size_t & i);
    void handleVariableReference(const std::vector<Token> & tokens, std::size_t & i);
    void handleComment(std::size_t & i);
src/ScriptInterpreterHelpers.hpp
@@ -14,7 +14,7 @@
namespace ScriptInterpreterHelpers {
static std::string extractSubstring(const std::string & str, size_t start, size_t end) {
static std::string extractSubstring(const std::string & str, const size_t & start, const size_t & end) {
    if (start >= 0 && start < str.length() && end >= start && end < str.length()) {
        return str.substr(start, end - start + 1);
    }
@@ -37,9 +37,11 @@
    // check the arguments types
    if (tokens[i].type != TokenType::StringDeclaration && tokens[i].type != TokenType::BooleanDeclaration &&
        tokens[i].type != TokenType::IntDeclaration && tokens[i].type != TokenType::DoubleDeclaration) {
        tokens[i].type != TokenType::IntDeclaration && tokens[i].type != TokenType::DoubleDeclaration &&
        tokens[i].type != TokenType::RightParenthesis) {
        THROW_UNEXPECTED_TOKEN_ERROR_HELPER(tokens[i], "variable declaration", file, line);
    }
    if (tokens[i].type != TokenType::RightParenthesis) {
    const auto parameter_type = getVariableTypeFromTokenTypeDeclaration(tokens[i].type);
    if (parameter_type == Variables::Type::VT_NOT_DEFINED) {
        THROW_UNEXPECTED_TOKEN_ERROR_HELPER(tokens[i], "valid type identifier", file, line);
@@ -59,6 +61,7 @@
    arguments.emplace_back(std::move(val));
    i++;  // Skip variable declaration
    }
    if (tokens[i].type != TokenType::RightParenthesis) {
        THROW_UNEXPECTED_TOKEN_ERROR_HELPER(tokens[i], ") - Only one argument is allowed", file, line);
@@ -68,17 +71,19 @@
    return arguments;
}
[[nodiscard]] static std::string getFunctionBody(const std::vector<Token> & tokens, std::size_t & i) {
    const size_t first_index = i;
static void getFunctionBody(const std::vector<Token> & tokens, std::size_t & i, std::size_t & start,
                            std::size_t & end) {
    start = tokens[i].pos.end;
    std::cout << "START Token: " << tokens[i].lexeme << " start pos: " << std::to_string(tokens[i].pos.start)
              << " end pos: " << std::to_string(tokens[i].pos.end) << std::endl;
    if (i >= tokens.size() || tokens[i].type != TokenType::LeftCurlyBracket) {
        THROW_UNEXPECTED_TOKEN_ERROR(tokens[i], "{");
    }
    i++;  // Skip '{'
    std::string lines;
    while (i < tokens.size() && tokens[i].type != TokenType::RightCurlyBracket) {
        if (tokens[i].type == TokenType::EndOfLine) {
            lines += "\n";
            i++;
            continue;
        }
@@ -86,14 +91,16 @@
            throw std::runtime_error("Unexpected end of file");
            break;
        }
        lines += tokens[i].lexeme + " ";
        i++;
    }
    end = tokens[i].pos.start - 1;
    std::cout << "END Token: " << tokens[i].lexeme << " start pos: " << std::to_string(tokens[i].pos.start)
              << " end pos: " << std::to_string(tokens[i].pos.end) << std::endl;
    if (i >= tokens.size() || tokens[i].type != TokenType::RightCurlyBracket) {
        THROW_UNEXPECTED_TOKEN_ERROR(tokens[i], "}");
    }
    return lines;
};
};  // namespace ScriptInterpreterHelpers