A simple scripting language in C++
Ferenc Szontágh
2025-04-14 c1a905b5020c4f2f4ade85577e0c36811be87de4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
#ifndef SSCRIPTINTERPRETER_HPP
#define SSCRIPTINTERPRETER_HPP
#include <functional>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
 
#include "BaseFunction.hpp"
#include "Token.hpp"
#include "Value.hpp"
 
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 registerModule(const std::string & name, std::shared_ptr<BaseFunction> fn);
    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;
    std::unordered_map<std::string, std::shared_ptr<BaseFunction>> functionObjects;
    // 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::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",
                     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;
    }
 
    [[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 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);
                if (innerMap != it->second.end()) {
                    return it->second.at(token.lexeme);
                }
            }
        }
        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);
    static void handleComment(std::size_t & i){ i++;}
    static void handleSemicolon(std::size_t & i) {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