#pragma once #include "quickjs/quickjs.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(__cpp_rtti) #define QJSPP_TYPENAME(...) (typeid(__VA_ARGS__).name()) #else #define QJSPP_TYPENAME(...) #__VA_ARGS__ #endif namespace qjs { class Context; class Value; /** Exception type. * Indicates that exception has occured in JS context. */ class exception { JSContext * ctx; public: exception(JSContext * ctx) : ctx(ctx) {} Context & context() const; /// Clears and returns the occurred exception. Value get(); }; /** std::shared_ptr, for compatibility with quickjspp v2. */ template using shared_ptr = std::shared_ptr; /** std::make_shared, for compatibility with quickjspp v2. */ template shared_ptr make_shared(JSContext *, Args&&... args) { return std::make_shared(std::forward(args)...); } /** Javascript conversion traits. * Describes how to convert type R to/from JSValue. Second template argument can be used for SFINAE/enable_if type filters. */ template struct js_traits { /** Create an object of C++ type R given JSValue v and JSContext. * This function is intentionally not implemented. User should implement this function for their own type. * @param v This value is passed as JSValueConst so it should be freed by the caller. * @throws exception in case of conversion error */ static R unwrap(JSContext * ctx, JSValueConst v) = delete; /** Create JSValue from an object of type R and JSContext. * This function is intentionally not implemented. User should implement this function for their own type. * @return Returns JSValue which should be freed by the caller or JS_EXCEPTION in case of error. */ static JSValue wrap(JSContext * ctx, R value) = delete; }; /** Conversion traits for JSValue (identity). */ template <> struct js_traits { static JSValue unwrap(JSContext * ctx, JSValueConst v) noexcept { return JS_DupValue(ctx, v); } static JSValue wrap(JSContext * ctx, JSValue&& v) noexcept { return v; } }; /** Conversion traits for integers. * Intentionally doesn't define traits for uint64_t since it can be typedefed to JSValue. (@see JS_NAN_BOXING) */ template struct js_traits && sizeof(Int) <= sizeof(int64_t) && !std::is_same_v>> { /// @throws exception static Int unwrap(JSContext * ctx, JSValueConst v) { if constexpr (sizeof(Int) > sizeof(int32_t)) { int64_t r; if(JS_ToInt64(ctx, &r, v)) throw exception{ctx}; return static_cast(r); } else { int32_t r; if(JS_ToInt32(ctx, &r, v)) throw exception{ctx}; return static_cast(r); } } static JSValue wrap(JSContext * ctx, Int i) noexcept { if constexpr (std::is_same_v || sizeof(Int) > sizeof(int32_t)) return JS_NewInt64(ctx, static_cast(i)); else return JS_NewInt32(ctx, static_cast(i)); } }; /** Conversion traits for boolean. */ template <> struct js_traits { static bool unwrap(JSContext * ctx, JSValueConst v) noexcept { // TODO: is this behaviour correct? return JS_ToBool(ctx, v) > 0; } static JSValue wrap(JSContext * ctx, bool i) noexcept { return JS_NewBool(ctx, i); } }; /** Conversion trait for void. */ template <> struct js_traits { /// @throws exception if jsvalue is neither undefined nor null static void unwrap(JSContext * ctx, JSValueConst value) { if(JS_IsException(value)) throw exception{ctx}; } }; /** Conversion traits for float64/double. */ template <> struct js_traits { /// @throws exception static double unwrap(JSContext * ctx, JSValueConst v) { double r; if(JS_ToFloat64(ctx, &r, v)) throw exception{ctx}; return r; } static JSValue wrap(JSContext * ctx, double i) noexcept { return JS_NewFloat64(ctx, i); } }; namespace detail { /** Fake std::string_view which frees the string on destruction. */ class js_string : public std::string_view { using Base = std::string_view; JSContext * ctx = nullptr; friend struct js_traits; js_string(JSContext * ctx, const char * ptr, std::size_t len) : Base(ptr, len), ctx(ctx) {} public: template js_string(Args&& ... args) : Base(std::forward(args)...), ctx(nullptr) {} js_string(const js_string& other) = delete; operator const char *() const { return this->data(); } ~js_string() { if(ctx) JS_FreeCString(ctx, this->data()); } }; } // namespace detail /** Conversion traits from std::string_view and to detail::js_string. */ template <> struct js_traits { static detail::js_string unwrap(JSContext * ctx, JSValueConst v) { size_t plen; const char * ptr = JS_ToCStringLen(ctx, &plen, v); if(!ptr) throw exception{ctx}; return detail::js_string{ctx, ptr, plen}; } static JSValue wrap(JSContext * ctx, std::string_view str) noexcept { return JS_NewStringLen(ctx, str.data(), str.size()); } }; /** Conversion traits for std::string */ template <> // slower struct js_traits { static std::string unwrap(JSContext * ctx, JSValueConst v) { auto str_view = js_traits::unwrap(ctx, v); return std::string{str_view.data(), str_view.size()}; } static JSValue wrap(JSContext * ctx, const std::string& str) noexcept { return JS_NewStringLen(ctx, str.data(), str.size()); } }; /** Conversion from const char * */ template <> struct js_traits { static JSValue wrap(JSContext * ctx, const char * str) noexcept { return JS_NewString(ctx, str); } static detail::js_string unwrap(JSContext * ctx, JSValueConst v) { return js_traits::unwrap(ctx, v); } }; /** Conversion from const std::variant */ template struct js_traits> { static JSValue wrap(JSContext * ctx, std::variant value) noexcept { return std::visit([ctx](auto&& value) { using T = std::decay_t; return js_traits::wrap(ctx, value); }, std::move(value)); } /* Useful type traits */ template struct is_shared_ptr : std::false_type {}; template struct is_shared_ptr> : std::true_type {}; template struct is_string { static constexpr bool value = std::is_same_v || std::is_same_v, std::string> || std::is_same_v, std::string_view>; }; template struct is_boolean { static constexpr bool value = std::is_same_v, bool>; }; template struct is_double { static constexpr bool value = std::is_same_v, double>; }; template struct is_vector : std::false_type {}; template struct is_vector> : std::true_type {}; template struct is_pair : std::false_type {}; template struct is_pair> : std::true_type {}; template struct is_variant : std::false_type {}; template struct is_variant> : std::true_type {}; /** Attempt to match common types (integral, floating-point, string, etc.) */ template