Skip to content

File machine.h

File List > jac > machine > machine.h

Go to the documentation of this file

#pragma once

#include <quickjs.h>

#include <chrono>
#include <functional>
#include <stdexcept>
#include <string>
#include <unordered_map>
#include <vector>

#include "values.h"


namespace jac {


template<class Base, template<class> class... MFeatures>
struct ComposeMachine;

template<class Base, template<class> class FirstFeature, template<class> class... MFeatures>
struct ComposeMachine<Base, FirstFeature, MFeatures...> : public ComposeMachine<FirstFeature<Base>, MFeatures...> {};

template<class Base>
struct ComposeMachine<Base> : public Base {};


enum class EvalFlags : int {
    Global = JS_EVAL_TYPE_GLOBAL,
    Module = JS_EVAL_TYPE_MODULE,

    Strict = JS_EVAL_FLAG_STRICT,
    Strip = JS_EVAL_FLAG_STRIP,
    CompileOnly = JS_EVAL_FLAG_COMPILE_ONLY,
    BacktraceBarrier = JS_EVAL_FLAG_BACKTRACE_BARRIER
};

inline constexpr EvalFlags operator|(EvalFlags a, EvalFlags b) {
    int res = static_cast<int>(a) | static_cast<int>(b);
    if (res & JS_EVAL_TYPE_GLOBAL && res & JS_EVAL_TYPE_MODULE) {
        throw std::runtime_error("Cannot use both global and module eval flags");
    }
    return static_cast<EvalFlags>(res);
}

inline constexpr EvalFlags operator&(EvalFlags a, EvalFlags b) {
    return static_cast<EvalFlags>(static_cast<int>(a) & static_cast<int>(b));
}


class MachineBase;


class Module {
    ContextRef _ctx;
    JSModuleDef *_def;

    std::vector<std::tuple<std::string, Value>> exports;

    inline MachineBase& base() {
        return *static_cast<MachineBase*>(JS_GetContextOpaque(_ctx));
    }

    static inline MachineBase& base(ContextRef ctx) {
        return *static_cast<MachineBase*>(JS_GetContextOpaque(ctx));
    }
public:
    Module(ContextRef ctx, std::string name);
    Module& operator=(const Module&) = delete;
    Module(const Module&) = delete;
    Module& operator=(Module&& other) {
        _ctx = other._ctx;
        _def = other._def;
        exports = std::move(other.exports);
        other._def = nullptr;
        return *this;
    }
    Module(Module&& other): _ctx(other._ctx), _def(other._def), exports(std::move(other.exports)) {
        other._def = nullptr;
    }

    void addExport(std::string name, Value val);

    JSModuleDef *get() {
        return _def;
    }
};


class MachineBase {
private:
    std::unordered_map<JSModuleDef*, Module> _modules;
    Module& findModule(JSModuleDef* m);

    bool _interrupt = false;

    std::chrono::milliseconds _watchdogTimeout = std::chrono::milliseconds(0);
    std::chrono::time_point<std::chrono::steady_clock> _watchdogNext;
    std::function<bool()> _wathdogCallback;

    JSRuntime* _runtime = nullptr;
    ContextRef _context = nullptr;
public:
    JSRuntime* runtime() {
        return _runtime;
    }

    ContextRef context() {
        return _context;
    }

    void initialize();

    MachineBase() = default;
    MachineBase(const MachineBase&) = delete;
    MachineBase(MachineBase&&) = delete;
    MachineBase& operator=(const MachineBase&) = delete;
    MachineBase& operator=(MachineBase&&) = delete;

    virtual ~MachineBase() {
        _modules.clear();
        if (_context) {
            JS_FreeContext(_context);
        }
        if (_runtime) {
            JS_FreeRuntime(_runtime);
        }
    }

    Value eval(std::string code, std::string filename, EvalFlags flags = EvalFlags::Global);

    Module& newModule(std::string name);

    void interruptRuntime() {
        _interrupt = true;
    }

    void resetWatchdog() {
        _watchdogNext = std::chrono::steady_clock::now() + _watchdogTimeout;
    }

    void setWatchdogTimeout(std::chrono::milliseconds timeout) {
        _watchdogTimeout = timeout;
    }

    void setWatchdogHandler(std::function<bool()> callback) {
        _wathdogCallback = callback;
    }

    friend class Module;
};


} // namespace jac