quickjspp.hpp 72 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232
  1. #pragma once
  2. #include "quickjs/quickjs.h"
  3. #include <vector>
  4. #include <string_view>
  5. #include <string>
  6. #include <cassert>
  7. #include <memory>
  8. #include <cstddef>
  9. #include <algorithm>
  10. #include <tuple>
  11. #include <functional>
  12. #include <stdexcept>
  13. #include <variant>
  14. #include <optional>
  15. #include <type_traits>
  16. #include <unordered_map>
  17. #include <fstream>
  18. #include <ios>
  19. #include <sstream>
  20. #include <filesystem>
  21. #if defined(__cpp_rtti)
  22. #define QJSPP_TYPENAME(...) (typeid(__VA_ARGS__).name())
  23. #else
  24. #define QJSPP_TYPENAME(...) #__VA_ARGS__
  25. #endif
  26. namespace qjs {
  27. class Context;
  28. class Value;
  29. /** Exception type.
  30. * Indicates that exception has occured in JS context.
  31. */
  32. class exception {
  33. JSContext * ctx;
  34. public:
  35. exception(JSContext * ctx) : ctx(ctx) {}
  36. Context & context() const;
  37. /// Clears and returns the occurred exception.
  38. Value get();
  39. };
  40. /** std::shared_ptr, for compatibility with quickjspp v2. */
  41. template <class T> using shared_ptr = std::shared_ptr<T>;
  42. /** std::make_shared, for compatibility with quickjspp v2. */
  43. template <class T, typename... Args>
  44. shared_ptr<T> make_shared(JSContext *, Args&&... args)
  45. {
  46. return std::make_shared<T>(std::forward<Args>(args)...);
  47. }
  48. /** Javascript conversion traits.
  49. * Describes how to convert type R to/from JSValue. Second template argument can be used for SFINAE/enable_if type filters.
  50. */
  51. template <typename R, typename /*_SFINAE*/ = void>
  52. struct js_traits
  53. {
  54. /** Create an object of C++ type R given JSValue v and JSContext.
  55. * This function is intentionally not implemented. User should implement this function for their own type.
  56. * @param v This value is passed as JSValueConst so it should be freed by the caller.
  57. * @throws exception in case of conversion error
  58. */
  59. static R unwrap(JSContext * ctx, JSValueConst v) = delete;
  60. /** Create JSValue from an object of type R and JSContext.
  61. * This function is intentionally not implemented. User should implement this function for their own type.
  62. * @return Returns JSValue which should be freed by the caller or JS_EXCEPTION in case of error.
  63. */
  64. static JSValue wrap(JSContext * ctx, R value) = delete;
  65. };
  66. /** Conversion traits for JSValue (identity).
  67. */
  68. template <>
  69. struct js_traits<JSValue>
  70. {
  71. static JSValue unwrap(JSContext * ctx, JSValueConst v) noexcept
  72. {
  73. return JS_DupValue(ctx, v);
  74. }
  75. static JSValue wrap(JSContext * ctx, JSValue&& v) noexcept
  76. {
  77. return v;
  78. }
  79. };
  80. /** Conversion traits for integers.
  81. * Intentionally doesn't define traits for uint64_t since it can be typedefed to JSValue. (@see JS_NAN_BOXING)
  82. */
  83. template <typename Int>
  84. struct js_traits<Int, std::enable_if_t<std::is_integral_v<Int> && sizeof(Int) <= sizeof(int64_t) && !std::is_same_v<Int, uint64_t>>>
  85. {
  86. /// @throws exception
  87. static Int unwrap(JSContext * ctx, JSValueConst v)
  88. {
  89. if constexpr (sizeof(Int) > sizeof(int32_t))
  90. {
  91. int64_t r;
  92. if(JS_ToInt64(ctx, &r, v))
  93. throw exception{ctx};
  94. return static_cast<Int>(r);
  95. }
  96. else
  97. {
  98. int32_t r;
  99. if(JS_ToInt32(ctx, &r, v))
  100. throw exception{ctx};
  101. return static_cast<Int>(r);
  102. }
  103. }
  104. static JSValue wrap(JSContext * ctx, Int i) noexcept
  105. {
  106. if constexpr (std::is_same_v<Int, uint32_t> || sizeof(Int) > sizeof(int32_t))
  107. return JS_NewInt64(ctx, static_cast<Int>(i));
  108. else
  109. return JS_NewInt32(ctx, static_cast<Int>(i));
  110. }
  111. };
  112. /** Conversion traits for boolean.
  113. */
  114. template <>
  115. struct js_traits<bool>
  116. {
  117. static bool unwrap(JSContext * ctx, JSValueConst v) noexcept
  118. {
  119. // TODO: is this behaviour correct?
  120. return JS_ToBool(ctx, v) > 0;
  121. }
  122. static JSValue wrap(JSContext * ctx, bool i) noexcept
  123. {
  124. return JS_NewBool(ctx, i);
  125. }
  126. };
  127. /** Conversion trait for void.
  128. */
  129. template <>
  130. struct js_traits<void>
  131. {
  132. /// @throws exception if jsvalue is neither undefined nor null
  133. static void unwrap(JSContext * ctx, JSValueConst value)
  134. {
  135. if(JS_IsException(value))
  136. throw exception{ctx};
  137. }
  138. };
  139. /** Conversion traits for float64/double.
  140. */
  141. template <>
  142. struct js_traits<double>
  143. {
  144. /// @throws exception
  145. static double unwrap(JSContext * ctx, JSValueConst v)
  146. {
  147. double r;
  148. if(JS_ToFloat64(ctx, &r, v))
  149. throw exception{ctx};
  150. return r;
  151. }
  152. static JSValue wrap(JSContext * ctx, double i) noexcept
  153. {
  154. return JS_NewFloat64(ctx, i);
  155. }
  156. };
  157. namespace detail {
  158. /** Fake std::string_view which frees the string on destruction.
  159. */
  160. class js_string : public std::string_view
  161. {
  162. using Base = std::string_view;
  163. JSContext * ctx = nullptr;
  164. friend struct js_traits<std::string_view>;
  165. js_string(JSContext * ctx, const char * ptr, std::size_t len) : Base(ptr, len), ctx(ctx) {}
  166. public:
  167. template <typename... Args>
  168. js_string(Args&& ... args) : Base(std::forward<Args>(args)...), ctx(nullptr) {}
  169. js_string(const js_string& other) = delete;
  170. operator const char *() const
  171. {
  172. return this->data();
  173. }
  174. ~js_string()
  175. {
  176. if(ctx)
  177. JS_FreeCString(ctx, this->data());
  178. }
  179. };
  180. } // namespace detail
  181. /** Conversion traits from std::string_view and to detail::js_string. */
  182. template <>
  183. struct js_traits<std::string_view>
  184. {
  185. static detail::js_string unwrap(JSContext * ctx, JSValueConst v)
  186. {
  187. size_t plen;
  188. const char * ptr = JS_ToCStringLen(ctx, &plen, v);
  189. if(!ptr)
  190. throw exception{ctx};
  191. return detail::js_string{ctx, ptr, plen};
  192. }
  193. static JSValue wrap(JSContext * ctx, std::string_view str) noexcept
  194. {
  195. return JS_NewStringLen(ctx, str.data(), str.size());
  196. }
  197. };
  198. /** Conversion traits for std::string */
  199. template <> // slower
  200. struct js_traits<std::string>
  201. {
  202. static std::string unwrap(JSContext * ctx, JSValueConst v)
  203. {
  204. auto str_view = js_traits<std::string_view>::unwrap(ctx, v);
  205. return std::string{str_view.data(), str_view.size()};
  206. }
  207. static JSValue wrap(JSContext * ctx, const std::string& str) noexcept
  208. {
  209. return JS_NewStringLen(ctx, str.data(), str.size());
  210. }
  211. };
  212. /** Conversion from const char * */
  213. template <>
  214. struct js_traits<const char *>
  215. {
  216. static JSValue wrap(JSContext * ctx, const char * str) noexcept
  217. {
  218. return JS_NewString(ctx, str);
  219. }
  220. static detail::js_string unwrap(JSContext * ctx, JSValueConst v)
  221. {
  222. return js_traits<std::string_view>::unwrap(ctx, v);
  223. }
  224. };
  225. /** Conversion from const std::variant */
  226. template <typename ... Ts>
  227. struct js_traits<std::variant<Ts...>>
  228. {
  229. static JSValue wrap(JSContext * ctx, std::variant<Ts...> value) noexcept
  230. {
  231. return std::visit([ctx](auto&& value) {
  232. using T = std::decay_t<decltype(value)>;
  233. return js_traits<T>::wrap(ctx, value);
  234. }, std::move(value));
  235. }
  236. /* Useful type traits */
  237. template <typename T> struct is_shared_ptr : std::false_type {};
  238. template <typename T> struct is_shared_ptr<std::shared_ptr<T>> : std::true_type {};
  239. template <typename T> struct is_string
  240. {
  241. static constexpr bool value = std::is_same_v<T, const char *> || std::is_same_v<std::decay_t<T>, std::string> ||
  242. std::is_same_v<std::decay_t<T>, std::string_view>;
  243. };
  244. template <typename T> struct is_boolean { static constexpr bool value = std::is_same_v<std::decay_t<T>, bool>; };
  245. template <typename T> struct is_double { static constexpr bool value = std::is_same_v<std::decay_t<T>, double>; };
  246. template <typename T> struct is_vector : std::false_type {};
  247. template <typename T> struct is_vector<std::vector<T>> : std::true_type {};
  248. template <typename T> struct is_pair : std::false_type {};
  249. template <typename U, typename V> struct is_pair<std::pair<U, V>> : std::true_type {};
  250. template <typename T> struct is_variant : std::false_type {};
  251. template <typename ... Us> struct is_variant<std::variant<Us...>> : std::true_type {};
  252. /** Attempt to match common types (integral, floating-point, string, etc.) */
  253. template <template <typename R> typename Trait, typename U, typename ... Us>
  254. static std::optional<std::variant<Ts...>> unwrapImpl(JSContext * ctx, JSValueConst v)
  255. {
  256. if constexpr (Trait<U>::value)
  257. {
  258. return js_traits<U>::unwrap(ctx, v);
  259. }
  260. if constexpr ((sizeof ... (Us)) > 0)
  261. {
  262. return unwrapImpl<Trait, Us...>(ctx, v);
  263. }
  264. return std::nullopt;
  265. }
  266. /** Attempt to match class ID with type */
  267. template <typename U, typename ... Us>
  268. static std::optional<std::variant<Ts...>> unwrapObj(JSContext * ctx, JSValueConst v, JSClassID class_id)
  269. {
  270. if constexpr (is_shared_ptr<U>::value)
  271. {
  272. if(class_id == js_traits<U>::QJSClassId)
  273. {
  274. return js_traits<U>::unwrap(ctx, v);
  275. }
  276. }
  277. // try to unwrap embedded variant (variant<variant<...>>), might be slow
  278. if constexpr (is_variant<U>::value)
  279. {
  280. if(auto opt = js_traits<std::optional<U>>::unwrap(ctx, v))
  281. return *opt;
  282. }
  283. if constexpr (is_vector<U>::value)
  284. {
  285. if(JS_IsArray(ctx, v) == 1)
  286. {
  287. auto firstElement = JS_GetPropertyUint32(ctx, v, 0);
  288. bool ok = isCompatible<std::decay_t<typename U::value_type>>(ctx, firstElement);
  289. JS_FreeValue(ctx, firstElement);
  290. if(ok)
  291. {
  292. return U{js_traits<U>::unwrap(ctx, v)};
  293. }
  294. }
  295. }
  296. if constexpr (is_pair<U>::value)
  297. {
  298. if(JS_IsArray(ctx, v) == 1)
  299. {
  300. // todo: check length?
  301. auto firstElement = JS_GetPropertyUint32(ctx, v, 0);
  302. auto secondElement = JS_GetPropertyUint32(ctx, v, 1);
  303. bool ok = isCompatible<std::decay_t<typename U::first_type>>(ctx, firstElement)
  304. && isCompatible<std::decay_t<typename U::second_type>>(ctx, secondElement);
  305. JS_FreeValue(ctx, firstElement);
  306. JS_FreeValue(ctx, secondElement);
  307. if(ok)
  308. {
  309. return U{js_traits<U>::unwrap(ctx, v)};
  310. }
  311. }
  312. }
  313. if constexpr ((sizeof ... (Us)) > 0)
  314. {
  315. return unwrapObj<Us...>(ctx, v, class_id);
  316. }
  317. return std::nullopt;
  318. }
  319. /** Attempt to cast to types satisfying traits, ordered in terms of priority */
  320. template <template <typename T> typename Trait, template <typename T> typename ... Traits>
  321. static std::variant<Ts...> unwrapPriority(JSContext * ctx, JSValueConst v)
  322. {
  323. if(auto result = unwrapImpl<Trait, Ts...>(ctx, v))
  324. {
  325. return *result;
  326. }
  327. if constexpr ((sizeof ... (Traits)) > 0)
  328. {
  329. return unwrapPriority<Traits...>(ctx, v);
  330. }
  331. JS_ThrowTypeError(ctx, "Expected type %s", QJSPP_TYPENAME(std::variant<Ts...>));
  332. throw exception{ctx};
  333. }
  334. template <typename T>
  335. static bool isCompatible(JSContext * ctx, JSValueConst v) noexcept
  336. {
  337. //const char * type_name = typeid(T).name();
  338. switch(JS_VALUE_GET_TAG(v))
  339. {
  340. case JS_TAG_STRING:
  341. return is_string<T>::value;
  342. case JS_TAG_FUNCTION_BYTECODE:
  343. return std::is_function<T>::value;
  344. case JS_TAG_OBJECT:
  345. if(JS_IsArray(ctx, v) == 1)
  346. return is_vector<T>::value || is_pair<T>::value;
  347. if constexpr (is_shared_ptr<T>::value)
  348. {
  349. if(JS_GetClassID(v) == js_traits<T>::QJSClassId)
  350. return true;
  351. }
  352. return false;
  353. case JS_TAG_INT:
  354. [[fallthrough]];
  355. case JS_TAG_BIG_INT:
  356. return std::is_integral_v<T> || std::is_floating_point_v<T>;
  357. case JS_TAG_BOOL:
  358. return is_boolean<T>::value || std::is_integral_v<T> || std::is_floating_point_v<T>;
  359. case JS_TAG_BIG_DECIMAL:
  360. [[fallthrough]];
  361. case JS_TAG_BIG_FLOAT:
  362. [[fallthrough]];
  363. case JS_TAG_FLOAT64:
  364. default: // >JS_TAG_FLOAT64 (JS_NAN_BOXING)
  365. return is_double<T>::value || std::is_floating_point_v<T>;
  366. case JS_TAG_SYMBOL:
  367. [[fallthrough]];
  368. case JS_TAG_MODULE:
  369. [[fallthrough]];
  370. case JS_TAG_NULL:
  371. [[fallthrough]];
  372. case JS_TAG_UNDEFINED:
  373. [[fallthrough]];
  374. case JS_TAG_UNINITIALIZED:
  375. [[fallthrough]];
  376. case JS_TAG_CATCH_OFFSET:
  377. [[fallthrough]];
  378. case JS_TAG_EXCEPTION:
  379. break;
  380. }
  381. return false;
  382. }
  383. static std::variant<Ts...> unwrap(JSContext * ctx, JSValueConst v)
  384. {
  385. const auto tag = JS_VALUE_GET_TAG(v);
  386. switch(tag)
  387. {
  388. case JS_TAG_STRING:
  389. return unwrapPriority<is_string>(ctx, v);
  390. case JS_TAG_FUNCTION_BYTECODE:
  391. return unwrapPriority<std::is_function>(ctx, v);
  392. case JS_TAG_OBJECT:
  393. if(auto result = unwrapObj<Ts...>(ctx, v, JS_GetClassID(v)))
  394. {
  395. return *result;
  396. }
  397. JS_ThrowTypeError(ctx, "Expected type %s, got object with classid %d",
  398. QJSPP_TYPENAME(std::variant<Ts...>), JS_GetClassID(v));
  399. break;
  400. case JS_TAG_INT:
  401. [[fallthrough]];
  402. case JS_TAG_BIG_INT:
  403. return unwrapPriority<std::is_integral, std::is_floating_point>(ctx, v);
  404. case JS_TAG_BOOL:
  405. return unwrapPriority<is_boolean, std::is_integral, std::is_floating_point>(ctx, v);
  406. case JS_TAG_SYMBOL:
  407. [[fallthrough]];
  408. case JS_TAG_MODULE:
  409. [[fallthrough]];
  410. case JS_TAG_NULL:
  411. [[fallthrough]];
  412. case JS_TAG_UNDEFINED:
  413. [[fallthrough]];
  414. case JS_TAG_UNINITIALIZED:
  415. [[fallthrough]];
  416. case JS_TAG_CATCH_OFFSET:
  417. JS_ThrowTypeError(ctx, "Expected type %s, got tag %d", QJSPP_TYPENAME(std::variant<Ts...>), tag);
  418. [[fallthrough]];
  419. case JS_TAG_EXCEPTION:
  420. break;
  421. case JS_TAG_BIG_DECIMAL:
  422. [[fallthrough]];
  423. case JS_TAG_BIG_FLOAT:
  424. [[fallthrough]];
  425. case JS_TAG_FLOAT64:
  426. [[fallthrough]];
  427. default: // more than JS_TAG_FLOAT64 (nan boxing)
  428. return unwrapPriority<is_double, std::is_floating_point>(ctx, v);
  429. }
  430. throw exception{ctx};
  431. }
  432. };
  433. template <typename T>
  434. struct rest : std::vector<T>
  435. {
  436. using std::vector<T>::vector;
  437. using std::vector<T>::operator=;
  438. };
  439. namespace detail {
  440. /** Helper function to convert and then free JSValue. */
  441. template <typename T>
  442. T unwrap_free(JSContext * ctx, JSValue val)
  443. {
  444. if constexpr(std::is_same_v<T, void>)
  445. {
  446. JS_FreeValue(ctx, val);
  447. return js_traits<T>::unwrap(ctx, val);
  448. }
  449. else
  450. {
  451. try
  452. {
  453. T result = js_traits<std::decay_t<T>>::unwrap(ctx, val);
  454. JS_FreeValue(ctx, val);
  455. return result;
  456. }
  457. catch(...)
  458. {
  459. JS_FreeValue(ctx, val);
  460. throw;
  461. }
  462. }
  463. }
  464. template <typename T, size_t I, size_t NArgs>
  465. struct unwrap_arg_impl {
  466. static auto unwrap(JSContext * ctx, int argc, JSValueConst * argv)
  467. {
  468. if (size_t(argc) <= I) {
  469. JS_ThrowTypeError(ctx, "Expected at least %lu arguments but received %d",
  470. (unsigned long)NArgs, argc);
  471. throw exception{ctx};
  472. }
  473. return js_traits<std::decay_t<T>>::unwrap(ctx, argv[I]);
  474. }
  475. };
  476. template <typename T, size_t I, size_t NArgs>
  477. struct unwrap_arg_impl<rest<T>, I, NArgs> {
  478. static rest<T> unwrap(JSContext * ctx, int argc, JSValueConst * argv) {
  479. static_assert(I == NArgs - 1, "The `rest` argument must be the last function argument.");
  480. rest<T> result;
  481. result.reserve(argc - I);
  482. for (size_t i = I; i < size_t(argc); ++i)
  483. result.push_back(js_traits<T>::unwrap(ctx, argv[i]));
  484. return result;
  485. }
  486. };
  487. template <class Tuple, std::size_t... I>
  488. Tuple unwrap_args_impl(JSContext * ctx, int argc, JSValueConst * argv, std::index_sequence<I...>)
  489. {
  490. return Tuple{unwrap_arg_impl<std::tuple_element_t<I, Tuple>, I, sizeof...(I)>::unwrap(ctx, argc, argv)...};
  491. }
  492. /** Helper function to convert an array of JSValues to a tuple.
  493. * @tparam Args C++ types of the argv array
  494. */
  495. template <typename... Args>
  496. std::tuple<std::decay_t<Args>...> unwrap_args(JSContext * ctx, int argc, JSValueConst * argv)
  497. {
  498. return unwrap_args_impl<std::tuple<std::decay_t<Args>...>>(ctx, argc, argv, std::make_index_sequence<sizeof...(Args)>());
  499. }
  500. /** Helper function to call f with an array of JSValues.
  501. * @tparam R return type of f
  502. * @tparam Args argument types of f
  503. * @tparam Callable type of f (inferred)
  504. * @param ctx JSContext
  505. * @param f callable object
  506. * @param argv array of JSValue's
  507. * @return converted return value of f or JS_NULL if f returns void
  508. */
  509. template <typename R, typename... Args, typename Callable>
  510. JSValue wrap_call(JSContext * ctx, Callable&& f, int argc, JSValueConst * argv) noexcept
  511. {
  512. try
  513. {
  514. if constexpr(std::is_same_v<R, void>)
  515. {
  516. std::apply(std::forward<Callable>(f), unwrap_args<Args...>(ctx, argc, argv));
  517. return JS_NULL;
  518. }
  519. else
  520. {
  521. return js_traits<std::decay_t<R>>::wrap(ctx,
  522. std::apply(std::forward<Callable>(f),
  523. unwrap_args<Args...>(ctx, argc, argv)));
  524. }
  525. }
  526. catch(exception)
  527. {
  528. return JS_EXCEPTION;
  529. }
  530. catch (std::exception const & err)
  531. {
  532. JS_ThrowInternalError(ctx, "%s", err.what());
  533. return JS_EXCEPTION;
  534. }
  535. catch (...)
  536. {
  537. JS_ThrowInternalError(ctx, "Unknown error");
  538. return JS_EXCEPTION;
  539. }
  540. }
  541. /** Same as wrap_call, but pass this_value as first argument.
  542. * @tparam FirstArg type of this_value
  543. */
  544. template <typename R, typename FirstArg, typename... Args, typename Callable>
  545. JSValue wrap_this_call(JSContext * ctx, Callable&& f, JSValueConst this_value, int argc, JSValueConst * argv) noexcept
  546. {
  547. try
  548. {
  549. if constexpr(std::is_same_v<R, void>)
  550. {
  551. std::apply(std::forward<Callable>(f), std::tuple_cat(unwrap_args<FirstArg>(ctx, 1, &this_value),
  552. unwrap_args<Args...>(ctx, argc, argv)));
  553. return JS_NULL;
  554. }
  555. else
  556. {
  557. return js_traits<std::decay_t<R>>::wrap(ctx,
  558. std::apply(std::forward<Callable>(f),
  559. std::tuple_cat(
  560. unwrap_args<FirstArg>(ctx, 1, &this_value),
  561. unwrap_args<Args...>(ctx, argc, argv))));
  562. }
  563. }
  564. catch(exception)
  565. {
  566. return JS_EXCEPTION;
  567. }
  568. catch (std::exception const & err)
  569. {
  570. JS_ThrowInternalError(ctx, "%s", err.what());
  571. return JS_EXCEPTION;
  572. }
  573. catch (...)
  574. {
  575. JS_ThrowInternalError(ctx, "Unknown error");
  576. return JS_EXCEPTION;
  577. }
  578. }
  579. template <class Tuple, std::size_t... I>
  580. void wrap_args_impl(JSContext * ctx, JSValue * argv, Tuple tuple, std::index_sequence<I...>)
  581. {
  582. ((argv[I] = js_traits<std::decay_t<std::tuple_element_t<I, Tuple>>>::wrap(ctx, std::get<I>(tuple))), ...);
  583. }
  584. /** Converts C++ args to JSValue array.
  585. * @tparam Args argument types
  586. * @param argv array of size at least sizeof...(Args)
  587. */
  588. template <typename... Args>
  589. void wrap_args(JSContext * ctx, JSValue * argv, Args&& ... args)
  590. {
  591. wrap_args_impl(ctx, argv, std::make_tuple(std::forward<Args>(args)...),
  592. std::make_index_sequence<sizeof...(Args)>());
  593. }
  594. // Helper trait to obtain `T` in `T::*` expressions
  595. template<typename T> struct class_from_member_pointer { using type = void; };
  596. template<typename T, typename U> struct class_from_member_pointer<T U::*> { using type = U; };
  597. template<typename T> using class_from_member_pointer_t = typename class_from_member_pointer<T>::type;
  598. } // namespace detail
  599. /** A wrapper type for free and class member functions.
  600. * Pointer to function F is a template argument.
  601. * @tparam F either a pointer to free function or a pointer to class member function
  602. * @tparam PassThis if true and F is a pointer to free function, passes Javascript "this" value as first argument:
  603. */
  604. template <auto F, bool PassThis = false /* pass this as the first argument */>
  605. struct fwrapper
  606. {
  607. /// "name" property of the JS function object (not defined if nullptr)
  608. const char * name = nullptr;
  609. };
  610. /** Conversion to JSValue for free function in fwrapper. */
  611. template <typename R, typename... Args, R (* F)(Args...), bool PassThis>
  612. struct js_traits<fwrapper<F, PassThis>>
  613. {
  614. static JSValue wrap(JSContext * ctx, fwrapper<F, PassThis> fw) noexcept
  615. {
  616. return JS_NewCFunction(ctx, [](JSContext * ctx, JSValueConst this_value, int argc,
  617. JSValueConst * argv) noexcept -> JSValue {
  618. if constexpr(PassThis)
  619. return detail::wrap_this_call<R, Args...>(ctx, F, this_value, argc, argv);
  620. else
  621. return detail::wrap_call<R, Args...>(ctx, F, argc, argv);
  622. }, fw.name, sizeof...(Args));
  623. }
  624. };
  625. /** Conversion to JSValue for class member function in fwrapper. PassThis is ignored and treated as true */
  626. template <typename R, class T, typename... Args, R (T::*F)(Args...), bool PassThis/*=ignored*/>
  627. struct js_traits<fwrapper<F, PassThis>>
  628. {
  629. static JSValue wrap(JSContext * ctx, fwrapper<F, PassThis> fw) noexcept
  630. {
  631. return JS_NewCFunction(ctx, [](JSContext * ctx, JSValueConst this_value, int argc,
  632. JSValueConst * argv) noexcept -> JSValue {
  633. return detail::wrap_this_call<R, std::shared_ptr<T>, Args...>(ctx, F, this_value, argc, argv);
  634. }, fw.name, sizeof...(Args));
  635. }
  636. };
  637. /** Conversion to JSValue for const class member function in fwrapper. PassThis is ignored and treated as true */
  638. template <typename R, class T, typename... Args, R (T::*F)(Args...) const, bool PassThis/*=ignored*/>
  639. struct js_traits<fwrapper<F, PassThis>>
  640. {
  641. static JSValue wrap(JSContext * ctx, fwrapper<F, PassThis> fw) noexcept
  642. {
  643. return JS_NewCFunction(ctx, [](JSContext * ctx, JSValueConst this_value, int argc,
  644. JSValueConst * argv) noexcept -> JSValue {
  645. return detail::wrap_this_call<R, std::shared_ptr<T>, Args...>(ctx, F, this_value, argc, argv);
  646. }, fw.name, sizeof...(Args));
  647. }
  648. };
  649. /** A wrapper type for constructor of type T with arguments Args.
  650. * Compilation fails if no such constructor is defined.
  651. * @tparam Args constructor arguments
  652. */
  653. template <class T, typename... Args>
  654. struct ctor_wrapper
  655. {
  656. static_assert(std::is_constructible<T, Args...>::value, "no such constructor!");
  657. /// "name" property of JS constructor object
  658. const char * name = nullptr;
  659. };
  660. namespace detail {
  661. /// equivalent to JS_GetPropertyStr(ctx, this_value, "prototype");
  662. inline JSValue GetPropertyPrototype(JSContext * ctx, JSValueConst this_value)
  663. {
  664. // constant atom: doesn't need to be freed and doesn't change with context
  665. static const JSAtom JS_ATOM_prototype = JS_NewAtom(ctx, "prototype");
  666. return JS_GetProperty(ctx, this_value, JS_ATOM_prototype);
  667. }
  668. } // namespace detail
  669. /** Conversion to JSValue for ctor_wrapper. */
  670. template <class T, typename... Args>
  671. struct js_traits<ctor_wrapper<T, Args...>>
  672. {
  673. static JSValue wrap(JSContext * ctx, ctor_wrapper<T, Args...> cw) noexcept
  674. {
  675. return JS_NewCFunction2(ctx, [](JSContext * ctx, JSValueConst this_value, int argc,
  676. JSValueConst * argv) noexcept -> JSValue {
  677. if(js_traits<std::shared_ptr<T>>::QJSClassId == 0) // not registered
  678. {
  679. #if defined(__cpp_rtti)
  680. // automatically register class on first use (no prototype)
  681. js_traits<std::shared_ptr<T>>::register_class(ctx, typeid(T).name());
  682. #else
  683. JS_ThrowTypeError(ctx, "quickjspp ctor_wrapper<T>::wrap: Class is not registered");
  684. return JS_EXCEPTION;
  685. #endif
  686. }
  687. auto proto = detail::GetPropertyPrototype(ctx, this_value);
  688. if(JS_IsException(proto))
  689. return proto;
  690. auto jsobj = JS_NewObjectProtoClass(ctx, proto, js_traits<std::shared_ptr<T>>::QJSClassId);
  691. JS_FreeValue(ctx, proto);
  692. if(JS_IsException(jsobj))
  693. return jsobj;
  694. try
  695. {
  696. std::shared_ptr<T> ptr = std::apply(std::make_shared<T, Args...>, detail::unwrap_args<Args...>(ctx, argc, argv));
  697. JS_SetOpaque(jsobj, new std::shared_ptr<T>(std::move(ptr)));
  698. return jsobj;
  699. }
  700. catch (exception)
  701. {
  702. JS_FreeValue(ctx, jsobj);
  703. return JS_EXCEPTION;
  704. }
  705. catch (std::exception const & err)
  706. {
  707. JS_FreeValue(ctx, jsobj);
  708. JS_ThrowInternalError(ctx, "%s", err.what());
  709. return JS_EXCEPTION;
  710. }
  711. catch (...)
  712. {
  713. JS_FreeValue(ctx, jsobj);
  714. JS_ThrowInternalError(ctx, "Unknown error");
  715. return JS_EXCEPTION;
  716. }
  717. // return detail::wrap_call<std::shared_ptr<T>, Args...>(ctx, std::make_shared<T, Args...>, argv);
  718. }, cw.name, sizeof...(Args), JS_CFUNC_constructor, 0);
  719. }
  720. };
  721. /** Conversions for std::shared_ptr<T>. Empty shared_ptr corresponds to JS_NULL.
  722. * T should be registered to a context before conversions.
  723. * @tparam T class type
  724. */
  725. template <class T>
  726. struct js_traits<std::shared_ptr<T>>
  727. {
  728. /// Registered class id in QuickJS.
  729. inline static JSClassID QJSClassId = 0;
  730. /// Signature of the function to obtain the std::shared_ptr from the JSValue.
  731. using ptr_cast_fcn_t = std::function<std::shared_ptr<T>(JSContext*, JSValueConst)>;
  732. /// Used by registerDerivedClass to register new derived classes with this class' base type.
  733. inline static std::function<void(JSClassID, ptr_cast_fcn_t)> registerWithBase;
  734. /// Mapping between derived class' JSClassID and function to obtain the std::shared_ptr from the JSValue.
  735. inline static std::unordered_map<JSClassID, ptr_cast_fcn_t> ptrCastFcnMap;
  736. /** Register a class as a derived class.
  737. *
  738. * @tparam D type of the derived class
  739. * @param derived_class_id class id of the derived class
  740. * @param ptr_cast_fcn function to obtain a std::shared_ptr from the JSValue
  741. */
  742. template<typename D>
  743. static void registerDerivedClass(JSClassID derived_class_id, ptr_cast_fcn_t ptr_cast_fcn) {
  744. static_assert(std::is_base_of<T,D>::value && !std::is_same<T,D>::value, "Type is not a derived class");
  745. using derived_ptr_cast_fcn_t = typename js_traits<std::shared_ptr<D>>::ptr_cast_fcn_t;
  746. // Register how to obtain the std::shared_ptr from the derived class.
  747. ptrCastFcnMap[derived_class_id] = ptr_cast_fcn;
  748. // Propagate the registration to our base class (if any).
  749. if (registerWithBase) registerWithBase(derived_class_id, ptr_cast_fcn);
  750. // Instrument the derived class so that it can propagate new derived classes to us.
  751. auto old_registerWithBase = js_traits<std::shared_ptr<D>>::registerWithBase;
  752. js_traits<std::shared_ptr<D>>::registerWithBase =
  753. [old_registerWithBase = std::move(old_registerWithBase)]
  754. (JSClassID derived_class_id, derived_ptr_cast_fcn_t derived_ptr_cast_fcn){
  755. if (old_registerWithBase) old_registerWithBase(derived_class_id, derived_ptr_cast_fcn);
  756. registerDerivedClass<D>(derived_class_id, [derived_cast_fcn = std::move(derived_ptr_cast_fcn)](JSContext * ctx, JSValueConst v) {
  757. return std::shared_ptr<T>(derived_cast_fcn(ctx, v));
  758. });
  759. };
  760. }
  761. template <typename B>
  762. static
  763. std::enable_if_t<std::is_same_v<B, T> || std::is_same_v<B, void>>
  764. ensureCanCastToBase() { }
  765. template <typename B>
  766. static
  767. std::enable_if_t<!std::is_same_v<B, T> && !std::is_same_v<B, void>>
  768. ensureCanCastToBase() {
  769. static_assert(std::is_base_of_v<B, T>, "Type is not a derived class");
  770. if(js_traits<std::shared_ptr<T>>::QJSClassId == 0)
  771. JS_NewClassID(&js_traits<std::shared_ptr<T>>::QJSClassId);
  772. js_traits<std::shared_ptr<B>>::template registerDerivedClass<T>(QJSClassId, unwrap);
  773. }
  774. template <auto M>
  775. static void ensureCanCastToBase() {
  776. ensureCanCastToBase<detail::class_from_member_pointer_t<decltype(M)>>();
  777. }
  778. /** Stores offsets to qjs::Value members of T.
  779. * These values should be marked by class_registrar::mark for QuickJS garbage collector
  780. * so that the cycle removal algorithm can find the other objects referenced by this object.
  781. */
  782. static inline std::vector<Value T::*> markOffsets;
  783. /** Register class in QuickJS context.
  784. *
  785. * @param ctx context
  786. * @param name class name
  787. * @param proto class prototype or JS_NULL
  788. * @param call QJS call function. see quickjs doc
  789. * @param exotic pointer to QJS exotic methods(static lifetime) which allow custom property handling. see quickjs doc
  790. * @throws exception
  791. */
  792. static void register_class(JSContext * ctx, const char * name, JSValue proto = JS_NULL,
  793. JSClassCall * call = nullptr, JSClassExoticMethods * exotic = nullptr)
  794. {
  795. if(QJSClassId == 0)
  796. {
  797. JS_NewClassID(&QJSClassId);
  798. }
  799. auto rt = JS_GetRuntime(ctx);
  800. if(!JS_IsRegisteredClass(rt, QJSClassId))
  801. {
  802. JSClassGCMark * marker = nullptr;
  803. if(!markOffsets.empty())
  804. {
  805. marker = [](JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) {
  806. auto pptr = static_cast<std::shared_ptr<T> *>(JS_GetOpaque(val, QJSClassId));
  807. assert(pptr);
  808. const T * ptr = pptr->get();
  809. assert(ptr);
  810. for(Value T::* member : markOffsets)
  811. {
  812. JS_MarkValue(rt, (*ptr.*member).v, mark_func);
  813. }
  814. };
  815. }
  816. JSClassDef def{
  817. name,
  818. // destructor (finalizer)
  819. [](JSRuntime * rt, JSValue obj) noexcept {
  820. auto pptr = static_cast<std::shared_ptr<T> *>(JS_GetOpaque(obj, QJSClassId));
  821. delete pptr;
  822. },
  823. // mark
  824. marker,
  825. // call
  826. call,
  827. // exotic
  828. exotic
  829. };
  830. int e = JS_NewClass(rt, QJSClassId, &def);
  831. if(e < 0)
  832. {
  833. JS_ThrowInternalError(ctx, "Can't register class %s", name);
  834. throw exception{ctx};
  835. }
  836. }
  837. JS_SetClassProto(ctx, QJSClassId, proto);
  838. }
  839. /** Create a JSValue from std::shared_ptr<T>.
  840. * Creates an object with class if #QJSClassId and sets its opaque pointer to a new copy of #ptr.
  841. */
  842. static JSValue wrap(JSContext * ctx, std::shared_ptr<T> ptr)
  843. {
  844. if(!ptr)
  845. return JS_NULL;
  846. if(QJSClassId == 0) // not registered
  847. {
  848. #if defined(__cpp_rtti)
  849. // automatically register class on first use (no prototype)
  850. register_class(ctx, typeid(T).name());
  851. #else
  852. JS_ThrowTypeError(ctx, "quickjspp std::shared_ptr<T>::wrap: Class is not registered");
  853. return JS_EXCEPTION;
  854. #endif
  855. }
  856. auto jsobj = JS_NewObjectClass(ctx, QJSClassId);
  857. if(JS_IsException(jsobj))
  858. return jsobj;
  859. auto pptr = new std::shared_ptr<T>(std::move(ptr));
  860. JS_SetOpaque(jsobj, pptr);
  861. return jsobj;
  862. }
  863. /// @throws exception if #v doesn't have the correct class id
  864. static std::shared_ptr<T> unwrap(JSContext * ctx, JSValueConst v)
  865. {
  866. std::shared_ptr<T> ptr = nullptr;
  867. if (JS_IsNull(v)) {
  868. return ptr;
  869. }
  870. auto obj_class_id = JS_GetClassID(v);
  871. if (obj_class_id == QJSClassId) {
  872. // The JS object is of class T
  873. void * opaque = JS_GetOpaque2(ctx, v, obj_class_id);
  874. assert(opaque && "No opaque pointer in object");
  875. ptr = *static_cast<std::shared_ptr<T> *>(opaque);
  876. } else if (ptrCastFcnMap.count(obj_class_id)) {
  877. // The JS object is of a class derived from T
  878. ptr = ptrCastFcnMap[obj_class_id](ctx, v);
  879. } else {
  880. // The JS object does not derives from T
  881. JS_ThrowTypeError(ctx, "Expected type %s, got object with classid %d",
  882. QJSPP_TYPENAME(T), obj_class_id);
  883. throw exception{ctx};
  884. }
  885. if(!ptr) {
  886. JS_ThrowInternalError(ctx, "Object's opaque pointer is NULL");
  887. throw exception{ctx};
  888. }
  889. return ptr;
  890. }
  891. };
  892. /** Conversions for non-owning pointers to class T. nullptr corresponds to JS_NULL.
  893. * @tparam T class type
  894. */
  895. template <class T>
  896. struct js_traits<T *, std::enable_if_t<std::is_class_v<T>>>
  897. {
  898. static JSValue wrap(JSContext * ctx, T * ptr)
  899. {
  900. if (ptr == nullptr) {
  901. return JS_NULL;
  902. }
  903. if(js_traits<std::shared_ptr<T>>::QJSClassId == 0) // not registered
  904. {
  905. #if defined(__cpp_rtti)
  906. // If you have an error here with T=JSValueConst
  907. // it probably means you are passing JSValueConst to where JSValue is expected
  908. js_traits<std::shared_ptr<T>>::register_class(ctx, typeid(T).name());
  909. #else
  910. JS_ThrowTypeError(ctx, "quickjspp js_traits<T *>::wrap: Class is not registered");
  911. return JS_EXCEPTION;
  912. #endif
  913. }
  914. auto jsobj = JS_NewObjectClass(ctx, js_traits<std::shared_ptr<T>>::QJSClassId);
  915. if(JS_IsException(jsobj))
  916. return jsobj;
  917. // shared_ptr with empty deleter since we don't own T*
  918. auto pptr = new std::shared_ptr<T>(ptr, [](T *) {});
  919. JS_SetOpaque(jsobj, pptr);
  920. return jsobj;
  921. }
  922. static T * unwrap(JSContext * ctx, JSValueConst v)
  923. {
  924. if (JS_IsNull(v)) {
  925. return nullptr;
  926. }
  927. auto ptr = js_traits<std::shared_ptr<T>>::unwrap(ctx, v);
  928. return ptr.get();
  929. }
  930. };
  931. /** Conversions for enums. */
  932. template <typename E>
  933. struct js_traits<E, std::enable_if_t<std::is_enum_v<E>>> {
  934. using T = std::underlying_type_t<E>;
  935. static E unwrap(JSContext* ctx, JSValue v) noexcept {
  936. return static_cast<E>(js_traits<T>::unwrap(ctx, v));
  937. }
  938. static JSValue wrap(JSContext* ctx, E t) noexcept {
  939. return js_traits<T>::wrap(ctx, static_cast<T>(t));;
  940. }
  941. };
  942. namespace detail {
  943. /** A faster std::function-like object with type erasure.
  944. * Used to convert any callable objects (including lambdas) to JSValue.
  945. */
  946. struct function
  947. {
  948. JSValue
  949. (* invoker)(function * self, JSContext * ctx, JSValueConst this_value, int argc, JSValueConst * argv) = nullptr;
  950. void (* destroyer)(function * self) = nullptr;
  951. alignas(std::max_align_t) char functor[];
  952. template <typename Functor>
  953. static function * create(JSRuntime * rt, Functor&& f)
  954. {
  955. using Functor_t = std::decay_t<Functor>;
  956. auto fptr = static_cast<function *>(js_malloc_rt(rt, sizeof(function) + sizeof(Functor_t)));
  957. if(!fptr)
  958. throw std::bad_alloc{};
  959. new(fptr) function;
  960. auto functorptr = reinterpret_cast<Functor_t *>(fptr->functor);
  961. new(functorptr) Functor_t(std::forward<Functor>(f));
  962. fptr->destroyer = nullptr;
  963. if constexpr(!std::is_trivially_destructible_v<Functor_t>)
  964. {
  965. fptr->destroyer = [](function * fptr) {
  966. auto functorptr = reinterpret_cast<Functor_t *>(fptr->functor);
  967. functorptr->~Functor_t();
  968. };
  969. }
  970. return fptr;
  971. }
  972. };
  973. static_assert(std::is_trivially_destructible_v<function>);
  974. }
  975. template <>
  976. struct js_traits<detail::function>
  977. {
  978. inline static JSClassID QJSClassId = 0;
  979. // TODO: replace ctx with rt
  980. static void register_class(JSContext * ctx, const char * name)
  981. {
  982. if(QJSClassId == 0)
  983. {
  984. JS_NewClassID(&QJSClassId);
  985. }
  986. auto rt = JS_GetRuntime(ctx);
  987. if(JS_IsRegisteredClass(rt, QJSClassId))
  988. return;
  989. JSClassDef def{
  990. name,
  991. // destructor
  992. [](JSRuntime * rt, JSValue obj) noexcept {
  993. auto fptr = static_cast<detail::function *>(JS_GetOpaque(obj, QJSClassId));
  994. assert(fptr);
  995. if(fptr->destroyer)
  996. fptr->destroyer(fptr);
  997. js_free_rt(rt, fptr);
  998. },
  999. nullptr, // mark
  1000. // call
  1001. [](JSContext * ctx, JSValueConst func_obj, JSValueConst this_val, int argc,
  1002. JSValueConst * argv, int flags) -> JSValue {
  1003. auto ptr = static_cast<detail::function *>(JS_GetOpaque2(ctx, func_obj, QJSClassId));
  1004. if(!ptr)
  1005. return JS_EXCEPTION;
  1006. return ptr->invoker(ptr, ctx, this_val, argc, argv);
  1007. },
  1008. nullptr
  1009. };
  1010. int e = JS_NewClass(rt, QJSClassId, &def);
  1011. if(e < 0)
  1012. throw std::runtime_error{"Cannot register C++ function class"};
  1013. }
  1014. };
  1015. /** Traits for accessing object properties.
  1016. * @tparam Key property key type (uint32 and strings are supported)
  1017. */
  1018. template <typename Key>
  1019. struct js_property_traits
  1020. {
  1021. static void set_property(JSContext * ctx, JSValue this_obj, Key key, JSValue value);
  1022. static JSValue get_property(JSContext * ctx, JSValue this_obj, Key key);
  1023. };
  1024. template <>
  1025. struct js_property_traits<const char *>
  1026. {
  1027. static void set_property(JSContext * ctx, JSValue this_obj, const char * name, JSValue value)
  1028. {
  1029. int err = JS_SetPropertyStr(ctx, this_obj, name, value);
  1030. if(err < 0)
  1031. throw exception{ctx};
  1032. }
  1033. static JSValue get_property(JSContext * ctx, JSValue this_obj, const char * name) noexcept
  1034. {
  1035. return JS_GetPropertyStr(ctx, this_obj, name);
  1036. }
  1037. };
  1038. template <>
  1039. struct js_property_traits<uint32_t>
  1040. {
  1041. static void set_property(JSContext * ctx, JSValue this_obj, uint32_t idx, JSValue value)
  1042. {
  1043. int err = JS_SetPropertyUint32(ctx, this_obj, idx, value);
  1044. if(err < 0)
  1045. throw exception{ctx};
  1046. }
  1047. static JSValue get_property(JSContext * ctx, JSValue this_obj, uint32_t idx) noexcept
  1048. {
  1049. return JS_GetPropertyUint32(ctx, this_obj, idx);
  1050. }
  1051. };
  1052. template <>
  1053. struct js_property_traits<int> : js_property_traits<uint32_t> {};
  1054. namespace detail {
  1055. template <typename Key>
  1056. struct property_proxy
  1057. {
  1058. JSContext * ctx;
  1059. JSValue this_obj;
  1060. Key key;
  1061. /** Conversion helper function */
  1062. template <typename T>
  1063. T as() const
  1064. {
  1065. return unwrap_free<T>(ctx, js_property_traits<Key>::get_property(ctx, this_obj, key));
  1066. }
  1067. /** Explicit conversion operator (to any type) */
  1068. template <typename T>
  1069. explicit operator T() const { return as<T>(); }
  1070. /** Implicit converion to qjs::Value */
  1071. operator Value() const; // defined later due to Value being incomplete type
  1072. /// noncopyable
  1073. property_proxy& operator =(property_proxy) = delete;
  1074. template <typename T>
  1075. property_proxy& operator =(T&& value)
  1076. {
  1077. js_property_traits<Key>::set_property(ctx, this_obj, key,
  1078. js_traits<std::decay_t<T>>::wrap(ctx, std::forward<T>(value)));
  1079. return *this;
  1080. }
  1081. template <typename Key2>
  1082. property_proxy<Key2> operator[](Key2 key2) const
  1083. {
  1084. return {ctx, as<JSValue>(), std::move(key2)};
  1085. }
  1086. ~property_proxy() noexcept { JS_FreeValue(ctx, this_obj); }
  1087. };
  1088. // class member variable getter/setter
  1089. template <auto M>
  1090. struct get_set {};
  1091. // M - member object
  1092. template <class T, typename R, R T::*M>
  1093. struct get_set<M>
  1094. {
  1095. using is_const = std::is_const<R>;
  1096. static const R& get(std::shared_ptr<T> ptr)
  1097. {
  1098. return *ptr.*M;
  1099. }
  1100. static R& set(std::shared_ptr<T> ptr, R value)
  1101. {
  1102. return *ptr.*M = std::move(value);
  1103. }
  1104. };
  1105. // M - static member object
  1106. template <typename R, R *M>
  1107. struct get_set<M>
  1108. {
  1109. using is_const = std::is_const<R>;
  1110. static const R& get(bool)
  1111. {
  1112. return *M;
  1113. }
  1114. static R& set(bool, R value)
  1115. {
  1116. return *M = std::move(value);
  1117. }
  1118. };
  1119. } // namespace detail
  1120. /** JSValue with RAAI semantics.
  1121. * A wrapper over (JSValue v, JSContext * ctx).
  1122. * Calls JS_FreeValue(ctx, v) on destruction. Can be copied and moved.
  1123. * A JSValue can be released by either JSValue x = std::move(value); or JSValue x = value.release(), then the Value becomes invalid and FreeValue won't be called
  1124. * Can be converted to C++ type, for example: auto string = value.as<std::string>(); qjs::exception would be thrown on error
  1125. * Properties can be accessed (read/write): value["property1"] = 1; value[2] = "2";
  1126. */
  1127. class Value
  1128. {
  1129. public:
  1130. JSValue v;
  1131. JSContext * ctx = nullptr;
  1132. public:
  1133. /** Use context.newValue(val) instead */
  1134. template <typename T>
  1135. Value(JSContext * ctx, T&& val) : ctx(ctx)
  1136. {
  1137. v = js_traits<std::decay_t<T>>::wrap(ctx, std::forward<T>(val));
  1138. if(JS_IsException(v))
  1139. throw exception{ctx};
  1140. }
  1141. Value(JSValue&& v) noexcept : v(std::move(v)), ctx(nullptr) {}
  1142. Value(const Value& rhs) noexcept
  1143. {
  1144. ctx = rhs.ctx;
  1145. v = JS_DupValue(ctx, rhs.v);
  1146. }
  1147. Value(Value&& rhs) noexcept
  1148. {
  1149. std::swap(ctx, rhs.ctx);
  1150. v = rhs.v;
  1151. }
  1152. Value& operator =(Value rhs) noexcept
  1153. {
  1154. std::swap(ctx, rhs.ctx);
  1155. std::swap(v, rhs.v);
  1156. return *this;
  1157. }
  1158. bool operator ==(JSValueConst other) const
  1159. {
  1160. return JS_VALUE_GET_TAG(v) == JS_VALUE_GET_TAG(other) && JS_VALUE_GET_PTR(v) == JS_VALUE_GET_PTR(other);
  1161. }
  1162. bool operator !=(JSValueConst other) const { return !((*this) == other); }
  1163. /** Returns true if 2 values are the same (equality for arithmetic types or point to the same object) */
  1164. bool operator ==(const Value& rhs) const
  1165. {
  1166. return (*this == rhs.v);
  1167. }
  1168. bool operator !=(const Value& rhs) const { return !((*this) == rhs); }
  1169. ~Value()
  1170. {
  1171. if(ctx) JS_FreeValue(ctx, v);
  1172. }
  1173. bool isError() const { return JS_IsError(ctx, v); }
  1174. /** Conversion helper function: value.as<T>()
  1175. * @tparam T type to convert to
  1176. * @return type returned by js_traits<std::decay_t<T>>::unwrap that should be implicitly convertible to T
  1177. * */
  1178. template <typename T>
  1179. auto as() const { return js_traits<std::decay_t<T>>::unwrap(ctx, v); }
  1180. /** Explicit conversion: static_cast<T>(value) or (T)value */
  1181. template <typename T>
  1182. explicit operator T() const { return as<T>(); }
  1183. JSValue release() noexcept// dont call freevalue
  1184. {
  1185. ctx = nullptr;
  1186. return v;
  1187. }
  1188. /** Implicit conversion to JSValue (rvalue only). Example: JSValue v = std::move(value); */
  1189. operator JSValue()&& noexcept { return release(); }
  1190. /** Access JS properties. Returns proxy type which is implicitly convertible to qjs::Value */
  1191. template <typename Key>
  1192. detail::property_proxy<Key> operator [](Key key)
  1193. {
  1194. assert(ctx && "Trying to access properties of Value with no JSContext");
  1195. return {ctx, JS_DupValue(ctx, v), std::move(key)};
  1196. }
  1197. // add("f", []() {...});
  1198. template <typename Function>
  1199. Value& add(const char * name, Function&& f)
  1200. {
  1201. (*this)[name] = js_traits<decltype(std::function{std::forward<Function>(f)})>::wrap(ctx,
  1202. std::forward<Function>(f));
  1203. return *this;
  1204. }
  1205. // add<&f>("f");
  1206. // add<&T::f>("f");
  1207. template <auto F>
  1208. std::enable_if_t<std::is_member_function_pointer_v<decltype(F)> || std::is_function_v<std::remove_pointer_t<decltype(F)>>, Value&>
  1209. add(const char * name)
  1210. {
  1211. (*this)[name] = fwrapper<F>{name};
  1212. return *this;
  1213. }
  1214. // add_getter_setter<&T::get_member, &T::set_member>("member");
  1215. template <auto FGet, auto FSet>
  1216. Value& add_getter_setter(const char * name)
  1217. {
  1218. auto prop = JS_NewAtom(ctx, name);
  1219. using fgetter = fwrapper<FGet, true>;
  1220. using fsetter = fwrapper<FSet, true>;
  1221. int ret = JS_DefinePropertyGetSet(ctx, v, prop,
  1222. js_traits<fgetter>::wrap(ctx, fgetter{name}),
  1223. js_traits<fsetter>::wrap(ctx, fsetter{name}),
  1224. JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE | JS_PROP_ENUMERABLE
  1225. );
  1226. JS_FreeAtom(ctx, prop);
  1227. if(ret < 0)
  1228. throw exception{ctx};
  1229. return *this;
  1230. }
  1231. // add_getter<&T::get_member>("member");
  1232. template <auto FGet>
  1233. Value& add_getter(const char * name)
  1234. {
  1235. auto prop = JS_NewAtom(ctx, name);
  1236. using fgetter = fwrapper<FGet, true>;
  1237. int ret = JS_DefinePropertyGetSet(ctx, v, prop,
  1238. js_traits<fgetter>::wrap(ctx, fgetter{name}),
  1239. JS_UNDEFINED,
  1240. JS_PROP_CONFIGURABLE | JS_PROP_ENUMERABLE
  1241. );
  1242. JS_FreeAtom(ctx, prop);
  1243. if(ret < 0)
  1244. throw exception{ctx};
  1245. return *this;
  1246. }
  1247. // add<&T::member>("member");
  1248. template <auto M>
  1249. std::enable_if_t<std::is_member_object_pointer_v<decltype(M)>, Value&>
  1250. add(const char * name)
  1251. {
  1252. if constexpr (detail::get_set<M>::is_const::value)
  1253. return add_getter<detail::get_set<M>::get>(name);
  1254. else
  1255. return add_getter_setter<detail::get_set<M>::get, detail::get_set<M>::set>(name);
  1256. }
  1257. // add<&T::static_member>("static_member");
  1258. template <auto M>
  1259. std::enable_if_t<std::is_pointer_v<decltype(M)> && !std::is_function_v<std::remove_pointer_t<decltype(M)>> , Value&>
  1260. add(const char * name)
  1261. {
  1262. if constexpr (detail::get_set<M>::is_const::value)
  1263. return add_getter<detail::get_set<M>::get>(name);
  1264. else
  1265. return add_getter_setter<detail::get_set<M>::get, detail::get_set<M>::set>(name);
  1266. }
  1267. std::string
  1268. toJSON(const Value& replacer = JS_UNDEFINED, const Value& space = JS_UNDEFINED)
  1269. {
  1270. assert(ctx);
  1271. assert(!replacer.ctx || ctx == replacer.ctx);
  1272. assert(!space.ctx || ctx == space.ctx);
  1273. return (std::string) Value{ctx, JS_JSONStringify(ctx, v, replacer.v, space.v)};
  1274. }
  1275. /** same as Context::eval() but with this Value as 'this' */
  1276. Value evalThis(std::string_view buffer, const char * filename = "<evalThis>", int flags = 0)
  1277. {
  1278. assert(buffer.data()[buffer.size()] == '\0' && "eval buffer is not null-terminated"); // JS_Eval requirement
  1279. assert(ctx);
  1280. return Value{ctx, JS_EvalThis(ctx, v, buffer.data(), buffer.size(), filename, flags)};
  1281. }
  1282. };
  1283. /** Thin wrapper over JSRuntime * rt
  1284. * Calls JS_FreeRuntime on destruction. noncopyable.
  1285. */
  1286. class Runtime
  1287. {
  1288. public:
  1289. JSRuntime * rt;
  1290. Runtime()
  1291. {
  1292. rt = JS_NewRuntime();
  1293. if(!rt)
  1294. throw std::runtime_error{"qjs: Cannot create runtime"};
  1295. JS_SetHostUnhandledPromiseRejectionTracker(rt, promise_unhandled_rejection_tracker, NULL);
  1296. JS_SetModuleLoaderFunc(rt, nullptr, module_loader, nullptr);
  1297. }
  1298. // noncopyable
  1299. Runtime(const Runtime&) = delete;
  1300. ~Runtime()
  1301. {
  1302. JS_FreeRuntime(rt);
  1303. }
  1304. /// @return pointer to qjs::Context of the executed job or nullptr if no job is pending
  1305. Context * executePendingJob();
  1306. bool isJobPending() const {
  1307. return JS_IsJobPending(rt);
  1308. }
  1309. private:
  1310. static void promise_unhandled_rejection_tracker(JSContext *ctx, JSValueConst promise,
  1311. JSValueConst reason, JS_BOOL is_handled, void *opaque);
  1312. static JSModuleDef *module_loader(JSContext *ctx,
  1313. const char *module_name, void *opaque);
  1314. };
  1315. namespace detail {
  1316. inline std::optional<std::string> readFile(std::filesystem::path const & filepath)
  1317. {
  1318. if (!std::filesystem::exists(filepath)) return std::nullopt;
  1319. std::ifstream f(filepath, std::ios::in | std::ios::binary);
  1320. if (!f.is_open()) return std::nullopt;
  1321. std::stringstream sstream;
  1322. sstream << f.rdbuf();
  1323. return sstream.str();
  1324. }
  1325. inline std::string toUri(std::string_view filename) {
  1326. auto fname = std::string{filename};
  1327. if (fname.find("://") < fname.find("/")) return fname;
  1328. auto fpath = std::filesystem::path(fname);
  1329. if (!fpath.is_absolute()) {
  1330. fpath = "." / fpath;
  1331. }
  1332. fpath = std::filesystem::weakly_canonical(fpath);
  1333. fname = "file://" + fpath.generic_string();
  1334. return fname;
  1335. }
  1336. }
  1337. /** Wrapper over JSContext * ctx
  1338. * Calls JS_SetContextOpaque(ctx, this); on construction and JS_FreeContext on destruction
  1339. */
  1340. class Context
  1341. {
  1342. public:
  1343. JSContext * ctx;
  1344. /** Module wrapper
  1345. * Workaround for lack of opaque pointer for module load function by keeping a list of modules in qjs::Context.
  1346. */
  1347. class Module
  1348. {
  1349. friend class Context;
  1350. JSModuleDef * m;
  1351. JSContext * ctx;
  1352. const char * name;
  1353. using nvp = std::pair<const char *, Value>;
  1354. std::vector<nvp> exports;
  1355. public:
  1356. Module(JSContext * ctx, const char * name) : ctx(ctx), name(name)
  1357. {
  1358. m = JS_NewCModule(ctx, name, [](JSContext * ctx, JSModuleDef * m) noexcept {
  1359. auto& context = Context::get(ctx);
  1360. auto it = std::find_if(context.modules.begin(), context.modules.end(),
  1361. [m](const Module& module) { return module.m == m; });
  1362. if(it == context.modules.end())
  1363. return -1;
  1364. for(const auto& e : it->exports)
  1365. {
  1366. if(JS_SetModuleExport(ctx, m, e.first, JS_DupValue(ctx, e.second.v)) != 0)
  1367. return -1;
  1368. }
  1369. return 0;
  1370. });
  1371. if(!m)
  1372. throw exception{ctx};
  1373. }
  1374. Module& add(const char * name, JSValue&& value)
  1375. {
  1376. exports.push_back({name, {ctx, std::move(value)}});
  1377. JS_AddModuleExport(ctx, m, name);
  1378. return *this;
  1379. }
  1380. template <typename T>
  1381. Module& add(const char * name, T&& value)
  1382. {
  1383. return add(name, js_traits<T>::wrap(ctx, std::forward<T>(value)));
  1384. }
  1385. Module(const Module&) = delete;
  1386. Module(Module&&) = default;
  1387. //Module& operator=(Module&&) = default;
  1388. // function wrappers
  1389. /** Add free function F.
  1390. * Example:
  1391. * module.function<static_cast<double (*)(double)>(&::sin)>("sin");
  1392. */
  1393. template <auto F>
  1394. Module& function(const char * name)
  1395. {
  1396. return add(name, qjs::fwrapper<F>{name});
  1397. }
  1398. /** Add function object f.
  1399. * Slower than template version.
  1400. * Example: module.function("sin", [](double x) { return ::sin(x); });
  1401. */
  1402. template <typename F>
  1403. Module& function(const char * name, F&& f)
  1404. {
  1405. return add(name, js_traits<decltype(std::function{std::forward<F>(f)})>::wrap(ctx, std::forward<F>(f)));
  1406. }
  1407. // class register wrapper
  1408. private:
  1409. /** Helper class to register class members and constructors.
  1410. * See fun, constructor.
  1411. * Actual registration occurs at object destruction.
  1412. */
  1413. template <class T>
  1414. class class_registrar
  1415. {
  1416. const char * name;
  1417. qjs::Value prototype;
  1418. qjs::Context::Module& module;
  1419. qjs::Context& context;
  1420. qjs::Value ctor; // last added constructor
  1421. public:
  1422. explicit class_registrar(const char * name, qjs::Context::Module& module, qjs::Context& context) :
  1423. name(name),
  1424. prototype(context.newObject()),
  1425. module(module),
  1426. context(context),
  1427. ctor(JS_NULL)
  1428. {
  1429. }
  1430. class_registrar(const class_registrar&) = delete;
  1431. /** Add functional object f
  1432. */
  1433. template <typename F>
  1434. class_registrar& fun(const char * name, F&& f)
  1435. {
  1436. prototype[name] = std::forward<F>(f);
  1437. return *this;
  1438. }
  1439. /** Add class member function or class member variable F
  1440. * Example:
  1441. * struct T { int var; int func(); }
  1442. * auto& module = context.addModule("module");
  1443. * module.class_<T>("T").fun<&T::var>("var").fun<&T::func>("func");
  1444. */
  1445. template <auto F>
  1446. class_registrar& fun(const char * name)
  1447. {
  1448. js_traits<std::shared_ptr<T>>::template ensureCanCastToBase<F>();
  1449. prototype.add<F>(name);
  1450. return *this;
  1451. }
  1452. /** Add a static member or function to the last added constructor.
  1453. * Example:
  1454. * struct T { static int var; static int func(); }
  1455. * module.class_<T>("T").contructor<>("T").static_fun<&T::var>("var").static_fun<&T::func>("func");
  1456. */
  1457. template <auto F>
  1458. class_registrar& static_fun(const char * name)
  1459. {
  1460. assert(!JS_IsNull(ctor.v) && "You should call .constructor before .static_fun");
  1461. js_traits<qjs::shared_ptr<T>>::template ensureCanCastToBase<F>();
  1462. ctor.add<F>(name);
  1463. return *this;
  1464. }
  1465. /** Add a property with custom getter and setter.
  1466. * Example:
  1467. * module.class_<T>("T").property<&T::getX, &T::setX>("x");
  1468. */
  1469. template <auto FGet, auto FSet = nullptr>
  1470. class_registrar& property(const char * name)
  1471. {
  1472. js_traits<std::shared_ptr<T>>::template ensureCanCastToBase<FGet>();
  1473. js_traits<std::shared_ptr<T>>::template ensureCanCastToBase<FSet>();
  1474. if constexpr (std::is_same_v<decltype(FSet), std::nullptr_t>)
  1475. prototype.add_getter<FGet>(name);
  1476. else
  1477. prototype.add_getter_setter<FGet, FSet>(name);
  1478. return *this;
  1479. }
  1480. /** Add class constructor
  1481. * @tparam Args contructor arguments
  1482. * @param name constructor name (if not specified class name will be used)
  1483. */
  1484. template <typename... Args>
  1485. class_registrar& constructor(const char * name = nullptr)
  1486. {
  1487. if(!name)
  1488. name = this->name;
  1489. ctor = context.newValue(qjs::ctor_wrapper<T, Args...>{name});
  1490. JS_SetConstructor(context.ctx, ctor.v, prototype.v);
  1491. module.add(name, qjs::Value{ctor});
  1492. return *this;
  1493. }
  1494. /** Sets the base class
  1495. * @tparam B base class
  1496. */
  1497. template <class B>
  1498. class_registrar& base()
  1499. {
  1500. static_assert(!std::is_same_v<B, T>, "Type cannot be a base of itself");
  1501. assert(js_traits<std::shared_ptr<B>>::QJSClassId && "base class is not registered");
  1502. js_traits<std::shared_ptr<T>>::template ensureCanCastToBase<B>();
  1503. auto base_proto = JS_GetClassProto(context.ctx, js_traits<std::shared_ptr<B>>::QJSClassId);
  1504. int err = JS_SetPrototype(context.ctx, prototype.v, base_proto);
  1505. JS_FreeValue(context.ctx, base_proto);
  1506. if(err < 0)
  1507. throw exception{context.ctx};
  1508. return *this;
  1509. }
  1510. /** All qjs::Value members of T should be marked by mark<> for QuickJS garbage collector
  1511. * so that the cycle removal algorithm can find the other objects referenced by this object.
  1512. */
  1513. template <Value T::* V>
  1514. class_registrar& mark()
  1515. {
  1516. js_traits<std::shared_ptr<T>>::markOffsets.push_back(V);
  1517. return *this;
  1518. }
  1519. ~class_registrar()
  1520. {
  1521. context.registerClass<T>(name, std::move(prototype));
  1522. }
  1523. };
  1524. public:
  1525. /** Add class to module.
  1526. * See \ref class_registrar.
  1527. */
  1528. template <class T>
  1529. class_registrar<T> class_(const char * name)
  1530. {
  1531. return class_registrar<T>{name, *this, qjs::Context::get(ctx)};
  1532. }
  1533. };
  1534. std::vector<Module> modules;
  1535. private:
  1536. void init()
  1537. {
  1538. JS_SetContextOpaque(ctx, this);
  1539. js_traits<detail::function>::register_class(ctx, "C++ function");
  1540. }
  1541. public:
  1542. Context(Runtime& rt) : Context(rt.rt) {}
  1543. Context(JSRuntime * rt)
  1544. {
  1545. ctx = JS_NewContext(rt);
  1546. if(!ctx)
  1547. throw std::runtime_error{"qjs: Cannot create context"};
  1548. init();
  1549. }
  1550. Context(JSContext * ctx) : ctx{ctx}
  1551. {
  1552. init();
  1553. }
  1554. // noncopyable
  1555. Context(const Context&) = delete;
  1556. ~Context()
  1557. {
  1558. modules.clear();
  1559. JS_FreeContext(ctx);
  1560. }
  1561. /** Callback triggered when a Promise rejection won't ever be handled */
  1562. std::function<void(Value)> onUnhandledPromiseRejection;
  1563. /** Data type returned by the moduleLoader function */
  1564. struct ModuleData {
  1565. std::optional<std::string> source, url;
  1566. ModuleData() : source(std::nullopt), url(std::nullopt) {}
  1567. ModuleData(std::optional<std::string> source) : source(std::move(source)), url(std::nullopt) {}
  1568. ModuleData(std::optional<std::string> url, std::optional<std::string> source) : source(std::move(source)), url(std::move(url)) {}
  1569. };
  1570. /** Function called to obtain the source of a module */
  1571. std::function<ModuleData(std::string_view)> moduleLoader =
  1572. [](std::string_view filename) -> ModuleData {
  1573. return ModuleData{ detail::toUri(filename), detail::readFile(filename) };
  1574. };
  1575. template <typename Function>
  1576. void enqueueJob(Function && job);
  1577. /** Create module and return a reference to it */
  1578. Module& addModule(const char * name)
  1579. {
  1580. modules.emplace_back(ctx, name);
  1581. return modules.back();
  1582. }
  1583. /** returns globalThis */
  1584. Value global() { return Value{ctx, JS_GetGlobalObject(ctx)}; }
  1585. /** returns new Object() */
  1586. Value newObject() { return Value{ctx, JS_NewObject(ctx)}; }
  1587. /** returns JS value converted from c++ object val */
  1588. template <typename T>
  1589. Value newValue(T&& val) { return Value{ctx, std::forward<T>(val)}; }
  1590. /** returns current exception associated with context and clears it. Should be called when qjs::exception is caught */
  1591. Value getException() { return Value{ctx, JS_GetException(ctx)}; }
  1592. /** Register class T for conversions to/from std::shared_ptr<T> to work.
  1593. * Wherever possible module.class_<T>("T")... should be used instead.
  1594. * @tparam T class type
  1595. * @param name class name in JS engine
  1596. * @param proto JS class prototype or JS_UNDEFINED
  1597. */
  1598. template <class T>
  1599. void registerClass(const char * name, JSValue proto = JS_NULL)
  1600. {
  1601. js_traits<std::shared_ptr<T>>::register_class(ctx, name, proto);
  1602. }
  1603. /// @see JS_Eval
  1604. Value eval(std::string_view buffer, const char * filename = "<eval>", int flags = 0)
  1605. {
  1606. assert(buffer.data()[buffer.size()] == '\0' && "eval buffer is not null-terminated"); // JS_Eval requirement
  1607. JSValue v = JS_Eval(ctx, buffer.data(), buffer.size(), filename, flags);
  1608. return Value{ctx, std::move(v)};
  1609. }
  1610. Value evalFile(const char * filename, int flags = 0)
  1611. {
  1612. auto buf = detail::readFile(filename);
  1613. if (!buf)
  1614. throw std::runtime_error{std::string{"evalFile: can't read file: "} + filename};
  1615. return eval(*buf, filename, flags);
  1616. }
  1617. /// @see JS_ParseJSON2
  1618. Value fromJSON(std::string_view buffer, const char * filename = "<fromJSON>", int flags = 0)
  1619. {
  1620. assert(buffer.data()[buffer.size()] == '\0' &&
  1621. "fromJSON buffer is not null-terminated"); // JS_ParseJSON requirement
  1622. return Value{ctx, JS_ParseJSON2(ctx, buffer.data(), buffer.size(), filename, flags)};
  1623. }
  1624. /** Get qjs::Context from JSContext opaque pointer */
  1625. static Context& get(JSContext * ctx)
  1626. {
  1627. void * ptr = JS_GetContextOpaque(ctx);
  1628. assert(ptr);
  1629. return *static_cast<Context *>(ptr);
  1630. }
  1631. };
  1632. /** Conversion traits for Value.
  1633. */
  1634. template <>
  1635. struct js_traits<Value>
  1636. {
  1637. static Value unwrap(JSContext * ctx, JSValueConst v)
  1638. {
  1639. return Value{ctx, JS_DupValue(ctx, v)};
  1640. }
  1641. static JSValue wrap(JSContext * ctx, Value v) noexcept
  1642. {
  1643. assert(JS_GetRuntime(ctx) == JS_GetRuntime(v.ctx));
  1644. return v.release();
  1645. }
  1646. };
  1647. /** Convert to/from std::function. Actually accepts/returns callable object that is compatible with function<R (Args...)>.
  1648. * @tparam R return type
  1649. * @tparam Args argument types
  1650. */
  1651. template <typename R, typename... Args>
  1652. struct js_traits<std::function<R(Args...)>, int>
  1653. {
  1654. static auto unwrap(JSContext * ctx, JSValueConst fun_obj)
  1655. {
  1656. const int argc = sizeof...(Args);
  1657. if constexpr(argc == 0)
  1658. {
  1659. return [jsfun_obj = Value{ctx, JS_DupValue(ctx, fun_obj)}]() -> R {
  1660. JSValue result = JS_Call(jsfun_obj.ctx, jsfun_obj.v, JS_UNDEFINED, 0, nullptr);
  1661. if(JS_IsException(result))
  1662. throw exception{jsfun_obj.ctx};
  1663. return detail::unwrap_free<R>(jsfun_obj.ctx, result);
  1664. };
  1665. }
  1666. else
  1667. {
  1668. return [jsfun_obj = Value{ctx, JS_DupValue(ctx, fun_obj)}](Args ... args) -> R {
  1669. const int argc = sizeof...(Args);
  1670. JSValue argv[argc];
  1671. detail::wrap_args(jsfun_obj.ctx, argv, std::forward<Args>(args)...);
  1672. JSValue result = JS_Call(jsfun_obj.ctx, jsfun_obj.v, JS_UNDEFINED, argc,
  1673. const_cast<JSValueConst *>(argv));
  1674. for(int i = 0; i < argc; i++) JS_FreeValue(jsfun_obj.ctx, argv[i]);
  1675. if(JS_IsException(result))
  1676. throw exception{jsfun_obj.ctx};
  1677. return detail::unwrap_free<R>(jsfun_obj.ctx, result);
  1678. };
  1679. }
  1680. }
  1681. /** Convert from function object functor to JSValue.
  1682. * Uses detail::function for type-erasure.
  1683. */
  1684. template <typename Functor>
  1685. static JSValue wrap(JSContext * ctx, Functor&& functor) noexcept
  1686. {
  1687. using detail::function;
  1688. assert(js_traits<function>::QJSClassId);
  1689. auto obj = JS_NewObjectClass(ctx, js_traits<function>::QJSClassId);
  1690. if(JS_IsException(obj))
  1691. return obj;
  1692. try
  1693. {
  1694. auto fptr = function::create(JS_GetRuntime(ctx), std::forward<Functor>(functor));
  1695. fptr->invoker = [](function * self, JSContext * ctx, JSValueConst this_value, int argc,
  1696. JSValueConst * argv) {
  1697. assert(self);
  1698. auto f = reinterpret_cast<std::decay_t<Functor> *>(&self->functor);
  1699. return detail::wrap_call<R, Args...>(ctx, *f, argc, argv);
  1700. };
  1701. JS_SetOpaque(obj, fptr);
  1702. return obj;
  1703. }
  1704. catch(const std::exception& e)
  1705. {
  1706. JS_ThrowInternalError(ctx, "%s", e.what());
  1707. return JS_EXCEPTION;
  1708. }
  1709. catch(...)
  1710. {
  1711. JS_ThrowInternalError(ctx, "Unknown errror");
  1712. return JS_EXCEPTION;
  1713. }
  1714. }
  1715. };
  1716. namespace detail {
  1717. template<typename T, typename = void>
  1718. struct is_callable : std::is_function<T> { };
  1719. template<typename T>
  1720. struct is_callable<T, std::enable_if_t<std::is_same_v<decltype(void(&T::operator())), void>>> : std::true_type { };
  1721. template<typename T>
  1722. inline constexpr bool is_callable_v = is_callable<T>::value;
  1723. }
  1724. template <typename Function>
  1725. struct js_traits<Function, std::enable_if_t<detail::is_callable_v<Function>>> {
  1726. static auto unwrap(JSContext * ctx, JSValueConst fun_obj)
  1727. {
  1728. return js_traits<
  1729. decltype(std::function{std::declval<Function>()}),
  1730. int
  1731. >::unwrap(ctx, fun_obj);
  1732. }
  1733. template <typename Functor>
  1734. static JSValue wrap(JSContext * ctx, Functor&& functor)
  1735. {
  1736. return js_traits<
  1737. decltype(std::function{std::declval<Function>()}),
  1738. int
  1739. >::wrap(ctx, std::forward<Functor>(functor));
  1740. }
  1741. };
  1742. /** Convert from std::vector<T> to Array and vice-versa. If Array holds objects that are non-convertible to T throws qjs::exception */
  1743. template <class T>
  1744. struct js_traits<std::vector<T>>
  1745. {
  1746. static JSValue wrap(JSContext * ctx, const std::vector<T>& arr) noexcept
  1747. {
  1748. try
  1749. {
  1750. auto jsarray = Value{ctx, JS_NewArray(ctx)};
  1751. for(uint32_t i = 0; i < (uint32_t) arr.size(); i++)
  1752. jsarray[i] = arr[i];
  1753. return jsarray.release();
  1754. }
  1755. catch(exception)
  1756. {
  1757. return JS_EXCEPTION;
  1758. }
  1759. catch (std::exception const & err)
  1760. {
  1761. JS_ThrowInternalError(ctx, "%s", err.what());
  1762. return JS_EXCEPTION;
  1763. }
  1764. catch (...)
  1765. {
  1766. JS_ThrowInternalError(ctx, "Unknown error");
  1767. return JS_EXCEPTION;
  1768. }
  1769. }
  1770. static std::vector<T> unwrap(JSContext * ctx, JSValueConst jsarr)
  1771. {
  1772. int e = JS_IsArray(ctx, jsarr);
  1773. if(e == 0)
  1774. JS_ThrowTypeError(ctx, "js_traits<std::vector<T>>::unwrap expects array");
  1775. if(e <= 0)
  1776. throw exception{ctx};
  1777. Value jsarray{ctx, JS_DupValue(ctx, jsarr)};
  1778. std::vector<T> arr;
  1779. auto len = static_cast<int32_t>(jsarray["length"]);
  1780. arr.reserve((uint32_t) len);
  1781. for(uint32_t i = 0; i < (uint32_t) len; i++)
  1782. arr.push_back(static_cast<T>(jsarray[i]));
  1783. return arr;
  1784. }
  1785. };
  1786. template <typename U, typename V>
  1787. struct js_traits<std::pair<U, V>>
  1788. {
  1789. static JSValue wrap(JSContext * ctx, std::pair<U, V> obj) noexcept
  1790. {
  1791. try
  1792. {
  1793. auto jsarray = Value{ctx, JS_NewArray(ctx)};
  1794. jsarray[uint32_t(0)] = std::move(obj.first);
  1795. jsarray[uint32_t(1)] = std::move(obj.second);
  1796. return jsarray.release();
  1797. }
  1798. catch(exception)
  1799. {
  1800. return JS_EXCEPTION;
  1801. }
  1802. catch (std::exception const & err)
  1803. {
  1804. JS_ThrowInternalError(ctx, "%s", err.what());
  1805. return JS_EXCEPTION;
  1806. }
  1807. catch (...)
  1808. {
  1809. JS_ThrowInternalError(ctx, "Unknown error");
  1810. return JS_EXCEPTION;
  1811. }
  1812. }
  1813. static std::pair<U, V> unwrap(JSContext * ctx, JSValueConst jsarr)
  1814. {
  1815. int e = JS_IsArray(ctx, jsarr);
  1816. if(e == 0)
  1817. JS_ThrowTypeError(ctx, "js_traits<%s>::unwrap expects array", QJSPP_TYPENAME(std::pair<U, V>));
  1818. if(e <= 0)
  1819. throw exception{ctx};
  1820. Value jsarray{ctx, JS_DupValue(ctx, jsarr)};
  1821. const auto len = static_cast<uint32_t>(jsarray["length"]);
  1822. if(len != 2)
  1823. {
  1824. JS_ThrowTypeError(ctx, "js_traits<%s>::unwrap expected array of length 2, got length %d",
  1825. QJSPP_TYPENAME(std::pair<U, V>), len);
  1826. throw exception{ctx};
  1827. }
  1828. return std::pair<U, V>{
  1829. static_cast<U>(jsarray[uint32_t(0)]),
  1830. static_cast<V>(jsarray[uint32_t(1)])
  1831. };
  1832. }
  1833. };
  1834. /** Conversions for std::optional.
  1835. * Unlike other types does not throw on unwrap but returns nullopt.
  1836. * Converts std::nullopt to null.
  1837. */
  1838. template <typename T>
  1839. struct js_traits<std::optional<T>>
  1840. {
  1841. /** Wraps T or null. */
  1842. static JSValue wrap(JSContext * ctx, std::optional<T> obj) noexcept
  1843. {
  1844. if(obj)
  1845. return js_traits<std::decay_t<T>>::wrap(ctx, *obj);
  1846. return JS_NULL;
  1847. }
  1848. /** If conversion to T fails returns std::nullopt. */
  1849. static auto unwrap(JSContext * ctx, JSValueConst v) noexcept -> std::optional<decltype(js_traits<std::decay_t<T>>::unwrap(ctx, v))>
  1850. {
  1851. try
  1852. {
  1853. if(JS_IsNull(v))
  1854. return std::nullopt;
  1855. return js_traits<std::decay_t<T>>::unwrap(ctx, v);
  1856. }
  1857. catch(exception)
  1858. {
  1859. // ignore and clear exception
  1860. JS_FreeValue(ctx, JS_GetException(ctx));
  1861. }
  1862. return std::nullopt;
  1863. }
  1864. };
  1865. namespace detail {
  1866. template <typename Key>
  1867. property_proxy<Key>::operator Value() const
  1868. {
  1869. return as<Value>();
  1870. }
  1871. }
  1872. template <typename Function>
  1873. void Context::enqueueJob(Function && job) {
  1874. JSValue job_val = js_traits<std::function<void()>>::wrap(ctx, std::forward<Function>(job));
  1875. JSValueConst arg = job_val;
  1876. int err = JS_EnqueueJob(ctx, [](JSContext *ctx, int argc, JSValueConst *argv){
  1877. try
  1878. {
  1879. assert(argc >= 1);
  1880. js_traits<std::function<void()>>::unwrap(ctx, argv[0])();
  1881. }
  1882. catch (exception)
  1883. {
  1884. return JS_EXCEPTION;
  1885. }
  1886. catch (std::exception const & err)
  1887. {
  1888. JS_ThrowInternalError(ctx, "%s", err.what());
  1889. return JS_EXCEPTION;
  1890. }
  1891. catch (...)
  1892. {
  1893. JS_ThrowInternalError(ctx, "Unknown error");
  1894. return JS_EXCEPTION;
  1895. }
  1896. return JS_UNDEFINED;
  1897. }, 1, &arg);
  1898. JS_FreeValue(ctx, job_val);
  1899. if(err < 0)
  1900. throw exception{ctx};
  1901. }
  1902. inline Context & exception::context() const {
  1903. return Context::get(ctx);
  1904. }
  1905. inline Value exception::get() {
  1906. return context().getException();
  1907. }
  1908. inline void Runtime::promise_unhandled_rejection_tracker(JSContext *ctx, JSValueConst promise,
  1909. JSValueConst reason, JS_BOOL is_handled, void *opaque)
  1910. {
  1911. auto & context = Context::get(ctx);
  1912. if (context.onUnhandledPromiseRejection) {
  1913. context.onUnhandledPromiseRejection(context.newValue(JS_DupValue(ctx, reason)));
  1914. }
  1915. }
  1916. inline JSModuleDef * Runtime::module_loader(JSContext *ctx,
  1917. const char *module_name, void *opaque)
  1918. {
  1919. Context::ModuleData data;
  1920. auto & context = Context::get(ctx);
  1921. try {
  1922. if (context.moduleLoader) data = context.moduleLoader(module_name);
  1923. if (!data.source) {
  1924. JS_ThrowReferenceError(ctx, "could not load module filename '%s'", module_name);
  1925. return NULL;
  1926. }
  1927. if (!data.url) data.url = module_name;
  1928. // compile the module
  1929. auto func_val = context.eval(*data.source, module_name, JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY);
  1930. assert(JS_VALUE_GET_TAG(func_val.v) == JS_TAG_MODULE);
  1931. JSModuleDef * m = reinterpret_cast<JSModuleDef *>(JS_VALUE_GET_PTR(func_val.v));
  1932. // set import.meta
  1933. auto meta = context.newValue(JS_GetImportMeta(ctx, m));
  1934. meta["url"] = *data.url;
  1935. meta["main"] = false;
  1936. return m;
  1937. }
  1938. catch(exception)
  1939. {
  1940. return NULL;
  1941. }
  1942. catch (std::exception const & err)
  1943. {
  1944. JS_ThrowInternalError(ctx, "%s", err.what());
  1945. return NULL;
  1946. }
  1947. catch (...)
  1948. {
  1949. JS_ThrowInternalError(ctx, "Unknown error");
  1950. return NULL;
  1951. }
  1952. }
  1953. inline Context * Runtime::executePendingJob() {
  1954. JSContext * ctx;
  1955. auto err = JS_ExecutePendingJob(rt, &ctx);
  1956. if (err == 0) {
  1957. // There was no job to run
  1958. return nullptr;
  1959. } else if (err < 0) {
  1960. throw exception{ctx};
  1961. }
  1962. return &Context::get(ctx);
  1963. }
  1964. } // namespace qjs