File indexing completed on 2024-11-24 03:56:27

0001 /*
0002  * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia <dev@dragon.best>
0003  *
0004  * SPDX-License-Identifier: GPL-3.0-or-later
0005  */
0006 
0007 #pragma once
0008 
0009 #include <stdexcept>
0010 #include <memory>
0011 
0012 #include <QString>
0013 #include <QObject>
0014 #include <QHash>
0015 #include <QDir>
0016 #include <QVariant>
0017 
0018 #include "app/qstring_exception.hpp"
0019 
0020 
0021 namespace app::scripting {
0022 
0023 class ScriptError : public QStringException<>{ using Ctor::Ctor; };
0024 
0025 class ScriptEngine;
0026 
0027 /**
0028  * \brief Context for running scripts (implementation)
0029  */
0030 class ScriptExecutionContext : public QObject
0031 {
0032     Q_OBJECT
0033 public:
0034 
0035     ScriptExecutionContext() = default;
0036     virtual ~ScriptExecutionContext() = default;
0037 
0038     /**
0039      * \brief Exposes \p obj as a global variable called \p name
0040      */
0041     virtual void expose(const QString& name, const QVariant& obj) = 0;
0042 
0043     /**
0044      * \brief Evaluates \p code and serializes the result (if any) into a string
0045      * \throws ScriptError on errors with the script
0046      */
0047     virtual QString eval_to_string(const QString& code) = 0;
0048 
0049     /**
0050      * \brief Evaluates \p line and returns a strin list of available code completions
0051      */
0052     virtual QStringList eval_completions(const QString& line) { Q_UNUSED(line); return {}; }
0053 
0054     /**
0055      * \brief Marks an app module that must be loaded
0056      */
0057     virtual void app_module(const QString& name) = 0;
0058 
0059     /**
0060      * \brief Runs a function from a file
0061      * \return Whether the call was successful
0062      */
0063     virtual bool run_from_module (
0064         const QDir& path,       ///< Path containing the module file
0065         const QString& module,  ///< Module name to load, meaning of this depends on the engine
0066         const QString& function,///< Function to call
0067         const QVariantList& args///< Arguments to pass the function
0068     ) = 0;
0069 
0070     /**
0071      * \brief Engine that created the context
0072      */
0073     virtual const ScriptEngine* engine() const = 0;
0074 
0075 Q_SIGNALS:
0076     void stderr_line(const QString&);
0077     void stdout_line(const QString&);
0078 
0079 private:
0080     ScriptExecutionContext(const ScriptExecutionContext&) = delete;
0081     ScriptExecutionContext& operator=(const ScriptExecutionContext&) = delete;
0082 };
0083 
0084 
0085 /**
0086  * \brief Script context holder
0087  */
0088 using ScriptContext = std::unique_ptr<ScriptExecutionContext>;
0089 
0090 
0091 /**
0092  * \brief Scripting system metadata
0093  */
0094 class ScriptEngine
0095 {
0096 public:
0097     virtual ~ScriptEngine() = default;
0098 
0099     /**
0100      * \brief short machine-readable name for the language / engine
0101      */
0102     virtual QString slug() const = 0;
0103 
0104     /**
0105      * \brief Human-readable name
0106      */
0107     virtual QString label() const = 0;
0108 
0109     /**
0110      * \brief Creates an execution context to run scripts
0111      */
0112     virtual ScriptContext create_context() const = 0;
0113 
0114 protected:
0115     template<class T>
0116     struct Autoregister;
0117 };
0118 
0119 
0120 class ScriptEngineFactory
0121 {
0122 public:
0123     static ScriptEngineFactory& instance()
0124     {
0125         static ScriptEngineFactory instance;
0126         return instance;
0127     }
0128 
0129     void register_engine(std::unique_ptr<ScriptEngine> eng)
0130     {
0131         engines_.push_back(std::move(eng));
0132     }
0133 
0134     const ScriptEngine* engine(const QString& slug) const
0135     {
0136         for ( const auto& engine: engines_ )
0137             if ( engine->slug() == slug )
0138                 return engine.get();
0139         return nullptr;
0140     }
0141 
0142     const std::vector<std::unique_ptr<ScriptEngine>>& engines() const
0143     {
0144         return engines_;
0145     }
0146 
0147 private:
0148     ScriptEngineFactory() = default;
0149     ScriptEngineFactory(const ScriptEngineFactory&) = delete;
0150     ~ScriptEngineFactory() = default;
0151     std::vector<std::unique_ptr<ScriptEngine>> engines_;
0152 };
0153 
0154 template<class T>
0155 struct ScriptEngine::Autoregister
0156 {
0157     template<class... Args>
0158     Autoregister(Args&&... args)
0159     {
0160         ScriptEngineFactory::instance().register_engine(std::make_unique<T>(std::forward<Args>(args)...));
0161     }
0162 };
0163 
0164 } // namespace app::scripting