A simple scripting language in C++
Ferenc Szontágh
2025-04-13 86904d513734134beffc29c6f4012d53a99f25c5
src/ScriptInterpreter.hpp
@@ -1,6 +1,7 @@
#ifndef SSCRIPTINTERPRETER_HPP
#define SSCRIPTINTERPRETER_HPP
#include <functional>
#include <map>
#include <memory>
#include <string>
#include <unordered_map>
@@ -10,90 +11,66 @@
#include "Token.hpp"
#include "Value.hpp"
using FunctionValidator = std::function<void(const std::vector<Token> &, size_t &)>;
using FunctionValidator =
    std::function<void(const std::vector<Token> &, size_t &, const std::unordered_map<std::string, Value> &)>;
using VariableContext = std::unordered_map<std::string, Value>;
class ScriptInterpreter {
  public:
    void registerFunction(const std::string & name, std::shared_ptr<BaseFunction> fn);
    void executeScript(const std::string & source, const std::string & filenaneame, bool debug = false);
    static void throwUnexpectedTokenError(const Token & token, const std::string & expected = "",
                                          const std::string & file = "", const int & line = 0) {
        const std::string error_content =
            "unexpected token: '" + token.lexeme + "' type: " + tokenTypeNames.at(token.type) +
            (expected.empty() ? "" : ", expected: '" + expected + "'") + " in file: " + token.file + ":" +
            std::to_string(token.lineNumber) + ":" + std::to_string(token.columnNumber);
#ifdef DEBUG_BUILD
        const std::string error_message = file + ":" + std::to_string(line) + "\n" + error_content;
#else
        const std::string& error_message = error_content;
#endif
        throw std::runtime_error(error_message);
    };
    static void throwUndefinedVariableError(const std::string & name, const Token & token, const std::string & file,
                                            const int & line = 0) {
        const std::string error_content = "undefined variable: '$" + name + "' in file: " + token.file + ":" +
                                          std::to_string(token.lineNumber) + ":" + std::to_string(token.columnNumber);
#ifdef DEBUG_BUILD
        const std::string error_message = file + ":" + std::to_string(line) + "\n" + error_content;
#else
        const std::string& error_message = error_content;
#endif
        throw std::runtime_error(error_message);
    }
    static void throwVariableTypeMissmatchError(const std::string & target_variable_name,
                                                const std::string & target_type,
                                                const std::string & source_variable_name,
                                                const std::string & source_type, const Token & token,
                                                const std::string & file = "", const int & line = 0) {
        std::string error_content =
            "variable type missmatch: '$" + target_variable_name + "' declared type: '" + target_type + "'";
        if (!source_variable_name.empty()) {
            error_content += " source variable: '" + source_variable_name + "'";
        }
        if (!source_type.empty()) {
            error_content += " assigned type: '" + source_type + "'";
        }
        error_content += " in file: " + token.file + ":" + std::to_string(token.lineNumber) + ":" +
                         std::to_string(token.columnNumber);
#ifdef DEBUG_BUILD
        const std::string error_message = file + ":" + std::to_string(line) + "\n" + error_content;
#else
        const std::string error_message = error_content;
#endif
        throw std::runtime_error(error_message);
    }
    static void throwVariableRedefinitionError(const std::string & name, const Token & token,
                                               const std::string & file = "", const int line = 0) {
        const std::string error_content = "variable alread defined: " + name + " in file: " + token.file + ":" +
                                          std::to_string(token.lineNumber) + ":" + std::to_string(token.columnNumber);
#ifdef DEBUG_BUILD
        const std::string error_message = file + ":" + std::to_string(line) + "\n" + error_content;
#else
        const std::string& error_message = error_content;
#endif
        throw std::runtime_error(error_message);
    }
    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);
  private:
    std::unordered_map<std::string, FunctionValidator>             functionValidators;
    std::unordered_map<std::string, std::shared_ptr<BaseFunction>> functionObjects;
    std::unordered_map<std::string, Value>                         variables;
    // store the current script's variables
    std::unordered_map<std::string, VariableContext>               variables;
    // store the current script's function arguments
    std::unordered_map<std::string, std::vector<Value>>            functionParameters;
    std::unordered_map<std::string, std::string>                   functionBodies;
    std::string                                                    filename;
    std::vector<Value> parseArguments(const std::vector<Token> & tokens, std::size_t & current_index) const;
    Value              evaluateExpression(const Token & token) const;
    [[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 expectSemicolon(const std::vector<Token> & tokens, std::size_t & i, const std::string & message) const;
    void setVariable(const std::string & name, const Value & value, const std::string & context = "default") {
        this->variables[context][name] = value;
    }
    [[nodiscard]] Value getVariable(const std::string & name, const std::string & context = "default") 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 it->second.at(name);
                }
            }
        }
        throw std::runtime_error("Variable not found: " + name);
    };
    [[nodiscard]] Value getVariable(const Token & token, const std::string & context = "default") const {
        for (auto it = variables.begin(); it != variables.end(); ++it) {
            if (it->first == context) {
                const auto & innerMap = it->second.find(token.lexeme);
                if (innerMap != it->second.end()) {
                    return it->second.at(token.lexeme);
                }
            }
        }
        THROW_UNDEFINED_VARIABLE_ERROR(token.lexeme, token);
    };
    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);
    void handleSemicolon(std::size_t & i);
    void handleStringDeclaration(const std::vector<Token> & tokens, std::size_t & i);
    void handleBooleanDeclaration(const std::vector<Token> & tokens, std::size_t & i);
    void handleNumberDeclaration(const std::vector<Token> & tokens, std::size_t & i, TokenType type);
    void handleFunctionDeclaration(const std::vector<Token> & tokens, std::size_t & i);
};
#endif  // SSCRIPTINTERPRETER_HPP