A simple scripting language in C++
Ferenc Szontágh
2025-04-19 bc2e09a3b7a4e414814b56be71ec5c540b8eb4d9
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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
// ModuleManager.hpp
#ifndef MODULES_MODULEMANAGER_HPP
#define MODULES_MODULEMANAGER_HPP
 
#include <filesystem>
#include <functional>
#include <memory>
#include <stdexcept>
#include <string>
#include <unordered_map>
#include <vector>
 
#include "BaseModule.hpp"
#include "Symbols/Value.hpp"
 
#ifndef _WIN32
#    include <dlfcn.h>
#else
#    include <windows.h>
#endif
 
namespace Modules {
using CallbackFunction = std::function<Symbols::Value(const std::vector<Symbols::Value> &)>;
 
/**
 * @brief Manager for registering and invoking modules.
 */
class ModuleManager {
  public:
    /**
     * @brief Get singleton instance of ModuleManager.
     */
    static ModuleManager & instance() {
        static ModuleManager mgr;
        return mgr;
    }
 
    /**
     * @brief Add a module to the manager.
     * @param module Unique pointer to a BaseModule.
     */
    void addModule(std::unique_ptr<BaseModule> module) { modules_.push_back(std::move(module)); }
 
    /**
     * @brief Invoke all registered modules to register their symbols.
     */
    void registerAll() {
        for (const auto & module : modules_) {
            module->registerModule();
        }
    }
 
  private:
    ModuleManager() = default;
    std::vector<std::unique_ptr<BaseModule>>                                                            modules_;
    // Built-in function callbacks: name -> function
    std::unordered_map<std::string, std::function<Symbols::Value(const std::vector<Symbols::Value> &)>> callbacks_;
    // Plugin handles for dynamically loaded modules
    std::vector<void *>                                                                                 pluginHandles_;
  public:
    /**
     * @brief Register a built-in function callback.
     * @param name Name of the function.
     * @param cb Callable taking argument values and returning a Value.
     */
    void registerFunction(const std::string &                                                name,
                          std::function<Symbols::Value(const std::vector<Symbols::Value> &)> cb) {
        callbacks_[name] = std::move(cb);
    }
 
    /**
     * @brief Check if a built-in function is registered.
     */
    bool hasFunction(const std::string & name) const { return callbacks_.find(name) != callbacks_.end(); }
 
    /**
     * @brief Call a built-in function callback.
     */
    Symbols::Value callFunction(const std::string & name, const std::vector<Symbols::Value> & args) const {
        auto it = callbacks_.find(name);
        if (it == callbacks_.end()) {
            throw std::runtime_error("Built-in function callback not found: " + name);
        }
        return it->second(args);
    }
 
    /**
     * @brief Load all plugin modules from specified directory.
     * @param directory Path to directory containing plugin shared libraries.
     */
    void loadPlugins(const std::string & directory) {
        namespace fs = std::filesystem;
        if (!fs::exists(directory) || !fs::is_directory(directory)) {
            return;
        }
        // Recursively search for plugin shared libraries
        for (const auto & entry : fs::recursive_directory_iterator(directory)) {
            if (!entry.is_regular_file()) {
                continue;
            }
#ifdef _WIN32
            if (entry.path().extension() == ".dll") {
#else
            if (entry.path().extension() == ".so") {
#endif
                loadPlugin(entry.path().string());
            }
        }
    }
 
    /**
     * @brief Load a single plugin module from shared library.
     * @param path Filesystem path to the shared library.
     */
    void loadPlugin(const std::string & path) {
#ifndef _WIN32
        void * handle = dlopen(path.c_str(), RTLD_NOW);
        if (!handle) {
            throw std::runtime_error("Failed to load module: " + path + ": " + dlerror());
        }
#else
        HMODULE handle = LoadLibraryA(path.c_str());
        if (!handle) {
            throw std::runtime_error("Failed to load module: " + path);
        }
#endif
        pluginHandles_.push_back(handle);
 
#ifndef _WIN32
        dlerror();  // clear any existing error
        using PluginInitFunc    = void (*)();
        auto         initFunc   = reinterpret_cast<PluginInitFunc>(dlsym(handle, "plugin_init"));
        const char * dlsymError = dlerror();
        if (dlsymError) {
            dlclose(handle);
            pluginHandles_.pop_back();
            throw std::runtime_error("Cannot find symbol 'plugin_init' in " + path + ": " + dlsymError);
        }
        initFunc();
#else
        using PluginInitFunc = void(__cdecl *)();
        auto initFunc        = reinterpret_cast<PluginInitFunc>(GetProcAddress(handle, "plugin_init"));
        if (!initFunc) {
            FreeLibrary(handle);
            pluginHandles_.pop_back();
            throw std::runtime_error("Cannot find symbol 'plugin_init' in " + path);
        }
        initFunc();
#endif
    }
 
    /**
     * @brief Destructor unloads modules and plugin libraries in safe order.
     * Modules (and their callback functions) are destroyed before the libraries are unloaded,
     * ensuring that destructors (in plugin code) run while the libraries are still mapped.
     */
    ~ModuleManager() {
        // Destroy module instances (may call code in plugin libraries)
        modules_.clear();
        // Clear callback functions (may refer to plugin code)
        callbacks_.clear();
        // Unload all dynamically loaded plugin libraries
        for (auto handle : pluginHandles_) {
#ifndef _WIN32
            dlclose(handle);
#else
            FreeLibrary((HMODULE) handle);
#endif
        }
        // Clear handles
        pluginHandles_.clear();
    }
};
 
}  // namespace Modules
#endif  // MODULES_MODULEMANAGER_HPP