@lucent-lang/lucent 0.0.4 → 0.1.1

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 (152) hide show
  1. package/README.md +97 -0
  2. package/app.plugin.js +46 -10
  3. package/dist/{bench-CR3E7e2e.js → bench-eEez4jhQ.js} +10 -6
  4. package/dist/{build-CAIrRSYA.js → build-CpUjeHFz.js} +1 -1
  5. package/dist/{build-check-QozuNJm5.js → build-check-BA1ZEp9s.js} +28 -13
  6. package/dist/{check-1SCmYlqA.js → check-BZx3FgI6.js} +1 -1
  7. package/dist/cli.js +62 -13
  8. package/dist/{codes-CIuwXt1S.js → codes-D-6LxUqD.js} +163 -7
  9. package/dist/compiler.js +4 -4
  10. package/dist/{dashboard-B-NzjjaN.js → dashboard-CC-HXhcd.js} +2 -2
  11. package/dist/{dev-BYWMP7oC.js → dev-DI5yL8zx.js} +62 -23
  12. package/dist/{diagnostic-BM9sXmwl.js → diagnostic-BvwLZQS1.js} +3 -1
  13. package/dist/{doctor-C0PrfWT6.js → doctor-CTAX3euc.js} +1 -1
  14. package/dist/{doctor-DdR4JnVc.js → doctor-CrgOAV5L.js} +1 -1
  15. package/dist/{explain--vV1oaRl.js → explain-Byit07F-.js} +1 -1
  16. package/dist/{packages-HyskKIsY.js → packages-CSmKeaAh.js} +2 -33
  17. package/dist/pipeline-BM_VM2X_.js +700 -0
  18. package/dist/project-BKNoTEEp.js +342 -0
  19. package/dist/sdk-coverage-iiv3JqMp.js +113 -0
  20. package/dist/sdk-diff-ByJ1Gw-F.js +109 -0
  21. package/dist/sdk-lock-BQn7IyRs.js +38 -0
  22. package/dist/{sdk-prefetch-mo04JDZV.js → sdk-prefetch-Bv2pNega.js} +2 -2
  23. package/dist/{sdk-search-Iv4K3-8a.js → sdk-search-CPGJm_Ne.js} +2 -2
  24. package/dist/{sdk-show-CkrdlqlP.js → sdk-show-BUkLqRnA.js} +5 -4
  25. package/dist/sdk-usage-0ZFMAYg1.js +95 -0
  26. package/dist/src-ChnmWLwn.js +40851 -0
  27. package/dist/trace-Bi4hkmPx.js +139 -0
  28. package/lib/core.cjs +368 -1
  29. package/lib/sdk/android.d.ts +54 -1
  30. package/lib/sdk/compose.d.ts +94 -0
  31. package/lib/sdk/compose.schemas.json.gz +0 -0
  32. package/lib/sdk/core.d.ts +201 -0
  33. package/lib/sdk/ios.d.ts +75 -2
  34. package/lib/sdk/ui.d.ts +129 -0
  35. package/metro/transformer.cjs +11 -2
  36. package/package.json +4 -3
  37. package/runtime/cpp/lucent/abort.cpp +59 -11
  38. package/runtime/cpp/lucent/abort.h +22 -6
  39. package/runtime/cpp/lucent/array.h +12 -2
  40. package/runtime/cpp/lucent/async.h +101 -21
  41. package/runtime/cpp/lucent/bigint.cpp +898 -0
  42. package/runtime/cpp/lucent/bigint.h +302 -0
  43. package/runtime/cpp/lucent/buffer.cpp +228 -0
  44. package/runtime/cpp/lucent/buffer.h +328 -0
  45. package/runtime/cpp/lucent/builtins.cpp +4 -1
  46. package/runtime/cpp/lucent/bytes.h +21 -2
  47. package/runtime/cpp/lucent/callback.cpp +29 -0
  48. package/runtime/cpp/lucent/callback.h +230 -0
  49. package/runtime/cpp/lucent/compute.cpp +370 -0
  50. package/runtime/cpp/lucent/compute.h +455 -0
  51. package/runtime/cpp/lucent/conversions.h +111 -0
  52. package/runtime/cpp/lucent/core.h +16 -5
  53. package/runtime/cpp/lucent/equality.h +107 -28
  54. package/runtime/cpp/lucent/execution.cpp +416 -0
  55. package/runtime/cpp/lucent/execution.h +270 -0
  56. package/runtime/cpp/lucent/extension.cpp +97 -0
  57. package/runtime/cpp/lucent/extension.h +210 -0
  58. package/runtime/cpp/lucent/helpers.h +12 -5
  59. package/runtime/cpp/lucent/items.h +52 -0
  60. package/runtime/cpp/lucent/jserror.cpp +9 -0
  61. package/runtime/cpp/lucent/jserror.h +11 -0
  62. package/runtime/cpp/lucent/jsi/convert.cpp +138 -0
  63. package/runtime/cpp/lucent/jsi/convert.h +128 -59
  64. package/runtime/cpp/lucent/jsi/host.cpp +213 -35
  65. package/runtime/cpp/lucent/jsi/host.h +116 -16
  66. package/runtime/cpp/lucent/json.h +19 -1
  67. package/runtime/cpp/lucent/lifecycle.cpp +260 -0
  68. package/runtime/cpp/lucent/lifecycle.h +124 -0
  69. package/runtime/cpp/lucent/lucent.h +7 -0
  70. package/runtime/cpp/lucent/map.h +41 -7
  71. package/runtime/cpp/lucent/native.cpp +35 -47
  72. package/runtime/cpp/lucent/native.h +116 -26
  73. package/runtime/cpp/lucent/number.cpp +38 -38
  74. package/runtime/cpp/lucent/number.h +33 -0
  75. package/runtime/cpp/lucent/operation.h +65 -0
  76. package/runtime/cpp/lucent/ops.h +14 -6
  77. package/runtime/cpp/lucent/platform/android.cpp +223 -10
  78. package/runtime/cpp/lucent/platform/android.h +227 -4
  79. package/runtime/cpp/lucent/platform/android_activity.cpp +258 -0
  80. package/runtime/cpp/lucent/platform/android_requests.h +140 -0
  81. package/runtime/cpp/lucent/platform/compose.h +256 -0
  82. package/runtime/cpp/lucent/platform/ios.h +325 -25
  83. package/runtime/cpp/lucent/platform/ios.mm +1 -14
  84. package/runtime/cpp/lucent/platform/ios_ui.h +192 -0
  85. package/runtime/cpp/lucent/platform/ios_ui.mm +454 -0
  86. package/runtime/cpp/lucent/platform/swiftui.h +190 -0
  87. package/runtime/cpp/lucent/presentation.cpp +11 -0
  88. package/runtime/cpp/lucent/presentation.h +109 -0
  89. package/runtime/cpp/lucent/reactive.cpp +406 -0
  90. package/runtime/cpp/lucent/reactive.h +566 -0
  91. package/runtime/cpp/lucent/report.cpp +40 -0
  92. package/runtime/cpp/lucent/report.h +16 -0
  93. package/runtime/cpp/lucent/resource.cpp +83 -0
  94. package/runtime/cpp/lucent/resource.h +75 -0
  95. package/runtime/cpp/lucent/scheduler.cpp +38 -124
  96. package/runtime/cpp/lucent/scheduler.h +102 -83
  97. package/runtime/cpp/lucent/scope.cpp +308 -0
  98. package/runtime/cpp/lucent/scope.h +452 -0
  99. package/runtime/cpp/lucent/sizing.h +263 -0
  100. package/runtime/cpp/lucent/slots.h +231 -0
  101. package/runtime/cpp/lucent/trace.cpp +539 -0
  102. package/runtime/cpp/lucent/trace.h +251 -0
  103. package/runtime/cpp/lucent/transport.cpp +9 -0
  104. package/runtime/cpp/lucent/transport.h +341 -0
  105. package/runtime/cpp/lucent/view.cpp +123 -0
  106. package/runtime/cpp/lucent/view.h +157 -0
  107. package/runtime/cpp/rn/LucentComponentView.h +121 -0
  108. package/runtime/cpp/rn/LucentComponentView.mm +479 -0
  109. package/runtime/cpp/rn/LucentModule.cpp +37 -7
  110. package/runtime/cpp/rn/LucentModule.h +3 -0
  111. package/runtime/cpp/rn/LucentViewChildren.h +80 -0
  112. package/runtime/cpp/rn/LucentViewChildren.mm +211 -0
  113. package/runtime/cpp/rn/LucentViewRegistry.h +62 -0
  114. package/runtime/cpp/rn/LucentViewRequests.cpp +91 -0
  115. package/runtime/cpp/rn/LucentViewRequests.h +104 -0
  116. package/runtime/cpp/rn/LucentViewSizing.h +345 -0
  117. package/runtime/cpp/rn/LucentViewSlots.h +242 -0
  118. package/runtime/cpp/rn/LucentViewValues.h +125 -0
  119. package/runtime/cpp/rn/LucentViews.h +440 -0
  120. package/runtime/cpp/rn/LucentViewsAndroid.cpp +439 -0
  121. package/runtime/cpp/rn/LucentViewsAndroid.h +138 -0
  122. package/runtime/cpp/third_party/dragonbox/LICENSE-Boost +23 -0
  123. package/runtime/cpp/third_party/dragonbox/README.md +16 -0
  124. package/runtime/cpp/third_party/dragonbox/dragonbox.h +4440 -0
  125. package/runtime/js/index.d.ts +11 -1
  126. package/runtime/js/index.js +84 -8
  127. package/runtime/js/views.d.ts +38 -0
  128. package/runtime/js/views.js +226 -0
  129. package/runtime/native/android/CMakeLists.txt +4 -1
  130. package/runtime/native/android/src/compose/java/dev/lucent/compose/CompositionRules.kt +44 -0
  131. package/runtime/native/android/src/compose/java/dev/lucent/compose/LucentComposition.kt +161 -0
  132. package/runtime/native/android/src/main/java/dev/lucent/LucentActivities.java +292 -0
  133. package/runtime/native/android/src/main/java/dev/lucent/LucentChildren.java +174 -0
  134. package/runtime/native/android/src/main/java/dev/lucent/LucentHostView.java +232 -0
  135. package/runtime/native/android/src/main/java/dev/lucent/LucentInitializer.java +46 -0
  136. package/runtime/native/android/src/main/java/dev/lucent/LucentPackage.java +4 -3
  137. package/runtime/native/android/src/main/java/dev/lucent/LucentRequestActivity.java +92 -0
  138. package/runtime/native/android/src/main/java/dev/lucent/LucentSlotView.java +102 -0
  139. package/runtime/native/android/src/main/java/dev/lucent/LucentViewManager.java +149 -0
  140. package/runtime/native/android/src/main/java/dev/lucent/LucentViewManagers.java +16 -0
  141. package/runtime/native/android/src/main/java/dev/lucent/LucentViews.java +44 -0
  142. package/runtime/test/jsi/harness.cpp +9 -0
  143. package/schemas/build.schema.json +96 -1
  144. package/schemas/check.schema.json +56 -1
  145. package/schemas/lucent.schema.json +495 -9
  146. package/schemas/sdk-diff.schema.json +70 -0
  147. package/schemas/sdk-lock.schema.json +67 -0
  148. package/ts-plugin/index.js +85 -4
  149. package/dist/pipeline-CH7-2cxu.js +0 -232
  150. package/dist/project-D8tEet0k.js +0 -150
  151. package/dist/sdk-coverage-BoW_3Efd.js +0 -64
  152. package/dist/src-BD7f1_aZ.js +0 -11493
@@ -16,27 +16,54 @@
16
16
 
17
17
  namespace lucent {
18
18
 
19
- /// How many platform objects Lucent holds (NativeRefs, not copies): debug
20
- /// builds report what is left when the module goes.
19
+ /// How many platform objects Lucent holds (NativeRefs, not copies), until
20
+ /// each is released.
21
21
  long liveNativeRefs();
22
22
 
23
+ /// The part of liveNativeRefs() module code made: not the main context's
24
+ /// (views' setups and the callbacks they give the platform, which the
25
+ /// renderer and the platform's garbage collector release on their own
26
+ /// schedule), nor the ones kept for the process. Debug builds report what
27
+ /// is left when a JavaScript runtime's module goes.
28
+ long moduleNativeRefs();
29
+
23
30
  namespace detail {
24
- void countNativeRef(int delta);
25
- }
31
+ /// Counts a platform object Lucent now holds; whether module code made it.
32
+ bool countNativeRef();
33
+
34
+ /// Counts one released (or kept for the process: `held` then stays true).
35
+ void uncountNativeRef(bool module, bool held = false);
36
+
37
+ /// Releases `handle` on `on`'s thread: at once there, or with no context;
38
+ /// else posted to it (or here, if it takes no more work).
39
+ void releaseNative(void* handle, void (*release)(void*), ExecutionContext* on, bool module);
40
+ } // namespace detail
26
41
 
27
42
  class NativeRef {
28
43
  public:
29
44
  NativeRef() = default;
30
- /// Takes ownership of `handle`; `release` runs when the last copy goes.
31
- NativeRef(void* handle, void (*release)(void*), bool (*same)(void*, void*))
32
- : p_(handle, Release{release}), same_(same) {
33
- if (handle) detail::countNativeRef(1);
34
- }
45
+
46
+ /// Takes ownership of `handle`; `release` runs when the last copy goes,
47
+ /// on the thread `releaseOn` runs on if the object requires one (UIKit
48
+ /// objects, the main context's), whichever thread dropped that copy.
49
+ NativeRef(void* handle, void (*release)(void*), bool (*same)(void*, void*), ExecutionContext* releaseOn = nullptr)
50
+ : p_(handle, Release{release, releaseOn, handle && detail::countNativeRef()}), same_(same) {}
35
51
 
36
52
  void* get() const { return p_.get(); }
37
53
  explicit operator bool() const { return p_ != nullptr; }
38
54
  const void* identity() const { return p_.get(); }
39
55
 
56
+ /// The object lives as long as the process (the Android Application):
57
+ /// the teardown report leaves it out. Any copy; once.
58
+ void keepForProcess() {
59
+ auto* r = std::get_deleter<Release>(p_);
60
+
61
+ if (r && r->module) {
62
+ r->module = false;
63
+ detail::uncountNativeRef(true, true);
64
+ }
65
+ }
66
+
40
67
  /// The same platform object: pointer identity on iOS, IsSameObject on Android.
41
68
  bool same(const NativeRef& o) const {
42
69
  if (p_ == o.p_) return true;
@@ -47,10 +74,12 @@ class NativeRef {
47
74
  private:
48
75
  struct Release {
49
76
  void (*release)(void*);
77
+ ExecutionContext* on;
78
+ /// Counted as module code's (moduleNativeRefs).
79
+ bool module;
80
+
50
81
  void operator()(void* p) const {
51
- if (!p) return;
52
- detail::countNativeRef(-1);
53
- release(p);
82
+ if (p) detail::releaseNative(p, release, on, module);
54
83
  }
55
84
  };
56
85
  std::shared_ptr<void> p_;
@@ -61,23 +90,24 @@ class NativeRef {
61
90
  inline bool strictEquals(const NativeRef& a, const NativeRef& b) { return a.same(b); }
62
91
  inline String toJsString(const NativeRef&) { return String::fromLatin1("[object NativeObject]"); }
63
92
 
64
- /// Runs `job` on the platform's main thread (main queue, main Looper; a
65
- /// dedicated thread standing in for it on other hosts).
66
- void postToMain(std::function<void()> job);
67
- bool onMainThread();
93
+ // --- the legacy module context ----------------------------------------------
94
+ //
95
+ // What generated module code calls: each enters module code by taking the
96
+ // Lucent lock.
68
97
 
69
- /// A platform callback into Lucent code that returns nothing and outlives
70
- /// the call: queued on the Lucent thread, so the platform never waits for
71
- /// Lucent code. `f` owns what it captured and is released after it runs.
98
+ /// A platform callback into module code that returns nothing and outlives
99
+ /// the call: a turn of the legacy module context on the Lucent thread, so
100
+ /// the platform never waits for Lucent code. `f` owns what it captured and
101
+ /// is released after it runs.
72
102
  template <class F>
73
103
  void postCallback(F f) {
74
104
  Scheduler::instance().post(std::move(f));
75
105
  }
76
106
 
77
- /// A platform callback the platform waits for (for its result, or because
78
- /// it runs during the call or on the main thread): runs on the calling
79
- /// thread, holding the Lucent lock. A Lucent error is reported and the
80
- /// platform gets a default result.
107
+ /// A platform callback into module code the platform waits for (for its
108
+ /// result, or because it runs during the call or on the main thread): runs
109
+ /// on the calling thread, holding the Lucent lock. A Lucent error is
110
+ /// reported and the platform gets a default result.
81
111
  template <class F>
82
112
  auto callNow(F f) -> std::invoke_result_t<F&> {
83
113
  using R = std::invoke_result_t<F&>;
@@ -90,9 +120,11 @@ auto callNow(F f) -> std::invoke_result_t<F&> {
90
120
  }
91
121
  }
92
122
 
93
- /// `main(f)` from lucent:thread: runs `f` on the main thread holding the
94
- /// Lucent lock, and settles the promise with its result. The caller never
95
- /// waits for the main thread, so the lock order stays deadlock-free.
123
+ /// `main(f)` from lucent:thread, kept for module code: runs `f` on the main
124
+ /// thread holding the Lucent lock, and settles the promise with its result.
125
+ /// The caller never waits for the main thread, so the lock order stays
126
+ /// deadlock-free, but the main thread waits for any module job. UI work
127
+ /// uses the main context (runIn) instead.
96
128
  template <class F>
97
129
  auto runOnMain(F f) -> Promise<std::invoke_result_t<F>> {
98
130
  using R = std::invoke_result_t<F>;
@@ -113,4 +145,62 @@ auto runOnMain(F f) -> Promise<std::invoke_result_t<F>> {
113
145
  return p;
114
146
  }
115
147
 
148
+ // --- any other context -------------------------------------------------------
149
+ //
150
+ // The same entries for another context, such as the main context: none
151
+ // takes the Lucent lock.
152
+
153
+ /// A platform callback into `context` that returns nothing and outlives the
154
+ /// call: a turn of that context. `f` owns what it captured and is released
155
+ /// on the context after it runs (or at once if the context has shut down).
156
+ template <class F>
157
+ void postTo(ExecutionContext& context, F f) {
158
+ context.post(std::move(f));
159
+ }
160
+
161
+ /// A platform callback into `context` the platform waits for, made on the
162
+ /// context's own thread (the main thread, for the main context): runs now,
163
+ /// in the context, and the microtasks it queued run before it returns. A
164
+ /// Lucent error, or a call from any other thread, is reported and the
165
+ /// platform gets a default result.
166
+ template <class F>
167
+ auto callNowIn(ExecutionContext& context, F f) -> std::invoke_result_t<F&> {
168
+ using R = std::invoke_result_t<F&>;
169
+ try {
170
+ ContextEntry entry(context);
171
+ return f();
172
+ } catch (...) {
173
+ reportUncaught(std::current_exception(), "callback");
174
+ if constexpr (!std::is_void_v<R>) return R{};
175
+ }
176
+ }
177
+
178
+ /// Runs `f` as a turn of `context` and settles the promise, which belongs to
179
+ /// the calling context, with its result there; a context that has shut down
180
+ /// rejects it. Neither side waits for the other. What `f` captures and
181
+ /// returns crosses threads, so it must not share mutable state with the
182
+ /// caller.
183
+ template <class F>
184
+ auto runIn(ExecutionContext& context, F f) -> Promise<std::invoke_result_t<F>> {
185
+ using R = std::invoke_result_t<F>;
186
+ Promise<R> p;
187
+
188
+ bool posted = context.post([p, f = std::move(f)]() mutable {
189
+ try {
190
+ if constexpr (std::is_void_v<R>) {
191
+ f();
192
+ p.resolve(undefined);
193
+ } else {
194
+ p.resolve(f());
195
+ }
196
+ } catch (...) {
197
+ p.reject(currentError(std::current_exception()));
198
+ }
199
+ });
200
+
201
+ if (!posted) p.reject(makeError(String::fromLatin1("AbortError"), String::fromLatin1("The execution context has shut down")));
202
+
203
+ return p;
204
+ }
205
+
116
206
  } // namespace lucent
@@ -8,6 +8,12 @@
8
8
  #include <random>
9
9
  #include <string>
10
10
 
11
+ // Dragonbox tests internal macros that may be undefined.
12
+ #pragma GCC diagnostic push
13
+ #pragma GCC diagnostic ignored "-Wundef"
14
+ #include "../third_party/dragonbox/dragonbox.h"
15
+ #pragma GCC diagnostic pop
16
+
11
17
  namespace lucent {
12
18
 
13
19
  namespace {
@@ -58,31 +64,19 @@ Decimal roundHalfUp(double a, int significant) {
58
64
  return d;
59
65
  }
60
66
 
61
- Decimal withPrecision(double v, int precision) {
62
- char buf[64];
63
- std::snprintf(buf, sizeof buf, "%.*e", precision - 1, v);
64
- return parseScientific(buf);
65
- }
66
-
67
- bool roundTrips(double v, int precision) {
68
- char buf[64];
69
- std::snprintf(buf, sizeof buf, "%.*e", precision - 1, v);
70
- // strtod needs '.', which is what the C locale prints.
71
- for (char* c = buf; *c; c++) {
72
- if (*c == ',') *c = '.';
73
- }
74
- return std::strtod(buf, nullptr) == v;
75
- }
76
-
77
- // Shortest digit string that round-trips (ECMA-262 Number::toString step 5).
67
+ /// The fewest digits that read back as `v` (finite, positive), the closest
68
+ /// to it of those (ECMA-262 Number::toString step 5). The correctly rounded
69
+ /// digits at that length are not always among them: below a power of two
70
+ /// the doubles are twice as dense, so only a farther string reads back.
78
71
  Decimal shortest(double v) {
79
- int lo = 1, hi = 17;
80
- while (lo < hi) {
81
- int mid = (lo + hi) / 2;
82
- if (roundTrips(v, mid)) hi = mid;
83
- else lo = mid + 1;
84
- }
85
- return withPrecision(v, lo);
72
+ auto decimal = jkj::dragonbox::to_decimal(v);
73
+ char digits[24];
74
+ auto end = std::to_chars(digits, digits + sizeof digits, decimal.significand).ptr;
75
+
76
+ Decimal d;
77
+ d.digits.assign(digits, end);
78
+ d.n = decimal.exponent + static_cast<int>(d.digits.size());
79
+ return d;
86
80
  }
87
81
 
88
82
  std::string formatDecimal(const Decimal& d) {
@@ -226,18 +220,19 @@ String numberToString(double v, double radixValue) {
226
220
  }
227
221
  } while (fraction >= delta);
228
222
  }
223
+ // Integer digits, least significant first. Those a double cannot tell
224
+ // apart (from 2^53 up) are zeros, as V8 prints them.
229
225
  std::string intDigits;
230
- if (integer == 0) {
231
- intDigits = "0";
232
- } else {
233
- while (integer >= 1) {
234
- double q = std::floor(integer / radix);
235
- int digit = static_cast<int>(integer - q * radix);
236
- intDigits.push_back(chars[digit]);
237
- integer = q;
238
- }
239
- std::reverse(intDigits.begin(), intDigits.end());
226
+ while (integer / radix >= 9007199254740992.0) {
227
+ integer /= radix;
228
+ intDigits.push_back('0');
240
229
  }
230
+ do {
231
+ double remainder = std::fmod(integer, radix);
232
+ intDigits.push_back(chars[static_cast<int>(remainder)]);
233
+ integer = (integer - remainder) / radix;
234
+ } while (integer > 0);
235
+ std::reverse(intDigits.begin(), intDigits.end());
241
236
  std::string out = negative ? "-" : "";
242
237
  out += intDigits;
243
238
  if (!fracDigits.empty()) out += "." + fracDigits;
@@ -249,10 +244,10 @@ String numberToFixed(double v, double digitsValue) {
249
244
  if (f < 0 || f > 100) throwRangeError("toFixed() digits argument must be between 0 and 100");
250
245
  if (std::isnan(v)) return String::fromLatin1("NaN");
251
246
  if (std::fabs(v) >= 1e21 || std::isinf(v)) return numberToString(v);
252
- std::string digits = fixedDigits(v, static_cast<int>(f));
253
- bool allZero = digits.find_first_not_of("0.") == std::string::npos;
254
- std::string out = (v < 0 && !allZero) ? "-" : "";
255
- return String::fromLatin1(out + digits);
247
+ // A negative value keeps its sign even when it rounds to zero (-0.00);
248
+ // only -0 has none.
249
+ std::string out = v < 0 ? "-" : "";
250
+ return String::fromLatin1(out + fixedDigits(v, static_cast<int>(f)));
256
251
  }
257
252
 
258
253
  String numberToExponential(double v, double digitsValue) {
@@ -542,4 +537,9 @@ double max(double a, double b) {
542
537
 
543
538
  } // namespace math
544
539
 
540
+ void throwInexactInteger(const std::string& value) {
541
+ std::string message = value + " is not an integer a number holds exactly (within +-(2^53 - 1))";
542
+ throwRangeError(message.c_str());
543
+ }
544
+
545
545
  } // namespace lucent
@@ -5,6 +5,7 @@
5
5
  #include <cmath>
6
6
  #include <cstdint>
7
7
  #include <limits>
8
+ #include <string>
8
9
  #include <type_traits>
9
10
 
10
11
  #include "jsstring.h"
@@ -70,6 +71,38 @@ inline String toJsString(Null) { return String::fromLatin1("null"); }
70
71
  inline bool isInteger(double v) { return std::isfinite(v) && std::trunc(v) == v; }
71
72
  inline bool isSafeInteger(double v) { return isInteger(v) && std::fabs(v) <= 9007199254740991.0; }
72
73
 
74
+ [[noreturn]] void throwInexactInteger(const std::string& value);
75
+
76
+ /// A native integer (a 64-bit one: Int64, NSInteger, Java's long) as a
77
+ /// number, exactly: RangeError beyond +-(2^53 - 1), which a number cannot
78
+ /// tell apart from its neighbors. Never rounded.
79
+ template <class I>
80
+ requires std::is_integral_v<I>
81
+ double exactNumber(I v) {
82
+ constexpr uint64_t kMax = 9007199254740991; // 2^53 - 1
83
+ bool fits;
84
+ if constexpr (std::is_signed_v<I>)
85
+ fits = v >= -static_cast<int64_t>(kMax) && v <= static_cast<int64_t>(kMax);
86
+ else
87
+ fits = static_cast<uint64_t>(v) <= kMax;
88
+
89
+ if (!fits) throwInexactInteger(std::to_string(v));
90
+ return static_cast<double>(v);
91
+ }
92
+
93
+ /// A number as a native 64-bit integer, as WebIDL's [EnforceRange] long
94
+ /// long converts it: truncated toward zero; RangeError when it is not
95
+ /// finite or beyond what a number holds exactly (below 0 for unsigned).
96
+ template <class I>
97
+ requires std::is_integral_v<I>
98
+ I toExactInteger(double v) {
99
+ double t = std::trunc(v);
100
+ double low = std::is_signed_v<I> ? -9007199254740991.0 : 0.0;
101
+ if (!(t >= low && t <= 9007199254740991.0)) throwInexactInteger(numberToString(v).toUtf8());
102
+
103
+ return static_cast<I>(t);
104
+ }
105
+
73
106
  namespace math {
74
107
  inline double abs(double v) { return std::fabs(v); }
75
108
  inline double floor(double v) { return std::floor(v); }
@@ -0,0 +1,65 @@
1
+ // Lucent runtime — native operations as promises.
2
+ //
3
+ // Native work that completes later, on any thread (a Kotlin coroutine, a
4
+ // platform callback), becomes a promise of the context that started it: an
5
+ // Operation (scope.h) under that context's root scope, whose outcome settles
6
+ // the promise there. Disposing the scope, or the AbortSignal the caller gave
7
+ // aborting, cancels it: the promise rejects at once, the registration's
8
+ // cleanup tells the native work to stop, and whatever it produces after that
9
+ // is released, never delivered.
10
+ #pragma once
11
+
12
+ #include <functional>
13
+ #include <memory>
14
+ #include <utility>
15
+
16
+ #include "abort.h"
17
+ #include "async.h"
18
+ #include "execution.h"
19
+ #include "scope.h"
20
+
21
+ namespace lucent {
22
+
23
+ /// Starts native work for the calling context: `registration` begins it and
24
+ /// returns what ends it (see Operation::start). Already aborted, the work
25
+ /// never starts.
26
+ template <class T>
27
+ Promise<T> nativeOperation(typename Operation<T>::Registration registration, Opt<AbortSignal> signal = {}) {
28
+ ContextRef owner = ExecutionContext::currentRef();
29
+ std::shared_ptr<Scope> scope = ExecutionContext::of(owner).root();
30
+ AbortSignal abort = signal.has() ? signal.get() : nullptr;
31
+
32
+ Opt<Error> abortedBy;
33
+ if (abort && abort->aborted.load()) abortedBy = abort->reason;
34
+
35
+ Promise<T> promise;
36
+ auto operation = Operation<T>::start(scope, registration, abortedBy);
37
+
38
+ // The signal holds a listener only while the operation is pending.
39
+ if (abort && operation->state() == OperationState::Pending) {
40
+ std::weak_ptr<Operation<T>> pending = operation;
41
+ std::weak_ptr<AbortSignalObject> weak = abort;
42
+
43
+ uint64_t id = abort->add([pending, weak] {
44
+ auto op = pending.lock();
45
+ auto s = weak.lock();
46
+ if (op && s) op->cancel(s->reason);
47
+ });
48
+
49
+ operation->onSettled([weak, id](const typename Operation<T>::Outcome&) {
50
+ if (auto s = weak.lock(); s && id) s->remove(id);
51
+ });
52
+ }
53
+
54
+ operation->onSettled([promise](const typename Operation<T>::Outcome& outcome) {
55
+ if (outcome.state == OperationState::Succeeded) {
56
+ promise.resolve(*outcome.value);
57
+ } else {
58
+ promise.reject(outcome.error);
59
+ }
60
+ });
61
+
62
+ return promise;
63
+ }
64
+
65
+ } // namespace lucent
@@ -7,6 +7,7 @@
7
7
 
8
8
  #include "array.h"
9
9
  #include "async.h"
10
+ #include "bigint.h"
10
11
  #include "bytes.h"
11
12
  #include "core.h"
12
13
  #include "jserror.h"
@@ -21,6 +22,7 @@ namespace lucent {
21
22
  inline bool truthy(bool v) { return v; }
22
23
  inline bool truthy(double v) { return v != 0 && !std::isnan(v); }
23
24
  inline bool truthy(const String& s) { return !s.empty(); }
25
+ inline bool truthy(const BigInt& v) { return !v.isZero(); }
24
26
  inline bool truthy(Undefined) { return false; }
25
27
  inline bool truthy(Null) { return false; }
26
28
  template <class T>
@@ -59,6 +61,7 @@ String toJsString(const std::variant<Ts...>& v) {
59
61
  inline String typeOf(double) { return String::fromLatin1("number"); }
60
62
  inline String typeOf(bool) { return String::fromLatin1("boolean"); }
61
63
  inline String typeOf(const String&) { return String::fromLatin1("string"); }
64
+ inline String typeOf(const BigInt&) { return String::fromLatin1("bigint"); }
62
65
  inline String typeOf(Undefined) { return String::fromLatin1("undefined"); }
63
66
  inline String typeOf(Null) { return String::fromLatin1("object"); }
64
67
  template <class Sig>
@@ -81,11 +84,6 @@ String typeOf(const T&) {
81
84
  }
82
85
 
83
86
  // --- unions -------------------------------------------------------------------
84
- template <class T>
85
- struct IsVariant : std::false_type {};
86
- template <class... Ts>
87
- struct IsVariant<std::variant<Ts...>> : std::true_type {};
88
-
89
87
  template <class T, class V>
90
88
  constexpr bool variantHolds() {
91
89
  if constexpr (IsVariant<V>::value) {
@@ -109,14 +107,24 @@ To convertImpl(const From& from) {
109
107
  return from;
110
108
  } else if constexpr (IsOpt<To>::value) {
111
109
  using Inner = typename To::ValueType;
110
+ // `null | undefined` (Opt<Undefined>) holds no value: narrowing proved
111
+ // any present one away, and throws here. Not in a helper returning To
112
+ // that only throws: clang 18 (Android NDK r27's) then returns garbage
113
+ // for the absent values beside it.
112
114
  if constexpr (IsOpt<F>::value) {
113
115
  if (from.isUndefined()) return To(undefined);
114
116
  if (from.isNull()) return To(null);
115
- return To(convert<Inner>(from.get()));
117
+ if constexpr (IsAbsent<Inner>) {
118
+ throwTypeError("Value does not match the expected type");
119
+ } else {
120
+ return To(convert<Inner>(from.get()));
121
+ }
116
122
  } else if constexpr (std::is_same_v<F, Undefined>) {
117
123
  return To(undefined);
118
124
  } else if constexpr (std::is_same_v<F, Null>) {
119
125
  return To(null);
126
+ } else if constexpr (IsAbsent<Inner>) {
127
+ throwTypeError("Value does not match the expected type");
120
128
  } else {
121
129
  return To(convert<Inner>(from));
122
130
  }