@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,152 @@
1
+ // Lucent runtime — strings with JavaScript semantics.
2
+ //
3
+ // A String is an immutable sequence of UTF-16 code units, shared by
4
+ // reference. Strings whose code units all fit in one byte (Latin-1) are
5
+ // stored one byte per unit, the way JavaScript engines do.
6
+ #pragma once
7
+
8
+ #include <atomic>
9
+ #include <string>
10
+ #include <string_view>
11
+ #include <vector>
12
+
13
+ #include "core.h"
14
+
15
+ namespace lucent {
16
+
17
+ class String {
18
+ public:
19
+ String() = default;
20
+
21
+ /// Bytes that are all < 0x80, or Latin-1 code units.
22
+ static String fromLatin1(std::string_view bytes);
23
+ /// Takes the buffer instead of copying it (Latin-1, as fromLatin1).
24
+ static String adoptLatin1(std::string&& bytes) { return make(std::move(bytes)); }
25
+ static String fromUtf8(std::string_view utf8);
26
+ static String fromUtf16(const char16_t* units, size_t length);
27
+ static String fromUtf16(std::u16string_view units) { return fromUtf16(units.data(), units.size()); }
28
+ static String fromCodeUnit(char16_t unit);
29
+ /// String.fromCodePoint for one code point.
30
+ static String fromCodePoint(double codePoint);
31
+
32
+ size_t length() const { return d_ ? d_->length() : n_; }
33
+ bool empty() const { return length() == 0; }
34
+ /// Unchecked code unit access.
35
+ char16_t unit(size_t i) const {
36
+ if (!d_) return static_cast<unsigned char>(s_[i]);
37
+ return d_->oneByte ? static_cast<unsigned char>(d_->bytes[i]) : d_->wide[i];
38
+ }
39
+ bool isOneByte() const { return !d_ || d_->oneByte; }
40
+
41
+ std::string toUtf8() const;
42
+ std::u16string toUtf16() const;
43
+ /// Latin-1 bytes; only valid when isOneByte().
44
+ std::string_view latin1() const { return d_ ? std::string_view(d_->bytes) : std::string_view(s_, n_); }
45
+ /// UTF-16 units; only valid when !isOneByte().
46
+ std::u16string_view utf16() const { return d_ ? std::u16string_view(d_->wide) : std::u16string_view(); }
47
+
48
+ // --- JavaScript String.prototype ---------------------------------------
49
+ double charCodeAt(double index) const;
50
+ String charAt(double index) const;
51
+ Opt<String> at(double index) const;
52
+ Opt<double> codePointAt(double index) const;
53
+ double indexOf(const String& search, double from = 0) const;
54
+ double lastIndexOf(const String& search) const;
55
+ double lastIndexOf(const String& search, double from) const;
56
+ bool includes(const String& search, double from = 0) const { return indexOf(search, from) >= 0; }
57
+ bool startsWith(const String& search, double from = 0) const;
58
+ bool endsWith(const String& search) const;
59
+ bool endsWith(const String& search, double endPosition) const;
60
+ String slice(double start) const;
61
+ String slice(double start, double end) const;
62
+ String substring(double start) const;
63
+ String substring(double start, double end) const;
64
+ String toUpperCase() const;
65
+ String toLowerCase() const;
66
+ String trim() const;
67
+ String trimStart() const;
68
+ String trimEnd() const;
69
+ String repeat(double count) const;
70
+ String padStart(double targetLength, const String& fill) const;
71
+ String padEnd(double targetLength, const String& fill) const;
72
+ String replace(const String& search, const String& replacement) const;
73
+ String replaceAll(const String& search, const String& replacement) const;
74
+ String concat(const String& other) const { return *this + other; }
75
+ double localeCompare(const String& other) const;
76
+ String normalize() const { return *this; }
77
+
78
+ /// Substring by code unit range, clamped.
79
+ String sub(size_t begin, size_t end) const;
80
+ size_t find(const String& needle, size_t from) const;
81
+
82
+ friend String operator+(const String& a, const String& b);
83
+ /// `s += x`. Mutates in place when this handle is the only owner.
84
+ String& operator+=(const String& other);
85
+
86
+ friend bool operator==(const String& a, const String& b);
87
+ friend bool operator!=(const String& a, const String& b) { return !(a == b); }
88
+ friend bool operator<(const String& a, const String& b) { return compare(a, b) < 0; }
89
+ friend bool operator>(const String& a, const String& b) { return compare(a, b) > 0; }
90
+ friend bool operator<=(const String& a, const String& b) { return compare(a, b) <= 0; }
91
+ friend bool operator>=(const String& a, const String& b) { return compare(a, b) >= 0; }
92
+ static int compare(const String& a, const String& b);
93
+
94
+ size_t hash() const;
95
+
96
+ private:
97
+ /// Short Latin-1 strings live in the handle, so the strings most calls
98
+ /// pass (names, keys, words) cost no allocation. make() stores every
99
+ /// one-byte string this short inline: Data holds longer or two-byte ones.
100
+ static constexpr size_t kInline = 15;
101
+
102
+ struct Data {
103
+ bool oneByte = true;
104
+ std::string bytes;
105
+ std::u16string wide;
106
+ mutable std::atomic<size_t> hash{0};
107
+ size_t length() const { return oneByte ? bytes.size() : wide.size(); }
108
+ };
109
+ explicit String(std::shared_ptr<Data> d) : d_(std::move(d)) {}
110
+ /// a + b stored inline; the two fit in kInline bytes.
111
+ static String inlined(std::string_view a, std::string_view b);
112
+ static String make(std::string&& latin1);
113
+ static String make(std::u16string&& wide); // narrows to one byte when possible
114
+ void appendUnitsTo(std::u16string& out) const;
115
+
116
+ std::shared_ptr<Data> d_; // null: the string is inline (n_ bytes of s_)
117
+ uint8_t n_ = 0;
118
+ char s_[kInline] = {};
119
+ friend class StringBuilder;
120
+ };
121
+
122
+ /// A string literal with static storage: built once per call site.
123
+ /// Builds a string from parts. With the size reserved up front, the buffer
124
+ /// grows once and becomes the result's storage, so building allocates about
125
+ /// as much as the result itself.
126
+ class StringBuilder {
127
+ public:
128
+ StringBuilder(size_t capacity, bool oneByte);
129
+ void append(const String& s);
130
+ /// Bytes < 0x80 (digits, signs, exponents).
131
+ void appendAscii(std::string_view ascii);
132
+ String build() &&;
133
+
134
+ private:
135
+ bool oneByte_;
136
+ std::string bytes_;
137
+ std::u16string wide_;
138
+ };
139
+
140
+ #define LUCENT_STR(literal) \
141
+ ([]() -> const ::lucent::String& { static const ::lucent::String s = ::lucent::String::fromUtf8(std::string_view(literal, sizeof(literal) - 1)); return s; }())
142
+ #define LUCENT_STR16(literal) \
143
+ ([]() -> const ::lucent::String& { static const ::lucent::String s = ::lucent::String::fromUtf16(literal, sizeof(literal) / sizeof(char16_t) - 1); return s; }())
144
+
145
+ bool isJsWhitespace(char16_t c);
146
+
147
+ } // namespace lucent
148
+
149
+ template <>
150
+ struct std::hash<lucent::String> {
151
+ size_t operator()(const lucent::String& s) const { return s.hash(); }
152
+ };
@@ -0,0 +1,24 @@
1
+ // Lucent runtime — umbrella header included by generated code.
2
+ #pragma once
3
+
4
+ #include "abort.h"
5
+ #include "array.h"
6
+ #include "async.h"
7
+ #include "bytes.h"
8
+ #include "console.h"
9
+ #include "core.h"
10
+ #include "date.h"
11
+ #include "equality.h"
12
+ #include "jserror.h"
13
+ #include "function.h"
14
+ #include "generator.h"
15
+ #include "helpers.h"
16
+ #include "json.h"
17
+ #include "json_parse.h"
18
+ #include "map.h"
19
+ #include "native.h"
20
+ #include "number.h"
21
+ #include "regexp.h"
22
+ #include "ops.h"
23
+ #include "scheduler.h"
24
+ #include "jsstring.h"
@@ -0,0 +1,344 @@
1
+ // Lucent runtime — Map, Set and Dict (string-keyed plain objects), all
2
+ // iterating in insertion order like their JavaScript counterparts.
3
+ #pragma once
4
+
5
+ #include <algorithm>
6
+ #include <cmath>
7
+ #include <cstdint>
8
+ #include <cstring>
9
+ #include <memory>
10
+ #include <vector>
11
+
12
+ #include "array.h"
13
+ #include "core.h"
14
+ #include "equality.h"
15
+
16
+ namespace lucent {
17
+
18
+ namespace detail {
19
+
20
+ template <class K>
21
+ struct KeyHash {
22
+ size_t operator()(const K& k) const {
23
+ if constexpr (std::is_same_v<K, double>) {
24
+ if (std::isnan(k)) return 0x7ff8;
25
+ double v = k == 0 ? 0.0 : k; // -0 and +0 are the same key
26
+ uint64_t bits;
27
+ std::memcpy(&bits, &v, sizeof bits);
28
+ return std::hash<uint64_t>()(bits);
29
+ } else if constexpr (std::is_same_v<K, bool>) {
30
+ return k ? 1 : 2;
31
+ } else if constexpr (std::is_same_v<K, String>) {
32
+ return k.hash();
33
+ } else if constexpr (IsRef<K>::value) {
34
+ return std::hash<const void*>()(static_cast<const void*>(k.get()));
35
+ } else {
36
+ return std::hash<const void*>()(k.identity());
37
+ }
38
+ }
39
+ };
40
+
41
+ template <class K>
42
+ struct KeyEq {
43
+ bool operator()(const K& a, const K& b) const { return sameValueZero(a, b); }
44
+ };
45
+
46
+ // Insertion-ordered hash table: entries in insertion order, plus an
47
+ // open-addressing index (power-of-two buckets, linear probing) from key hash
48
+ // to entry. Deleted entries leave tombstones so an iteration in progress
49
+ // keeps its position; they are compacted later.
50
+ template <class K, class V>
51
+ class OrderedTable {
52
+ public:
53
+ struct Entry {
54
+ K key;
55
+ V value;
56
+ bool live;
57
+ };
58
+
59
+ size_t size() const { return live_; }
60
+ size_t slotCount() const { return entries_.size(); }
61
+ bool slotLive(size_t i) const { return entries_[i].live; }
62
+ const Entry& slot(size_t i) const { return entries_[i]; }
63
+ Entry& slot(size_t i) { return entries_[i]; }
64
+
65
+ Entry* find(const K& k) {
66
+ size_t b = lookup(k, hashOf(k));
67
+ return b == kNone ? nullptr : &entries_[buckets_[b].entry];
68
+ }
69
+ const Entry* find(const K& k) const { return const_cast<OrderedTable*>(this)->find(k); }
70
+ void set(const K& k, V v) {
71
+ size_t h = hashOf(k);
72
+ size_t b = lookup(k, h);
73
+ if (b != kNone) {
74
+ entries_[buckets_[b].entry].value = std::move(v);
75
+ return;
76
+ }
77
+ maybeCompact();
78
+ if ((used_ + 1) * 2 > buckets_.size()) rebuild(std::max<size_t>(8, live_ * 4));
79
+ size_t i = h & (buckets_.size() - 1);
80
+ while (buckets_[i].entry != kEmpty && buckets_[i].entry != kDeleted) i = (i + 1) & (buckets_.size() - 1);
81
+ if (buckets_[i].entry == kEmpty) used_++;
82
+ buckets_[i] = Bucket{h, static_cast<uint32_t>(entries_.size())};
83
+ entries_.push_back(Entry{k, std::move(v), true});
84
+ live_++;
85
+ }
86
+ bool remove(const K& k) {
87
+ size_t b = lookup(k, hashOf(k));
88
+ if (b == kNone) return false;
89
+ Entry& e = entries_[buckets_[b].entry];
90
+ e.live = false;
91
+ e.value = V();
92
+ buckets_[b].entry = kDeleted;
93
+ live_--;
94
+ return true;
95
+ }
96
+ void clear() {
97
+ for (auto& e : entries_) {
98
+ e.live = false;
99
+ e.value = V();
100
+ }
101
+ for (auto& b : buckets_) b.entry = kEmpty;
102
+ used_ = 0;
103
+ live_ = 0;
104
+ }
105
+ /// Iteration guard: compaction is deferred while any iteration is active.
106
+ struct Iterating {
107
+ explicit Iterating(OrderedTable& t) : t_(t) { t_.iterators_++; }
108
+ ~Iterating() { t_.iterators_--; }
109
+ OrderedTable& t_;
110
+ };
111
+
112
+ private:
113
+ static constexpr uint32_t kEmpty = UINT32_MAX;
114
+ static constexpr uint32_t kDeleted = UINT32_MAX - 1;
115
+ static constexpr size_t kNone = SIZE_MAX;
116
+ struct Bucket {
117
+ size_t hash;
118
+ uint32_t entry = kEmpty;
119
+ };
120
+
121
+ static size_t hashOf(const K& k) {
122
+ // Integer and pointer hashes are the identity in common standard
123
+ // libraries; mix so that the low bits the index masks with vary.
124
+ uint64_t x = KeyHash<K>()(k);
125
+ x ^= x >> 33;
126
+ x *= 0xff51afd7ed558ccdULL;
127
+ x ^= x >> 33;
128
+ return static_cast<size_t>(x);
129
+ }
130
+
131
+ /// The bucket holding `k`, or kNone.
132
+ size_t lookup(const K& k, size_t h) const {
133
+ if (buckets_.empty()) return kNone;
134
+ size_t mask = buckets_.size() - 1;
135
+ for (size_t i = h & mask;; i = (i + 1) & mask) {
136
+ const Bucket& b = buckets_[i];
137
+ if (b.entry == kEmpty) return kNone;
138
+ if (b.entry != kDeleted && b.hash == h && KeyEq<K>()(entries_[b.entry].key, k)) return i;
139
+ }
140
+ }
141
+
142
+ /// Re-indexes the live entries into `capacity` (rounded up to a power of two) buckets.
143
+ void rebuild(size_t capacity) {
144
+ size_t n = 8;
145
+ while (n < capacity) n <<= 1;
146
+ buckets_.assign(n, Bucket{});
147
+ used_ = 0;
148
+ for (size_t e = 0; e < entries_.size(); e++) {
149
+ if (!entries_[e].live) continue;
150
+ size_t h = hashOf(entries_[e].key);
151
+ size_t i = h & (n - 1);
152
+ while (buckets_[i].entry != kEmpty) i = (i + 1) & (n - 1);
153
+ buckets_[i] = Bucket{h, static_cast<uint32_t>(e)};
154
+ used_++;
155
+ }
156
+ }
157
+
158
+ void maybeCompact() {
159
+ if (iterators_ > 0 || entries_.size() < 16 || live_ * 2 > entries_.size()) return;
160
+ std::vector<Entry> kept;
161
+ kept.reserve(live_ + 1);
162
+ for (auto& e : entries_) {
163
+ if (e.live) kept.push_back(std::move(e));
164
+ }
165
+ entries_.swap(kept);
166
+ rebuild(std::max<size_t>(8, live_ * 4));
167
+ }
168
+
169
+ std::vector<Entry> entries_;
170
+ std::vector<Bucket> buckets_;
171
+ /// Buckets that are not empty (live or tombstones): probes stop at empty ones.
172
+ size_t used_ = 0;
173
+ size_t live_ = 0;
174
+ int iterators_ = 0;
175
+ };
176
+
177
+ struct Empty {};
178
+
179
+ } // namespace detail
180
+
181
+ template <class K, class V>
182
+ class Map {
183
+ public:
184
+ using Table = detail::OrderedTable<K, V>;
185
+ Map() : t_(std::make_shared<Table>()) {}
186
+
187
+ double size() const { return static_cast<double>(t_->size()); }
188
+ Opt<V> get(const K& k) const {
189
+ if (auto* e = t_->find(k)) return e->value;
190
+ return undefined;
191
+ }
192
+ bool has(const K& k) const { return t_->find(k) != nullptr; }
193
+ Map& set(const K& k, V v) {
194
+ t_->set(k, std::move(v));
195
+ return *this;
196
+ }
197
+ bool remove(const K& k) { return t_->remove(k); }
198
+ void clear() { t_->clear(); }
199
+
200
+ template <class F>
201
+ void forEach(F&& f) const {
202
+ typename Table::Iterating guard(*t_);
203
+ for (size_t i = 0; i < t_->slotCount(); i++) {
204
+ if (!t_->slotLive(i)) continue;
205
+ K k = t_->slot(i).key;
206
+ V v = t_->slot(i).value;
207
+ invokeCallback(f, v, k, *this);
208
+ }
209
+ }
210
+ Array<K> keys() const {
211
+ Array<K> out;
212
+ for (size_t i = 0; i < t_->slotCount(); i++) {
213
+ if (t_->slotLive(i)) out.push(t_->slot(i).key);
214
+ }
215
+ return out;
216
+ }
217
+ Array<V> values() const {
218
+ Array<V> out;
219
+ for (size_t i = 0; i < t_->slotCount(); i++) {
220
+ if (t_->slotLive(i)) out.push(t_->slot(i).value);
221
+ }
222
+ return out;
223
+ }
224
+
225
+ Table& table() const { return *t_; }
226
+ const void* identity() const { return t_.get(); }
227
+ friend bool strictEquals(const Map& a, const Map& b) { return a.t_ == b.t_; }
228
+ friend String toJsString(const Map&) { return String::fromLatin1("[object Map]"); }
229
+
230
+ private:
231
+ std::shared_ptr<Table> t_;
232
+ };
233
+
234
+ template <class T>
235
+ class Set {
236
+ public:
237
+ using Table = detail::OrderedTable<T, detail::Empty>;
238
+ Set() : t_(std::make_shared<Table>()) {}
239
+ explicit Set(const Array<T>& items) : Set() {
240
+ for (const auto& v : items.items()) add(static_cast<T>(v));
241
+ }
242
+
243
+ double size() const { return static_cast<double>(t_->size()); }
244
+ bool has(const T& v) const { return t_->find(v) != nullptr; }
245
+ Set& add(const T& v) {
246
+ if (!t_->find(v)) t_->set(v, detail::Empty{});
247
+ return *this;
248
+ }
249
+ bool remove(const T& v) { return t_->remove(v); }
250
+ void clear() { t_->clear(); }
251
+ template <class F>
252
+ void forEach(F&& f) const {
253
+ typename Table::Iterating guard(*t_);
254
+ for (size_t i = 0; i < t_->slotCount(); i++) {
255
+ if (!t_->slotLive(i)) continue;
256
+ T v = t_->slot(i).key;
257
+ invokeCallback(f, v, v, *this);
258
+ }
259
+ }
260
+ Array<T> values() const {
261
+ Array<T> out;
262
+ for (size_t i = 0; i < t_->slotCount(); i++) {
263
+ if (t_->slotLive(i)) out.push(t_->slot(i).key);
264
+ }
265
+ return out;
266
+ }
267
+ Array<T> keys() const { return values(); }
268
+
269
+ Table& table() const { return *t_; }
270
+ const void* identity() const { return t_.get(); }
271
+ friend bool strictEquals(const Set& a, const Set& b) { return a.t_ == b.t_; }
272
+ friend String toJsString(const Set&) { return String::fromLatin1("[object Set]"); }
273
+
274
+ private:
275
+ std::shared_ptr<Table> t_;
276
+ };
277
+
278
+ /// `Record<string, V>` / `{ [key: string]: V }`: a plain object used as a
279
+ /// dictionary.
280
+ template <class V>
281
+ class Dict {
282
+ public:
283
+ using Table = detail::OrderedTable<String, V>;
284
+ Dict() : t_(std::make_shared<Table>()) {}
285
+
286
+ Opt<V> get(const String& k) const {
287
+ if (auto* e = t_->find(k)) return e->value;
288
+ return undefined;
289
+ }
290
+ void set(const String& k, V v) { t_->set(k, std::move(v)); }
291
+ bool has(const String& k) const { return t_->find(k) != nullptr; }
292
+ bool remove(const String& k) { return t_->remove(k); }
293
+ size_t count() const { return t_->size(); }
294
+
295
+ /// Object.keys order: integer-like keys ascending, then insertion order.
296
+ Array<String> keys() const {
297
+ std::vector<std::pair<double, String>> numeric;
298
+ Array<String> rest;
299
+ for (size_t i = 0; i < t_->slotCount(); i++) {
300
+ if (!t_->slotLive(i)) continue;
301
+ const String& k = t_->slot(i).key;
302
+ double index;
303
+ if (isArrayIndex(k, index)) numeric.emplace_back(index, k);
304
+ else rest.push(k);
305
+ }
306
+ if (numeric.empty()) return rest;
307
+ std::stable_sort(numeric.begin(), numeric.end(), [](const auto& a, const auto& b) { return a.first < b.first; });
308
+ Array<String> out;
309
+ for (auto& p : numeric) out.push(p.second);
310
+ out.append(rest);
311
+ return out;
312
+ }
313
+ Array<V> values() const {
314
+ Array<V> out;
315
+ Array<String> ks = keys();
316
+ for (const auto& k : ks.items()) out.push(t_->find(k)->value);
317
+ return out;
318
+ }
319
+
320
+ Table& table() const { return *t_; }
321
+ const void* identity() const { return t_.get(); }
322
+ friend bool strictEquals(const Dict& a, const Dict& b) { return a.t_ == b.t_; }
323
+ friend String toJsString(const Dict&) { return String::fromLatin1("[object Object]"); }
324
+
325
+ static bool isArrayIndex(const String& k, double& out) {
326
+ size_t n = k.length();
327
+ if (n == 0 || n > 10) return false;
328
+ if (n > 1 && k.unit(0) == '0') return false;
329
+ double v = 0;
330
+ for (size_t i = 0; i < n; i++) {
331
+ char16_t c = k.unit(i);
332
+ if (c < '0' || c > '9') return false;
333
+ v = v * 10 + (c - '0');
334
+ }
335
+ if (v >= 4294967295.0) return false;
336
+ out = v;
337
+ return true;
338
+ }
339
+
340
+ private:
341
+ std::shared_ptr<Table> t_;
342
+ };
343
+
344
+ } // namespace lucent
@@ -0,0 +1,76 @@
1
+ // Lucent runtime — the main thread on hosts without one (tests, tools). iOS
2
+ // and Android implement postToMain in lucent/platform.
3
+ #include "native.h"
4
+
5
+ #include <atomic>
6
+
7
+ #if defined(__APPLE__)
8
+ #include <TargetConditionals.h>
9
+ #endif
10
+
11
+ namespace lucent {
12
+ namespace {
13
+ std::atomic<long> nativeRefs{0};
14
+ }
15
+ long liveNativeRefs() { return nativeRefs.load(); }
16
+ void detail::countNativeRef(int delta) { nativeRefs += delta; }
17
+ } // namespace lucent
18
+
19
+ #if !defined(__ANDROID__) && !(defined(TARGET_OS_IOS) && TARGET_OS_IOS)
20
+
21
+ #include <condition_variable>
22
+ #include <deque>
23
+ #include <mutex>
24
+ #include <thread>
25
+
26
+ namespace lucent {
27
+
28
+ namespace {
29
+
30
+ class HostMainThread {
31
+ public:
32
+ static HostMainThread& instance() {
33
+ static HostMainThread* t = new HostMainThread(); // lives for the process
34
+ return *t;
35
+ }
36
+ void post(std::function<void()> job) {
37
+ {
38
+ std::lock_guard<std::mutex> g(m_);
39
+ jobs_.push_back(std::move(job));
40
+ }
41
+ cv_.notify_one();
42
+ }
43
+ bool current() const { return std::this_thread::get_id() == id_; }
44
+
45
+ private:
46
+ HostMainThread() {
47
+ std::thread t([this] { run(); });
48
+ id_ = t.get_id();
49
+ t.detach();
50
+ }
51
+ void run() {
52
+ for (;;) {
53
+ std::function<void()> job;
54
+ {
55
+ std::unique_lock<std::mutex> g(m_);
56
+ cv_.wait(g, [this] { return !jobs_.empty(); });
57
+ job = std::move(jobs_.front());
58
+ jobs_.pop_front();
59
+ }
60
+ job();
61
+ }
62
+ }
63
+ std::mutex m_;
64
+ std::condition_variable cv_;
65
+ std::deque<std::function<void()>> jobs_;
66
+ std::thread::id id_;
67
+ };
68
+
69
+ } // namespace
70
+
71
+ void postToMain(std::function<void()> job) { HostMainThread::instance().post(std::move(job)); }
72
+ bool onMainThread() { return HostMainThread::instance().current(); }
73
+
74
+ } // namespace lucent
75
+
76
+ #endif