← index
libmx2/cmd/src/command_reg.cpp
Source: libmx2/cmd/src/command_reg.cpp
#include"command_reg.hpp"
#include"ast.hpp"
#include<vector>
#include<cstdio>
#include<unordered_set>

namespace cmd {

    void CommandRegistry::registerCommand(const std::string& name,
                                        CommandFunction func)    {
        commands[name] = func;
    }

    void CommandRegistry::registerTypedCommand(const std::string& name,
                                            TypedCommandFunction func) {
        typedCommands[name] = func;
    }

    void CommandRegistry::registerUserDefinedCommand(
        const std::string& name,
        const UserDefinedCommandInfo& info) {
        userDefinedCommands[name] = info;
    }

    bool CommandRegistry::isUserDefinedCommand(const std::string& name) const {
        return userDefinedCommands.find(name) != userDefinedCommands.end();
    }

    int CommandRegistry::executeCommand(const std::string& name,
                                        const std::vector<Argument>& args,
                                        std::istream& input,
                                        std::ostream& output) {

        auto it_typed = typedCommands.find(name);
        if (it_typed != typedCommands.end()) {
            return it_typed->second(args, input, output);
        }

        auto it_str = commands.find(name);
        if (it_str != commands.end()) {
            std::vector<std::string> argValues;
            argValues.reserve(args.size());
            for (auto const & arg : args) {
                argValues.push_back(arg.value);
            }
            return it_str->second(argValues, input, output);
        }

        auto it_ud = userDefinedCommands.find(name);
        if (it_ud != userDefinedCommands.end()) {
            return executeUserDefinedCommand(name, it_ud->second,
                                            args, input, output);
        }

        auto it_ext = externCommands.find(name);
        if (it_ext != externCommands.end()) {
            return executeExternCommand(name, args, input, output);
        }
        
        throw std::runtime_error("Command not found: " + name);
        return 1;
    }

    void CommandRegistry::registerExternCommand(const std::string& name, const ExternCommandInfo& info) {
        auto it = commands.find(name);
        if (it != commands.end()) {
            throw std::runtime_error("Command " + name + " already registered");
        }
        externCommands[name] = info;
    }
        
    // C callback: plugin calls this to write text to the host's ostream.
    // The void* ctx is a pointer to the host's std::ostream — no C++ types
    // cross the DLL boundary because the callback is compiled into the host.
    static void plugin_write_callback(void* ctx, const char* text) {
        auto* out = static_cast<std::ostream*>(ctx);
        *out << text;
        out->flush();
        if (out == &std::cout) {
            std::fflush(stdout);
        }
    }

    int CommandRegistry::executeExternCommand(const std::string& name, const std::vector<Argument>& args, 
                                std::istream& input, std::ostream& output) {
        auto it = externCommands.find(name);
        if (it != externCommands.end()) {
            const auto& info = it->second;
            if (info.func) {
                // Pre-resolve every Argument to a plain C string on the host side.
                // This keeps std::string, std::shared_ptr, and getVar() out of
                // the plugin entirely.
                std::vector<std::string> resolved;
                resolved.reserve(args.size());
                for (const auto& a : args) {
                    resolved.push_back(getVar(a));
                }
                std::vector<const char*> argv;
                argv.reserve(resolved.size());
                for (const auto& s : resolved) {
                    argv.push_back(s.c_str());
                }

                int result = info.func(
                    static_cast<int>(argv.size()),
                    argv.empty() ? nullptr : argv.data(),
                    static_cast<void*>(&output),
                    plugin_write_callback
                );

                if (result == 1 && info.library && info.library->hasSymbol("SDL_GetError")) {
                    try {
                        auto sdlGetError = info.library->getFunction<const char* (*)()>("SDL_GetError");
                        if (sdlGetError) {
                            const char* err = sdlGetError();
                            if (err != nullptr && err[0] != '\0') {
                                output << "SDL_GetError: " << err << "\n";
                            }
                        }
                    } catch (...) {
                    }
                }
                return result;
            } 
            else {
                output << "Function: " << info.functionName << " not found in library: " << info.libraryPath << "\n";
                return 1;
            }
        }
            return 0;
    }

    bool CommandRegistry::unregisterExternCommand(const std::string& name) {
        auto it = externCommands.find(name);
        if (it == externCommands.end()) {
            return false;
        }
        externCommands.erase(it);
        return true;
    }

    std::size_t CommandRegistry::clearExternCommands() {
        std::size_t count = externCommands.size();
        externCommands.clear();
        return count;
    }

    std::size_t CommandRegistry::pruneLibraries() {
        std::unordered_set<Library*> referenced;
        referenced.reserve(externCommands.size());
        for (const auto& kv : externCommands) {
            if (kv.second.library) {
                referenced.insert(kv.second.library.get());
            }
        }

        std::size_t removed = 0;
        for (auto it = libraries.begin(); it != libraries.end();) {
            const auto& libPtr = it->second;
            if (!libPtr || referenced.find(libPtr.get()) == referenced.end()) {
                it = libraries.erase(it);
                ++removed;
            } else {
                ++it;
            }
        }
        return removed;
    }
    
    int CommandRegistry::executeUserDefinedCommand(
        const std::string& name,
        const UserDefinedCommandInfo& info,
        const std::vector<Argument>& args,
        std::istream& input,
        std::ostream& output) {
        
        state::GameState* gameState = state::getGameState();
        std::unordered_map<std::string, std::optional<std::string>> origValues;

        for (size_t i = 0; i < std::min(args.size(), info.parameters.size()); ++i) {
            const auto& paramName = info.parameters[i];
        
            try {
                origValues[paramName] = gameState->getVariable(paramName);
            }
            catch (const state::StateException&) {
                origValues[paramName] = std::nullopt;
            }

            try {
                if (args[i].type == ARG_VARIABLE) {
                    std::string val;
                    try {
                        val = gameState->getVariable(args[i].value);
                    }
                    catch (...) {
                        val.clear();
                    }
                    gameState->setVariable(paramName, val);
                }
                else if (args[i].type == ARG_COMMAND_SUBST && args[i].cmdNode) {
                    AstExecutor &executor = AstExecutor::getExecutor();
                    std::stringstream cmdInput, cmdOutput;
                    executor.executeDirectly(args[i].cmdNode, cmdInput, cmdOutput);
                    std::string result = cmdOutput.str();
                    while (!result.empty() && (result.back() == '\n' || result.back() == '\r')) {
                        result.pop_back();
                    }
                    gameState->setVariable(paramName, result);
                }
                else {
                    gameState->setVariable(paramName, args[i].value);
                }
            }
            catch (const std::exception& e) {
                for (auto const & kv : origValues) {
                    if (kv.second.has_value())
                        gameState->setVariable(kv.first, *kv.second);
                    else
                        gameState->clearVariable(kv.first);
                }
                output << name << ": error setting parameter '"
                    << paramName << "': " << e.what() << "\n";
                return 1;
            }
        }

        int result = 0;
        try {
            AstExecutor &executor = AstExecutor::getExecutor();
            for (auto const & kv : userDefinedCommands) {
                executor.getRegistry()
                        .registerUserDefinedCommand(kv.first, kv.second);
            }
            bool previousReturnSignal = executor.getReturnSignal();
            executor.setReturnSignal(false);
            executor.execute(input, output, info.body);
            result = executor.getLastExitStatus();
            executor.setReturnSignal(previousReturnSignal);
        }
        catch (const AstFailure& e) {
            AstExecutor &executor = AstExecutor::getExecutor();
            executor.setReturnSignal(false);
            for (auto const & kv : origValues) {
                if (kv.second.has_value())
                    gameState->setVariable(kv.first, *kv.second);
                else
                    gameState->clearVariable(kv.first);
            }
            throw;
        }
        catch (const std::exception& e) {
            output << name << ": execution failed: " << e.what() << "\n";
            result = 1;
        }
        for (auto const & kv : origValues) {
            if (kv.second.has_value())
                gameState->setVariable(kv.first, *kv.second);
            else
                gameState->clearVariable(kv.first);
        }
        return result;
    }

    std::shared_ptr<Library> &CommandRegistry::getLibrary(const std::string& name){
        auto it = libraries.find(name);
        if (it != libraries.end()) {
            return it->second;
        }
        throw std::runtime_error("Library not found: " + name);
    }
    std::shared_ptr<Library> &CommandRegistry::setLibrary(const std::string& name) {
        auto it = libraries.find(name);
        if (it != libraries.end()) {
            return it->second;
        }
        try {
            libraries[name] = std::make_shared<cmd::Library>(name);
        } catch (const std::exception &e) {
            libraries[name] = nullptr;
        }
        return libraries[name];
    }

    void CommandRegistry::printInfo(std::ostream &out) {
        auto listSorted = [](std::ostream  &o, auto &l) -> void {
            std::vector<std::string> lst;
            for(auto &f: l) {
                lst.push_back(f.first);
            }
            std::sort(lst.begin(), lst.end());
            for(auto &i : lst) {
                o << "\t" << i << "\n";
            }
        };
        out << "Commands {\n";
        listSorted(out, this->commands);
        out << "}\n";
        out << "Typed Commands {\n";
        listSorted(out, this->typedCommands);
        out << "}\n";
        out << "User Defined Commands {\n";
        listSorted(out, this->userDefinedCommands);
        out << "}\n";
        out << "Commands from Libraries {\n";
        listSorted(out, this->externCommands);
        out << "}\n";
    }

    bool CommandRegistry::empty() const {
        return commands.empty() && typedCommands.empty() && userDefinedCommands.empty();
    }

}