@lucent-lang/lucent 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/app.plugin.js +92 -0
  2. package/bin/lucent.cjs +16 -0
  3. package/core/index.cjs +11 -0
  4. package/dist/bench-CR3E7e2e.js +304 -0
  5. package/dist/build-CAIrRSYA.js +7 -0
  6. package/dist/build-check-QozuNJm5.js +92 -0
  7. package/dist/check-1SCmYlqA.js +7 -0
  8. package/dist/clean-BVwpbJ5K.js +35 -0
  9. package/dist/cli.js +406 -0
  10. package/dist/codes-CIuwXt1S.js +260 -0
  11. package/dist/compiler.js +4 -0
  12. package/dist/confirm-BYF5eY3q.js +34 -0
  13. package/dist/dashboard-B-NzjjaN.js +91 -0
  14. package/dist/dev-BYWMP7oC.js +221 -0
  15. package/dist/diagnostic-BM9sXmwl.js +30 -0
  16. package/dist/diff-BTDo3Sro.js +44 -0
  17. package/dist/doctor-C0PrfWT6.js +230 -0
  18. package/dist/doctor-DdR4JnVc.js +30 -0
  19. package/dist/explain--vV1oaRl.js +35 -0
  20. package/dist/format-D9fnIMEx.js +53 -0
  21. package/dist/init-gIabu1S_.js +207 -0
  22. package/dist/live-steps-BGcWFM9M.js +79 -0
  23. package/dist/new-module-C2mtb0op.js +66 -0
  24. package/dist/package-manager-BnxwCySi.js +30 -0
  25. package/dist/package.json +3 -0
  26. package/dist/packages-HyskKIsY.js +150 -0
  27. package/dist/pipeline-CH7-2cxu.js +232 -0
  28. package/dist/project-D8tEet0k.js +150 -0
  29. package/dist/sdk-coverage-BoW_3Efd.js +64 -0
  30. package/dist/sdk-prefetch-mo04JDZV.js +89 -0
  31. package/dist/sdk-search-Iv4K3-8a.js +124 -0
  32. package/dist/sdk-show-CkrdlqlP.js +74 -0
  33. package/dist/sdks-Xoyiy2E4.js +28 -0
  34. package/dist/src-BD7f1_aZ.js +11493 -0
  35. package/dist/steps--_h-FFS7.js +24 -0
  36. package/dist/theme-uv5bc0aK.js +134 -0
  37. package/dist/tsconfig-S4uyLNzf.js +59 -0
  38. package/dist/version-bqpIO5sP.js +26 -0
  39. package/gradle/lucent-classpath.gradle +38 -0
  40. package/gradle/lucent-classpath.init.gradle +6 -0
  41. package/gradle/lucent.gradle +24 -0
  42. package/lib/core.cjs +106 -0
  43. package/lib/globals.d.ts +36 -0
  44. package/lib/sdk/android.d.ts +8 -0
  45. package/lib/sdk/core.d.ts +23 -0
  46. package/lib/sdk/ios.d.ts +33 -0
  47. package/lib/sdk/platform.d.ts +8 -0
  48. package/lib/sdk/thread.d.ts +8 -0
  49. package/metro/index.cjs +72 -0
  50. package/metro/index.d.ts +6 -0
  51. package/metro/transformer.cjs +70 -0
  52. package/package.json +61 -0
  53. package/runtime/cpp/lucent/abort.cpp +61 -0
  54. package/runtime/cpp/lucent/abort.h +54 -0
  55. package/runtime/cpp/lucent/array.h +442 -0
  56. package/runtime/cpp/lucent/async.h +236 -0
  57. package/runtime/cpp/lucent/builtins.cpp +163 -0
  58. package/runtime/cpp/lucent/bytes.h +129 -0
  59. package/runtime/cpp/lucent/console.h +17 -0
  60. package/runtime/cpp/lucent/core.h +129 -0
  61. package/runtime/cpp/lucent/date.cpp +441 -0
  62. package/runtime/cpp/lucent/date.h +82 -0
  63. package/runtime/cpp/lucent/equality.h +87 -0
  64. package/runtime/cpp/lucent/function.h +61 -0
  65. package/runtime/cpp/lucent/generator.h +244 -0
  66. package/runtime/cpp/lucent/helpers.h +226 -0
  67. package/runtime/cpp/lucent/jserror.cpp +51 -0
  68. package/runtime/cpp/lucent/jserror.h +66 -0
  69. package/runtime/cpp/lucent/jsi/convert.cpp +202 -0
  70. package/runtime/cpp/lucent/jsi/convert.h +612 -0
  71. package/runtime/cpp/lucent/jsi/host.cpp +322 -0
  72. package/runtime/cpp/lucent/jsi/host.h +166 -0
  73. package/runtime/cpp/lucent/json.cpp +243 -0
  74. package/runtime/cpp/lucent/json.h +164 -0
  75. package/runtime/cpp/lucent/json_parse.h +138 -0
  76. package/runtime/cpp/lucent/jsstring.cpp +874 -0
  77. package/runtime/cpp/lucent/jsstring.h +152 -0
  78. package/runtime/cpp/lucent/lucent.h +24 -0
  79. package/runtime/cpp/lucent/map.h +344 -0
  80. package/runtime/cpp/lucent/native.cpp +76 -0
  81. package/runtime/cpp/lucent/native.h +116 -0
  82. package/runtime/cpp/lucent/number.cpp +545 -0
  83. package/runtime/cpp/lucent/number.h +190 -0
  84. package/runtime/cpp/lucent/ops.h +165 -0
  85. package/runtime/cpp/lucent/platform/android.cpp +437 -0
  86. package/runtime/cpp/lucent/platform/android.h +117 -0
  87. package/runtime/cpp/lucent/platform/ios.h +297 -0
  88. package/runtime/cpp/lucent/platform/ios.mm +31 -0
  89. package/runtime/cpp/lucent/regexp.cpp +440 -0
  90. package/runtime/cpp/lucent/regexp.h +107 -0
  91. package/runtime/cpp/lucent/scheduler.cpp +153 -0
  92. package/runtime/cpp/lucent/scheduler.h +160 -0
  93. package/runtime/cpp/lucent/unicode_data.inc +4051 -0
  94. package/runtime/cpp/rn/LucentModule.cpp +35 -0
  95. package/runtime/cpp/rn/LucentModule.h +37 -0
  96. package/runtime/cpp/third_party/quickjs/LICENSE +22 -0
  97. package/runtime/cpp/third_party/quickjs/README.md +11 -0
  98. package/runtime/cpp/third_party/quickjs/cutils.c +641 -0
  99. package/runtime/cpp/third_party/quickjs/cutils.h +457 -0
  100. package/runtime/cpp/third_party/quickjs/libregexp-opcode.h +73 -0
  101. package/runtime/cpp/third_party/quickjs/libregexp.c +3448 -0
  102. package/runtime/cpp/third_party/quickjs/libregexp.h +65 -0
  103. package/runtime/cpp/third_party/quickjs/libunicode-table.h +5206 -0
  104. package/runtime/cpp/third_party/quickjs/libunicode.c +2124 -0
  105. package/runtime/cpp/third_party/quickjs/libunicode.h +196 -0
  106. package/runtime/js/index.d.ts +7 -0
  107. package/runtime/js/index.js +60 -0
  108. package/runtime/native/LucentNative.podspec +28 -0
  109. package/runtime/native/android/CMakeLists.txt +51 -0
  110. package/runtime/native/android/build.gradle +22 -0
  111. package/runtime/native/android/include/lucentnative.h +16 -0
  112. package/runtime/native/android/src/main/java/dev/lucent/LucentPackage.java +25 -0
  113. package/runtime/native/android/src/main/java/dev/lucent/NativeProxy.java +132 -0
  114. package/runtime/native/ios/LucentRegistration.mm +23 -0
  115. package/runtime/native/package.json +6 -0
  116. package/runtime/native/react-native.config.js +19 -0
  117. package/runtime/test/jsi/harness.cpp +181 -0
  118. package/schemas/build.schema.json +152 -0
  119. package/schemas/check.schema.json +74 -0
  120. package/schemas/doctor.schema.json +40 -0
  121. package/schemas/lucent.schema.json +43 -0
  122. package/schemas/sdk-prefetch.schema.json +37 -0
  123. package/schemas/sdk-search.schema.json +54 -0
  124. package/ts-plugin/index.d.ts +4 -0
  125. package/ts-plugin/index.js +99 -0
@@ -0,0 +1,612 @@
1
+ // Lucent runtime — conversions between JSI values and Lucent values.
2
+ //
3
+ // Values coming from JavaScript are validated against the declared Lucent
4
+ // type, because a JS caller can pass anything. Errors name the function and
5
+ // argument: "hash: argument 'input' must be a string".
6
+ //
7
+ // Arrays, records, maps and structs are copied across the boundary. Class
8
+ // instances keep their identity. All functions here run on the JS thread with
9
+ // the Lucent lock held.
10
+ #pragma once
11
+
12
+ #include <jsi/jsi.h>
13
+
14
+ #include <string>
15
+ #include <vector>
16
+
17
+ #include "../lucent.h"
18
+ #include "host.h"
19
+
20
+ namespace lucent::js {
21
+
22
+ /// Where a value sits, for error messages: "fn: argument 'a.b[2]'".
23
+ /// Where a value sits in what JavaScript passed, for error messages:
24
+ /// `sumPoints: argument 'ps'[3].x`. Each step points at its parent on the
25
+ /// stack and is rendered only when a conversion fails, so converting a large
26
+ /// array allocates nothing for paths.
27
+ struct Path {
28
+ const char* fn;
29
+ /// The root: "argument 'ps'", "this", "resolved value".
30
+ std::string root;
31
+ const Path* parent = nullptr;
32
+ enum class Step : unsigned char { Root, Field, Index, Key } step = Step::Root;
33
+ const char* name = nullptr;
34
+ size_t idx = 0;
35
+ const std::string* keyName = nullptr;
36
+
37
+ Path field(const char* n) const {
38
+ Path p{fn, {}, this, Step::Field};
39
+ p.name = n;
40
+ return p;
41
+ }
42
+ Path index(size_t i) const {
43
+ Path p{fn, {}, this, Step::Index};
44
+ p.idx = i;
45
+ return p;
46
+ }
47
+ Path key(const std::string& k) const {
48
+ Path p{fn, {}, this, Step::Key};
49
+ p.keyName = &k;
50
+ return p;
51
+ }
52
+ /// `argument 'ps'[3].x`
53
+ std::string where() const {
54
+ switch (step) {
55
+ case Step::Root: return root;
56
+ case Step::Field: return parent->where() + "." + name;
57
+ case Step::Index: return parent->where() + "[" + std::to_string(idx) + "]";
58
+ case Step::Key: return parent->where() + "[\"" + *keyName + "\"]";
59
+ }
60
+ return root;
61
+ }
62
+ };
63
+
64
+ [[noreturn]] void throwBoundaryError(jsi::Runtime& rt, const Path& path, const char* expected, const jsi::Value& actual);
65
+ const char* jsTypeName(jsi::Runtime& rt, const jsi::Value& v);
66
+
67
+ inline const jsi::Value& arg(const jsi::Value* args, size_t count, size_t i) {
68
+ static const jsi::Value undef = jsi::Value::undefined();
69
+ return i < count ? args[i] : undef;
70
+ }
71
+
72
+ template <class T, class Enable = void>
73
+ struct Convert;
74
+
75
+ /// Conversions that also take an object the caller owns (generated structs):
76
+ /// an array element is moved into one instead of its handle being cloned.
77
+ template <class T>
78
+ concept ConvertsFromObject = requires(jsi::Runtime& rt, const jsi::Object& o, const Path& p) { Convert<T>::fromObject(rt, o, p); };
79
+
80
+ // --- primitives ---------------------------------------------------------------
81
+
82
+ template <>
83
+ struct Convert<double> {
84
+ static double fromJs(jsi::Runtime& rt, const jsi::Value& v, const Path& p) {
85
+ if (!v.isNumber()) throwBoundaryError(rt, p, "a number", v);
86
+ return v.getNumber();
87
+ }
88
+ static jsi::Value toJs(jsi::Runtime&, Host&, double v) { return jsi::Value(v); }
89
+ };
90
+
91
+ template <>
92
+ struct Convert<bool> {
93
+ static bool fromJs(jsi::Runtime& rt, const jsi::Value& v, const Path& p) {
94
+ if (!v.isBool()) throwBoundaryError(rt, p, "a boolean", v);
95
+ return v.getBool();
96
+ }
97
+ static jsi::Value toJs(jsi::Runtime&, Host&, bool v) { return jsi::Value(v); }
98
+ };
99
+
100
+ String stringFromJs(jsi::Runtime& rt, const jsi::String& s);
101
+ jsi::String stringToJs(jsi::Runtime& rt, const String& s);
102
+
103
+ template <>
104
+ struct Convert<String> {
105
+ static String fromJs(jsi::Runtime& rt, const jsi::Value& v, const Path& p) {
106
+ if (!v.isString()) throwBoundaryError(rt, p, "a string", v);
107
+ return stringFromJs(rt, v.getString(rt));
108
+ }
109
+ static jsi::Value toJs(jsi::Runtime& rt, Host&, const String& v) { return jsi::Value(stringToJs(rt, v)); }
110
+ };
111
+
112
+ template <>
113
+ struct Convert<Undefined> {
114
+ static Undefined fromJs(jsi::Runtime& rt, const jsi::Value& v, const Path& p) {
115
+ if (!v.isUndefined()) throwBoundaryError(rt, p, "undefined", v);
116
+ return undefined;
117
+ }
118
+ static jsi::Value toJs(jsi::Runtime&, Host&, Undefined) { return jsi::Value::undefined(); }
119
+ };
120
+
121
+ template <>
122
+ struct Convert<Null> {
123
+ static Null fromJs(jsi::Runtime& rt, const jsi::Value& v, const Path& p) {
124
+ if (!v.isNull()) throwBoundaryError(rt, p, "null", v);
125
+ return null;
126
+ }
127
+ static jsi::Value toJs(jsi::Runtime&, Host&, Null) { return jsi::Value::null(); }
128
+ };
129
+
130
+ // --- optional -------------------------------------------------------------------
131
+
132
+ template <class T>
133
+ struct Convert<Opt<T>> {
134
+ static Opt<T> fromJs(jsi::Runtime& rt, const jsi::Value& v, const Path& p) {
135
+ if (v.isUndefined()) return undefined;
136
+ if (v.isNull()) return null;
137
+ return Convert<T>::fromJs(rt, v, p);
138
+ }
139
+ static jsi::Value toJs(jsi::Runtime& rt, Host& h, const Opt<T>& v) {
140
+ if (v.isUndefined()) return jsi::Value::undefined();
141
+ if (v.isNull()) return jsi::Value::null();
142
+ return Convert<T>::toJs(rt, h, v.get());
143
+ }
144
+ };
145
+
146
+ // --- arrays -----------------------------------------------------------------------
147
+
148
+ template <class T>
149
+ struct Convert<Array<T>> {
150
+ static Array<T> fromJs(jsi::Runtime& rt, const jsi::Value& v, const Path& p) {
151
+ if (!v.isObject() || !v.getObject(rt).isArray(rt)) throwBoundaryError(rt, p, "an array", v);
152
+ jsi::Array a = v.getObject(rt).getArray(rt);
153
+ size_t n = a.size(rt);
154
+ std::vector<typename Array<T>::Elem> items;
155
+ items.reserve(n);
156
+ for (size_t i = 0; i < n; i++) {
157
+ if constexpr (ConvertsFromObject<T>) {
158
+ jsi::Value e = a.getValueAtIndex(rt, i);
159
+ if (!e.isObject()) throwBoundaryError(rt, p.index(i), "an object", e);
160
+ items.push_back(static_cast<typename Array<T>::Elem>(Convert<T>::fromObject(rt, std::move(e).getObject(rt), p.index(i))));
161
+ } else {
162
+ items.push_back(static_cast<typename Array<T>::Elem>(Convert<T>::fromJs(rt, a.getValueAtIndex(rt, i), p.index(i))));
163
+ }
164
+ }
165
+ return Array<T>(std::move(items));
166
+ }
167
+ static jsi::Value toJs(jsi::Runtime& rt, Host& h, const Array<T>& v) {
168
+ const auto& items = v.items();
169
+ jsi::Array a(rt, items.size());
170
+ for (size_t i = 0; i < items.size(); i++) a.setValueAtIndex(rt, i, Convert<T>::toJs(rt, h, static_cast<T>(items[i])));
171
+ return jsi::Value(std::move(a));
172
+ }
173
+ };
174
+
175
+ // --- tuples (JS arrays of fixed length) --------------------------------------------
176
+
177
+ template <class... Ts>
178
+ struct Convert<std::tuple<Ts...>> {
179
+ static std::tuple<Ts...> fromJs(jsi::Runtime& rt, const jsi::Value& v, const Path& p) {
180
+ if (!v.isObject() || !v.getObject(rt).isArray(rt)) throwBoundaryError(rt, p, "an array", v);
181
+ jsi::Array a = v.getObject(rt).getArray(rt);
182
+ return read(rt, a, p, std::index_sequence_for<Ts...>{});
183
+ }
184
+ static jsi::Value toJs(jsi::Runtime& rt, Host& h, const std::tuple<Ts...>& t) {
185
+ jsi::Array a(rt, sizeof...(Ts));
186
+ write(rt, h, a, t, std::index_sequence_for<Ts...>{});
187
+ return jsi::Value(std::move(a));
188
+ }
189
+
190
+ private:
191
+ template <size_t... I>
192
+ static std::tuple<Ts...> read(jsi::Runtime& rt, jsi::Array& a, const Path& p, std::index_sequence<I...>) {
193
+ size_t n = a.size(rt);
194
+ return std::tuple<Ts...>(Convert<Ts>::fromJs(rt, I < n ? a.getValueAtIndex(rt, I) : jsi::Value::undefined(), p.index(I))...);
195
+ }
196
+ template <size_t... I>
197
+ static void write(jsi::Runtime& rt, Host& h, jsi::Array& a, const std::tuple<Ts...>& t, std::index_sequence<I...>) {
198
+ (a.setValueAtIndex(rt, I, Convert<Ts>::toJs(rt, h, std::get<I>(t))), ...);
199
+ }
200
+ };
201
+
202
+ // --- records (plain objects used as dictionaries) ----------------------------------
203
+
204
+ template <class V>
205
+ struct Convert<Dict<V>> {
206
+ static Dict<V> fromJs(jsi::Runtime& rt, const jsi::Value& v, const Path& p) {
207
+ if (!v.isObject() || v.getObject(rt).isArray(rt) || v.getObject(rt).isFunction(rt)) throwBoundaryError(rt, p, "an object", v);
208
+ jsi::Object o = v.getObject(rt);
209
+ jsi::Array names = o.getPropertyNames(rt);
210
+ Dict<V> out;
211
+ size_t n = names.size(rt);
212
+ for (size_t i = 0; i < n; i++) {
213
+ jsi::String key = names.getValueAtIndex(rt, i).getString(rt);
214
+ std::string k = key.utf8(rt);
215
+ out.set(stringFromJs(rt, key), Convert<V>::fromJs(rt, o.getProperty(rt, jsi::PropNameID::forString(rt, key)), p.key(k)));
216
+ }
217
+ return out;
218
+ }
219
+ static jsi::Value toJs(jsi::Runtime& rt, Host& h, const Dict<V>& d) {
220
+ jsi::Object o(rt);
221
+ auto& t = d.table();
222
+ for (size_t i = 0; i < t.slotCount(); i++) {
223
+ if (!t.slotLive(i)) continue;
224
+ o.setProperty(rt, jsi::PropNameID::forString(rt, stringToJs(rt, t.slot(i).key)), Convert<V>::toJs(rt, h, t.slot(i).value));
225
+ }
226
+ return jsi::Value(std::move(o));
227
+ }
228
+ };
229
+
230
+ // --- Map and Set -----------------------------------------------------------------------
231
+
232
+ bool isInstanceOf(jsi::Runtime& rt, const jsi::Object& o, const char* ctor);
233
+
234
+ template <class K, class V>
235
+ struct Convert<Map<K, V>> {
236
+ static Map<K, V> fromJs(jsi::Runtime& rt, const jsi::Value& v, const Path& p) {
237
+ if (!v.isObject() || !isInstanceOf(rt, v.getObject(rt), "Map")) throwBoundaryError(rt, p, "a Map", v);
238
+ jsi::Value entries = v.getObject(rt).getPropertyAsFunction(rt, "entries").callWithThis(rt, v.getObject(rt));
239
+ jsi::Array list = rt.global().getPropertyAsObject(rt, "Array").getPropertyAsFunction(rt, "from").call(rt, entries).getObject(rt).getArray(rt);
240
+ Map<K, V> out;
241
+ size_t n = list.size(rt);
242
+ for (size_t i = 0; i < n; i++) {
243
+ jsi::Array pair = list.getValueAtIndex(rt, i).getObject(rt).getArray(rt);
244
+ out.set(Convert<K>::fromJs(rt, pair.getValueAtIndex(rt, 0), p.index(i).field("key")),
245
+ Convert<V>::fromJs(rt, pair.getValueAtIndex(rt, 1), p.index(i).field("value")));
246
+ }
247
+ return out;
248
+ }
249
+ static jsi::Value toJs(jsi::Runtime& rt, Host& h, const Map<K, V>& m) {
250
+ jsi::Object out = rt.global().getPropertyAsFunction(rt, "Map").callAsConstructor(rt).getObject(rt);
251
+ jsi::Function set = out.getPropertyAsFunction(rt, "set");
252
+ auto& t = m.table();
253
+ for (size_t i = 0; i < t.slotCount(); i++) {
254
+ if (!t.slotLive(i)) continue;
255
+ set.callWithThis(rt, out, Convert<K>::toJs(rt, h, t.slot(i).key), Convert<V>::toJs(rt, h, t.slot(i).value));
256
+ }
257
+ return jsi::Value(std::move(out));
258
+ }
259
+ };
260
+
261
+ template <class T>
262
+ struct Convert<Set<T>> {
263
+ static Set<T> fromJs(jsi::Runtime& rt, const jsi::Value& v, const Path& p) {
264
+ if (!v.isObject() || !isInstanceOf(rt, v.getObject(rt), "Set")) throwBoundaryError(rt, p, "a Set", v);
265
+ jsi::Array list = rt.global().getPropertyAsObject(rt, "Array").getPropertyAsFunction(rt, "from").call(rt, v).getObject(rt).getArray(rt);
266
+ Set<T> out;
267
+ size_t n = list.size(rt);
268
+ for (size_t i = 0; i < n; i++) out.add(Convert<T>::fromJs(rt, list.getValueAtIndex(rt, i), p.index(i)));
269
+ return out;
270
+ }
271
+ static jsi::Value toJs(jsi::Runtime& rt, Host& h, const Set<T>& s) {
272
+ jsi::Object out = rt.global().getPropertyAsFunction(rt, "Set").callAsConstructor(rt).getObject(rt);
273
+ jsi::Function add = out.getPropertyAsFunction(rt, "add");
274
+ auto& t = s.table();
275
+ for (size_t i = 0; i < t.slotCount(); i++) {
276
+ if (t.slotLive(i)) add.callWithThis(rt, out, Convert<T>::toJs(rt, h, t.slot(i).key));
277
+ }
278
+ return jsi::Value(std::move(out));
279
+ }
280
+ };
281
+
282
+ // --- Uint8Array ------------------------------------------------------------------------
283
+
284
+ template <>
285
+ struct Convert<Bytes> {
286
+ static Bytes fromJs(jsi::Runtime& rt, const jsi::Value& v, const Path& p);
287
+ static jsi::Value toJs(jsi::Runtime& rt, Host& h, const Bytes& b);
288
+ };
289
+
290
+ // --- errors ------------------------------------------------------------------------------
291
+
292
+ template <>
293
+ struct Convert<Error> {
294
+ static Error fromJs(jsi::Runtime& rt, const jsi::Value& v, const Path& p);
295
+ static jsi::Value toJs(jsi::Runtime& rt, Host& h, const Error& e) { return h.errorToJs(rt, e); }
296
+ };
297
+
298
+ /// Dates cross as copies of their time value, like arrays.
299
+ template <>
300
+ struct Convert<Date> {
301
+ static Date fromJs(jsi::Runtime& rt, const jsi::Value& v, const Path& p);
302
+ static jsi::Value toJs(jsi::Runtime& rt, Host& h, const Date& d);
303
+ };
304
+
305
+ /// RegExps cross as their source and flags (JavaScript's lastIndex is not kept).
306
+ template <>
307
+ struct Convert<RegExp> {
308
+ static RegExp fromJs(jsi::Runtime& rt, const jsi::Value& v, const Path& p) {
309
+ if (v.isObject()) {
310
+ jsi::Object o = v.getObject(rt);
311
+ jsi::Value source = o.getProperty(rt, "source"), flags = o.getProperty(rt, "flags");
312
+ if (source.isString() && flags.isString()) return makeRegExp(stringFromJs(rt, source.getString(rt)), stringFromJs(rt, flags.getString(rt)));
313
+ }
314
+ throwBoundaryError(rt, p, "a RegExp", v);
315
+ }
316
+ static jsi::Value toJs(jsi::Runtime& rt, Host&, const RegExp& re) {
317
+ return rt.global().getPropertyAsFunction(rt, "RegExp").callAsConstructor(rt, stringToJs(rt, re->source()), stringToJs(rt, re->flags()));
318
+ }
319
+ };
320
+
321
+ /// A JavaScript iterable arrives as a snapshot (Array.from), iterated lazily.
322
+ template <class T>
323
+ struct Convert<Iter<T>> {
324
+ static Iter<T> fromJs(jsi::Runtime& rt, const jsi::Value& v, const Path& p) {
325
+ if (!v.isObject() && !v.isString()) throwBoundaryError(rt, p, "an iterable", v);
326
+ jsi::Function from = rt.global().getPropertyAsObject(rt, "Array").getPropertyAsFunction(rt, "from");
327
+ return iterOf(Convert<Array<T>>::fromJs(rt, from.call(rt, v), p));
328
+ }
329
+ };
330
+
331
+ /// An AbortSignal from JavaScript becomes a native signal that a listener on
332
+ /// the JS signal aborts. The native signal is cached on the JS object, so one
333
+ /// JS signal always maps to one native signal. Signals do not go the other way.
334
+ template <>
335
+ struct Convert<AbortSignal> {
336
+ static AbortSignal fromJs(jsi::Runtime& rt, const jsi::Value& v, const Path& p);
337
+ };
338
+
339
+ // --- promises ------------------------------------------------------------------------------
340
+
341
+ /// Hands a Lucent promise to JavaScript: the JS promise settles on the JS
342
+ /// thread when the Lucent one does.
343
+ template <class T>
344
+ struct Convert<Promise<T>> {
345
+ static jsi::Value toJs(jsi::Runtime& rt, Host& h, const Promise<T>& p) {
346
+ uint64_t id = 0;
347
+ jsi::Value jsPromise = h.createPromise(rt, id);
348
+ std::weak_ptr<Host> weak = h.weak_from_this();
349
+ p.onSettled([p, weak, id] {
350
+ auto host = weak.lock();
351
+ if (!host) return;
352
+ host->postToJs([p, id, weak](jsi::Runtime& rt) {
353
+ auto host = weak.lock();
354
+ if (!host) return;
355
+ LucentScope scope;
356
+ if (p.fulfilled()) {
357
+ jsi::Value value = jsi::Value::undefined();
358
+ try {
359
+ if constexpr (std::is_void_v<T>) {
360
+ value = jsi::Value::undefined();
361
+ } else {
362
+ value = Convert<T>::toJs(rt, *host, p.value());
363
+ }
364
+ } catch (const jsi::JSError& e) {
365
+ host->reject(rt, id, jsi::Value(rt, e.value()));
366
+ return;
367
+ }
368
+ host->resolve(rt, id, value);
369
+ } else {
370
+ host->reject(rt, id, host->errorToJs(rt, p.error()));
371
+ }
372
+ });
373
+ });
374
+ return jsPromise;
375
+ }
376
+ /// A JS promise passed into Lucent (e.g. returned by a callback).
377
+ static Promise<T> fromJs(jsi::Runtime& rt, const jsi::Value& v, const Path& p);
378
+ };
379
+
380
+ // --- functions -------------------------------------------------------------------------------
381
+
382
+ /// A JavaScript function held by Lucent. Calling it on the JS thread is a
383
+ /// normal synchronous call; from the Lucent thread the call is posted to the
384
+ /// JS thread (so it must return void or a Promise).
385
+ class JsCallback {
386
+ public:
387
+ JsCallback(Host& host, uint64_t id) : host_(host.weak_from_this()), id_(id) {}
388
+ ~JsCallback() {
389
+ if (auto h = host_.lock()) h->release(id_);
390
+ }
391
+ std::shared_ptr<Host> host() const { return host_.lock(); }
392
+ uint64_t id() const { return id_; }
393
+
394
+ private:
395
+ std::weak_ptr<Host> host_;
396
+ uint64_t id_;
397
+ };
398
+
399
+ template <class T>
400
+ struct IsPromiseType : std::false_type {};
401
+ template <class T>
402
+ struct IsPromiseType<Promise<T>> : std::true_type {};
403
+
404
+ template <class R, class... A>
405
+ struct Convert<Fn<R(A...)>> {
406
+ static Fn<R(A...)> fromJs(jsi::Runtime& rt, const jsi::Value& v, const Path& p) {
407
+ if (!v.isObject() || !v.getObject(rt).isFunction(rt)) throwBoundaryError(rt, p, "a function", v);
408
+ Host& host = Host::get(rt);
409
+ auto cb = std::make_shared<JsCallback>(host, host.retain(rt, v.getObject(rt).getFunction(rt)));
410
+ std::string where = std::string(p.fn) + " (callback " + p.where() + ")";
411
+ return Fn<R(A...)>([cb, where](A... args) -> R { return invoke(cb, where, std::move(args)...); });
412
+ }
413
+
414
+ static R invoke(const std::shared_ptr<JsCallback>& cb, const std::string& where, A... args) {
415
+ auto host = cb->host();
416
+ if (!host || !host->alive()) {
417
+ if constexpr (std::is_void_v<R>) return;
418
+ else throwError(String::fromLatin1("Error"), String::fromLatin1("The JavaScript runtime is gone"));
419
+ }
420
+ if (host->onJsThread()) {
421
+ jsi::Runtime& rt = host->runtime();
422
+ jsi::Function* fn = host->retained(cb->id());
423
+ if (!fn) throwError(String::fromLatin1("Error"), String::fromLatin1("Callback was released"));
424
+ jsi::Value result = jsi::Value::undefined();
425
+ try {
426
+ result = fn->call(rt, Convert<A>::toJs(rt, *host, args)...);
427
+ } catch (const jsi::JSError& e) {
428
+ throw Exception(Host::errorFromJs(rt, e));
429
+ }
430
+ if constexpr (std::is_void_v<R>) {
431
+ return;
432
+ } else {
433
+ return Convert<R>::fromJs(rt, result, Path{where.c_str(), "return value"});
434
+ }
435
+ }
436
+ if constexpr (std::is_void_v<R>) {
437
+ std::weak_ptr<Host> weak = host;
438
+ host->postToJs([cb, args...](jsi::Runtime& rt) mutable {
439
+ auto host = cb->host();
440
+ if (!host) return;
441
+ jsi::Function* fn = host->retained(cb->id());
442
+ if (!fn) return;
443
+ LucentScope scope;
444
+ try {
445
+ fn->call(rt, Convert<A>::toJs(rt, *host, args)...);
446
+ } catch (const jsi::JSError& e) {
447
+ consoleWrite(ConsoleLevel::Error, String::fromUtf8("Uncaught error in callback: " + e.getMessage()));
448
+ }
449
+ });
450
+ return;
451
+ } else if constexpr (IsPromiseType<R>::value) {
452
+ R out;
453
+ host->postToJs([cb, out, where, args...](jsi::Runtime& rt) mutable {
454
+ auto host = cb->host();
455
+ jsi::Function* fn = host ? host->retained(cb->id()) : nullptr;
456
+ LucentScope scope;
457
+ if (!fn) {
458
+ out.reject(makeError(String::fromLatin1("Callback was released")));
459
+ return;
460
+ }
461
+ try {
462
+ jsi::Value result = fn->call(rt, Convert<A>::toJs(rt, *host, args)...);
463
+ R inner = Convert<R>::fromJs(rt, result, Path{where.c_str(), "return value"});
464
+ inner.onSettled([inner, out] {
465
+ if (inner.fulfilled()) {
466
+ if constexpr (std::is_void_v<typename R::value_type>) out.resolve(undefined);
467
+ else out.resolve(inner.value());
468
+ } else {
469
+ out.reject(inner.error());
470
+ }
471
+ });
472
+ } catch (const jsi::JSError& e) {
473
+ out.reject(Host::errorFromJs(rt, e));
474
+ } catch (const Exception& e) {
475
+ out.reject(e.error());
476
+ }
477
+ });
478
+ return out;
479
+ } else {
480
+ throwError(String::fromLatin1("Error"),
481
+ String::fromUtf8(where + ": a callback that returns a value can only be called synchronously; make it return a Promise"));
482
+ }
483
+ }
484
+
485
+ static jsi::Value toJs(jsi::Runtime& rt, Host& h, const Fn<R(A...)>& f) {
486
+ std::weak_ptr<Host> weak = h.weak_from_this();
487
+ return jsi::Function::createFromHostFunction(
488
+ rt, jsi::PropNameID::forAscii(rt, "lucentFunction"), sizeof...(A),
489
+ [f, weak](jsi::Runtime& rt, const jsi::Value&, const jsi::Value* args, size_t count) -> jsi::Value {
490
+ auto host = weak.lock();
491
+ if (!host) throw jsi::JSError(rt, "Lucent host is gone");
492
+ LucentScope scope;
493
+ try {
494
+ return call(rt, *host, f, args, count, std::index_sequence_for<A...>{});
495
+ } catch (const Exception& e) {
496
+ throw jsi::JSError(rt, host->errorToJs(rt, e.error()));
497
+ }
498
+ });
499
+ }
500
+
501
+ private:
502
+ template <size_t... I>
503
+ static jsi::Value call(jsi::Runtime& rt, Host& h, const Fn<R(A...)>& f, const jsi::Value* args, size_t count, std::index_sequence<I...>) {
504
+ static const jsi::Value undef = jsi::Value::undefined();
505
+ Path p{"function", ""};
506
+ if constexpr (std::is_void_v<R>) {
507
+ f(Convert<std::decay_t<A>>::fromJs(rt, I < count ? args[I] : undef, Path{"function", "argument " + std::to_string(I)})...);
508
+ return jsi::Value::undefined();
509
+ } else {
510
+ return Convert<R>::toJs(rt, h, f(Convert<std::decay_t<A>>::fromJs(rt, I < count ? args[I] : undef, Path{"function", "argument " + std::to_string(I)})...));
511
+ }
512
+ }
513
+ };
514
+
515
+ template <class T>
516
+ Promise<T> Convert<Promise<T>>::fromJs(jsi::Runtime& rt, const jsi::Value& v, const Path& p) {
517
+ Promise<T> out;
518
+ if (!v.isObject() || !v.getObject(rt).getProperty(rt, "then").isObject()) {
519
+ // Not a thenable: behaves like `await value`.
520
+ if constexpr (std::is_void_v<T>) out.resolve(undefined);
521
+ else out.resolve(Convert<T>::fromJs(rt, v, p));
522
+ return out;
523
+ }
524
+ std::string where = std::string(p.fn) + " " + p.where();
525
+ auto onFulfilled = jsi::Function::createFromHostFunction(
526
+ rt, jsi::PropNameID::forAscii(rt, "onFulfilled"), 1,
527
+ [out, where](jsi::Runtime& rt, const jsi::Value&, const jsi::Value* args, size_t count) -> jsi::Value {
528
+ LucentScope scope;
529
+ try {
530
+ if constexpr (std::is_void_v<T>) out.resolve(undefined);
531
+ else out.resolve(Convert<T>::fromJs(rt, arg(args, count, 0), Path{where.c_str(), "resolved value"}));
532
+ } catch (const jsi::JSError& e) {
533
+ out.reject(Host::errorFromJs(rt, e));
534
+ }
535
+ return jsi::Value::undefined();
536
+ });
537
+ auto onRejected = jsi::Function::createFromHostFunction(
538
+ rt, jsi::PropNameID::forAscii(rt, "onRejected"), 1,
539
+ [out](jsi::Runtime& rt, const jsi::Value&, const jsi::Value* args, size_t count) -> jsi::Value {
540
+ LucentScope scope;
541
+ out.reject(Convert<Error>::fromJs(rt, arg(args, count, 0), Path{"promise", "rejection"}));
542
+ return jsi::Value::undefined();
543
+ });
544
+ jsi::Object promise = v.getObject(rt);
545
+ promise.getPropertyAsFunction(rt, "then").callWithThis(rt, promise, onFulfilled, onRejected);
546
+ return out;
547
+ }
548
+
549
+ // --- helpers used by generated bindings ----------------------------------------------------
550
+
551
+
552
+ /// Runs a synchronous Lucent call from JS, translating Lucent errors into
553
+ /// JS exceptions.
554
+ template <class F>
555
+ jsi::Value callSync(jsi::Runtime& rt, Host& host, F&& body) {
556
+ LucentScope scope;
557
+ try {
558
+ return body();
559
+ } catch (const Exception& e) {
560
+ throw jsi::JSError(rt, host.errorToJs(rt, e.error()));
561
+ } catch (const jsi::JSError&) {
562
+ throw;
563
+ } catch (const jsi::JSIException&) {
564
+ throw;
565
+ } catch (const std::exception& e) {
566
+ throw jsi::JSError(rt, host.errorToJs(rt, currentError(std::current_exception())));
567
+ }
568
+ }
569
+
570
+ /// Starts an exported async function on the Lucent thread and returns a JS
571
+ /// promise for its result. `start` runs on the Lucent thread and returns the
572
+ /// Lucent promise.
573
+ template <class T, class F>
574
+ jsi::Value callAsync(jsi::Runtime& rt, Host& host, F&& start) {
575
+ uint64_t id = 0;
576
+ jsi::Value jsPromise = host.createPromise(rt, id);
577
+ std::weak_ptr<Host> weak = host.weak_from_this();
578
+ Scheduler::instance().post([weak, id, start = std::forward<F>(start)]() mutable {
579
+ Promise<T> p;
580
+ try {
581
+ p = start();
582
+ } catch (...) {
583
+ p = Promise<T>::rejected(currentError(std::current_exception()));
584
+ }
585
+ p.onSettled([p, weak, id] {
586
+ auto host = weak.lock();
587
+ if (!host) return;
588
+ host->postToJs([p, id, weak](jsi::Runtime& rt) {
589
+ auto host = weak.lock();
590
+ if (!host) return;
591
+ LucentScope scope;
592
+ if (p.fulfilled()) {
593
+ jsi::Value value = jsi::Value::undefined();
594
+ if constexpr (!std::is_void_v<T>) {
595
+ try {
596
+ value = Convert<T>::toJs(rt, *host, p.value());
597
+ } catch (const jsi::JSError& e) {
598
+ host->reject(rt, id, jsi::Value(rt, e.value()));
599
+ return;
600
+ }
601
+ }
602
+ host->resolve(rt, id, value);
603
+ } else {
604
+ host->reject(rt, id, host->errorToJs(rt, p.error()));
605
+ }
606
+ });
607
+ });
608
+ });
609
+ return jsPromise;
610
+ }
611
+
612
+ } // namespace lucent::js