@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.
- package/app.plugin.js +92 -0
- package/bin/lucent.cjs +16 -0
- package/core/index.cjs +11 -0
- package/dist/bench-CR3E7e2e.js +304 -0
- package/dist/build-CAIrRSYA.js +7 -0
- package/dist/build-check-QozuNJm5.js +92 -0
- package/dist/check-1SCmYlqA.js +7 -0
- package/dist/clean-BVwpbJ5K.js +35 -0
- package/dist/cli.js +406 -0
- package/dist/codes-CIuwXt1S.js +260 -0
- package/dist/compiler.js +4 -0
- package/dist/confirm-BYF5eY3q.js +34 -0
- package/dist/dashboard-B-NzjjaN.js +91 -0
- package/dist/dev-BYWMP7oC.js +221 -0
- package/dist/diagnostic-BM9sXmwl.js +30 -0
- package/dist/diff-BTDo3Sro.js +44 -0
- package/dist/doctor-C0PrfWT6.js +230 -0
- package/dist/doctor-DdR4JnVc.js +30 -0
- package/dist/explain--vV1oaRl.js +35 -0
- package/dist/format-D9fnIMEx.js +53 -0
- package/dist/init-gIabu1S_.js +207 -0
- package/dist/live-steps-BGcWFM9M.js +79 -0
- package/dist/new-module-C2mtb0op.js +66 -0
- package/dist/package-manager-BnxwCySi.js +30 -0
- package/dist/package.json +3 -0
- package/dist/packages-HyskKIsY.js +150 -0
- package/dist/pipeline-CH7-2cxu.js +232 -0
- package/dist/project-D8tEet0k.js +150 -0
- package/dist/sdk-coverage-BoW_3Efd.js +64 -0
- package/dist/sdk-prefetch-mo04JDZV.js +89 -0
- package/dist/sdk-search-Iv4K3-8a.js +124 -0
- package/dist/sdk-show-CkrdlqlP.js +74 -0
- package/dist/sdks-Xoyiy2E4.js +28 -0
- package/dist/src-BD7f1_aZ.js +11493 -0
- package/dist/steps--_h-FFS7.js +24 -0
- package/dist/theme-uv5bc0aK.js +134 -0
- package/dist/tsconfig-S4uyLNzf.js +59 -0
- package/dist/version-bqpIO5sP.js +26 -0
- package/gradle/lucent-classpath.gradle +38 -0
- package/gradle/lucent-classpath.init.gradle +6 -0
- package/gradle/lucent.gradle +24 -0
- package/lib/core.cjs +106 -0
- package/lib/globals.d.ts +36 -0
- package/lib/sdk/android.d.ts +8 -0
- package/lib/sdk/core.d.ts +23 -0
- package/lib/sdk/ios.d.ts +33 -0
- package/lib/sdk/platform.d.ts +8 -0
- package/lib/sdk/thread.d.ts +8 -0
- package/metro/index.cjs +72 -0
- package/metro/index.d.ts +6 -0
- package/metro/transformer.cjs +70 -0
- package/package.json +61 -0
- package/runtime/cpp/lucent/abort.cpp +61 -0
- package/runtime/cpp/lucent/abort.h +54 -0
- package/runtime/cpp/lucent/array.h +442 -0
- package/runtime/cpp/lucent/async.h +236 -0
- package/runtime/cpp/lucent/builtins.cpp +163 -0
- package/runtime/cpp/lucent/bytes.h +129 -0
- package/runtime/cpp/lucent/console.h +17 -0
- package/runtime/cpp/lucent/core.h +129 -0
- package/runtime/cpp/lucent/date.cpp +441 -0
- package/runtime/cpp/lucent/date.h +82 -0
- package/runtime/cpp/lucent/equality.h +87 -0
- package/runtime/cpp/lucent/function.h +61 -0
- package/runtime/cpp/lucent/generator.h +244 -0
- package/runtime/cpp/lucent/helpers.h +226 -0
- package/runtime/cpp/lucent/jserror.cpp +51 -0
- package/runtime/cpp/lucent/jserror.h +66 -0
- package/runtime/cpp/lucent/jsi/convert.cpp +202 -0
- package/runtime/cpp/lucent/jsi/convert.h +612 -0
- package/runtime/cpp/lucent/jsi/host.cpp +322 -0
- package/runtime/cpp/lucent/jsi/host.h +166 -0
- package/runtime/cpp/lucent/json.cpp +243 -0
- package/runtime/cpp/lucent/json.h +164 -0
- package/runtime/cpp/lucent/json_parse.h +138 -0
- package/runtime/cpp/lucent/jsstring.cpp +874 -0
- package/runtime/cpp/lucent/jsstring.h +152 -0
- package/runtime/cpp/lucent/lucent.h +24 -0
- package/runtime/cpp/lucent/map.h +344 -0
- package/runtime/cpp/lucent/native.cpp +76 -0
- package/runtime/cpp/lucent/native.h +116 -0
- package/runtime/cpp/lucent/number.cpp +545 -0
- package/runtime/cpp/lucent/number.h +190 -0
- package/runtime/cpp/lucent/ops.h +165 -0
- package/runtime/cpp/lucent/platform/android.cpp +437 -0
- package/runtime/cpp/lucent/platform/android.h +117 -0
- package/runtime/cpp/lucent/platform/ios.h +297 -0
- package/runtime/cpp/lucent/platform/ios.mm +31 -0
- package/runtime/cpp/lucent/regexp.cpp +440 -0
- package/runtime/cpp/lucent/regexp.h +107 -0
- package/runtime/cpp/lucent/scheduler.cpp +153 -0
- package/runtime/cpp/lucent/scheduler.h +160 -0
- package/runtime/cpp/lucent/unicode_data.inc +4051 -0
- package/runtime/cpp/rn/LucentModule.cpp +35 -0
- package/runtime/cpp/rn/LucentModule.h +37 -0
- package/runtime/cpp/third_party/quickjs/LICENSE +22 -0
- package/runtime/cpp/third_party/quickjs/README.md +11 -0
- package/runtime/cpp/third_party/quickjs/cutils.c +641 -0
- package/runtime/cpp/third_party/quickjs/cutils.h +457 -0
- package/runtime/cpp/third_party/quickjs/libregexp-opcode.h +73 -0
- package/runtime/cpp/third_party/quickjs/libregexp.c +3448 -0
- package/runtime/cpp/third_party/quickjs/libregexp.h +65 -0
- package/runtime/cpp/third_party/quickjs/libunicode-table.h +5206 -0
- package/runtime/cpp/third_party/quickjs/libunicode.c +2124 -0
- package/runtime/cpp/third_party/quickjs/libunicode.h +196 -0
- package/runtime/js/index.d.ts +7 -0
- package/runtime/js/index.js +60 -0
- package/runtime/native/LucentNative.podspec +28 -0
- package/runtime/native/android/CMakeLists.txt +51 -0
- package/runtime/native/android/build.gradle +22 -0
- package/runtime/native/android/include/lucentnative.h +16 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentPackage.java +25 -0
- package/runtime/native/android/src/main/java/dev/lucent/NativeProxy.java +132 -0
- package/runtime/native/ios/LucentRegistration.mm +23 -0
- package/runtime/native/package.json +6 -0
- package/runtime/native/react-native.config.js +19 -0
- package/runtime/test/jsi/harness.cpp +181 -0
- package/schemas/build.schema.json +152 -0
- package/schemas/check.schema.json +74 -0
- package/schemas/doctor.schema.json +40 -0
- package/schemas/lucent.schema.json +43 -0
- package/schemas/sdk-prefetch.schema.json +37 -0
- package/schemas/sdk-search.schema.json +54 -0
- package/ts-plugin/index.d.ts +4 -0
- package/ts-plugin/index.js +99 -0
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
#include "regexp.h"
|
|
2
|
+
|
|
3
|
+
#include <cmath>
|
|
4
|
+
#include <cstdlib>
|
|
5
|
+
#include <cstring>
|
|
6
|
+
#include <mutex>
|
|
7
|
+
#include <string>
|
|
8
|
+
#include <unordered_map>
|
|
9
|
+
#include <vector>
|
|
10
|
+
|
|
11
|
+
#include "jserror.h"
|
|
12
|
+
#include "number.h"
|
|
13
|
+
|
|
14
|
+
extern "C" {
|
|
15
|
+
#include "../third_party/quickjs/libregexp.h"
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// --- libregexp host callbacks --------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
extern "C" int lre_check_stack_overflow(void*, size_t) { return 0; }
|
|
21
|
+
extern "C" int lre_check_timeout(void*) { return 0; }
|
|
22
|
+
extern "C" void* lre_realloc(void*, void* ptr, size_t size) {
|
|
23
|
+
if (size == 0) {
|
|
24
|
+
std::free(ptr);
|
|
25
|
+
return nullptr;
|
|
26
|
+
}
|
|
27
|
+
return std::realloc(ptr, size);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
namespace lucent {
|
|
31
|
+
|
|
32
|
+
struct CompiledRegExp {
|
|
33
|
+
uint8_t* bytecode = nullptr;
|
|
34
|
+
int flags = 0;
|
|
35
|
+
int captureCount = 0; // including the whole match
|
|
36
|
+
int allocCount = 0;
|
|
37
|
+
std::vector<std::string> groupNames; // per capture; empty when unnamed
|
|
38
|
+
~CompiledRegExp() { std::free(bytecode); }
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
namespace {
|
|
42
|
+
|
|
43
|
+
int parseFlags(const String& flags) {
|
|
44
|
+
int out = 0;
|
|
45
|
+
for (size_t i = 0; i < flags.length(); i++) {
|
|
46
|
+
int f = 0;
|
|
47
|
+
switch (flags.unit(i)) {
|
|
48
|
+
case 'd':
|
|
49
|
+
f = LRE_FLAG_INDICES;
|
|
50
|
+
break;
|
|
51
|
+
case 'g':
|
|
52
|
+
f = LRE_FLAG_GLOBAL;
|
|
53
|
+
break;
|
|
54
|
+
case 'i':
|
|
55
|
+
f = LRE_FLAG_IGNORECASE;
|
|
56
|
+
break;
|
|
57
|
+
case 'm':
|
|
58
|
+
f = LRE_FLAG_MULTILINE;
|
|
59
|
+
break;
|
|
60
|
+
case 's':
|
|
61
|
+
f = LRE_FLAG_DOTALL;
|
|
62
|
+
break;
|
|
63
|
+
case 'u':
|
|
64
|
+
f = LRE_FLAG_UNICODE;
|
|
65
|
+
break;
|
|
66
|
+
case 'v':
|
|
67
|
+
f = LRE_FLAG_UNICODE_SETS;
|
|
68
|
+
break;
|
|
69
|
+
case 'y':
|
|
70
|
+
f = LRE_FLAG_STICKY;
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
if (f == 0 || (out & f)) throwError(String::fromLatin1("SyntaxError"), String::fromUtf8("Invalid regular expression flags '" + flags.toUtf8() + "'"));
|
|
74
|
+
out |= f;
|
|
75
|
+
}
|
|
76
|
+
if ((out & LRE_FLAG_UNICODE) && (out & LRE_FLAG_UNICODE_SETS)) throwError(String::fromLatin1("SyntaxError"), String::fromUtf8("Invalid regular expression flags '" + flags.toUtf8() + "'"));
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
std::shared_ptr<CompiledRegExp> compile(const String& source, const String& flags) {
|
|
81
|
+
static std::mutex mutex;
|
|
82
|
+
static std::unordered_map<std::string, std::shared_ptr<CompiledRegExp>> cache;
|
|
83
|
+
std::string pattern = source.toUtf8();
|
|
84
|
+
std::string key = flags.toUtf8() + "/" + pattern;
|
|
85
|
+
std::lock_guard<std::mutex> lock(mutex);
|
|
86
|
+
if (auto it = cache.find(key); it != cache.end()) return it->second;
|
|
87
|
+
int reFlags = parseFlags(flags);
|
|
88
|
+
char error[128];
|
|
89
|
+
int len = 0;
|
|
90
|
+
uint8_t* bc = lre_compile(&len, error, sizeof error, pattern.c_str(), pattern.size(), reFlags, nullptr);
|
|
91
|
+
if (!bc) throwError(String::fromLatin1("SyntaxError"), String::fromUtf8("Invalid regular expression: /" + pattern + "/" + flags.toUtf8() + ": " + error));
|
|
92
|
+
auto re = std::make_shared<CompiledRegExp>();
|
|
93
|
+
re->bytecode = bc;
|
|
94
|
+
re->flags = lre_get_flags(bc);
|
|
95
|
+
re->captureCount = lre_get_capture_count(bc);
|
|
96
|
+
re->allocCount = lre_get_alloc_count(bc);
|
|
97
|
+
re->groupNames.resize(static_cast<size_t>(re->captureCount));
|
|
98
|
+
if (const char* names = lre_get_groupnames(bc)) {
|
|
99
|
+
for (int i = 1; i < re->captureCount; i++) {
|
|
100
|
+
re->groupNames[static_cast<size_t>(i)] = names;
|
|
101
|
+
names += std::strlen(names) + LRE_GROUP_NAME_TRAILER_LEN;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
cache.emplace(std::move(key), re);
|
|
105
|
+
return re;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
double toLength(double v) {
|
|
109
|
+
if (std::isnan(v) || v <= 0) return 0;
|
|
110
|
+
return std::min(std::floor(v), 9007199254740991.0);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/// AdvanceStringIndex: past a whole code point in Unicode mode.
|
|
114
|
+
double advance(const String& s, double index, bool unicode) {
|
|
115
|
+
if (!unicode || index + 1 >= static_cast<double>(s.length())) return index + 1;
|
|
116
|
+
char16_t c = s.unit(static_cast<size_t>(index));
|
|
117
|
+
char16_t d = s.unit(static_cast<size_t>(index) + 1);
|
|
118
|
+
return (c >= 0xD800 && c <= 0xDBFF && d >= 0xDC00 && d <= 0xDFFF) ? index + 2 : index + 1;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
String slice(const String& s, size_t from, size_t to) { return s.substring(static_cast<double>(from), static_cast<double>(to)); }
|
|
122
|
+
|
|
123
|
+
/// GetSubstitution: `$$`, `$&`, `` $` ``, `$'`, `$n`, `$nn`, `$<name>`.
|
|
124
|
+
String substitute(const String& matched, const String& str, size_t position, const Array<Opt<String>>& captures, const Opt<Dict<String>>& named, const String& tmpl) {
|
|
125
|
+
std::u16string out;
|
|
126
|
+
size_t m = captures.size();
|
|
127
|
+
size_t n = tmpl.length();
|
|
128
|
+
auto append = [&out](const String& x) {
|
|
129
|
+
for (size_t k = 0; k < x.length(); k++) out.push_back(x.unit(k));
|
|
130
|
+
};
|
|
131
|
+
for (size_t i = 0; i < n; i++) {
|
|
132
|
+
char16_t c = tmpl.unit(i);
|
|
133
|
+
if (c != '$' || i + 1 >= n) {
|
|
134
|
+
out.push_back(c);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
char16_t d = tmpl.unit(i + 1);
|
|
138
|
+
if (d == '$') {
|
|
139
|
+
out.push_back('$');
|
|
140
|
+
i++;
|
|
141
|
+
} else if (d == '&') {
|
|
142
|
+
append(matched);
|
|
143
|
+
i++;
|
|
144
|
+
} else if (d == '`') {
|
|
145
|
+
append(slice(str, 0, position));
|
|
146
|
+
i++;
|
|
147
|
+
} else if (d == '\'') {
|
|
148
|
+
append(slice(str, std::min(position + matched.length(), str.length()), str.length()));
|
|
149
|
+
i++;
|
|
150
|
+
} else if (d >= '0' && d <= '9') {
|
|
151
|
+
size_t one = static_cast<size_t>(d - '0');
|
|
152
|
+
size_t two = (i + 2 < n && tmpl.unit(i + 2) >= '0' && tmpl.unit(i + 2) <= '9') ? one * 10 + static_cast<size_t>(tmpl.unit(i + 2) - '0') : 0;
|
|
153
|
+
if (two >= 1 && two <= m) {
|
|
154
|
+
if (auto v = captures.at(two - 1); v.has()) append(v.get());
|
|
155
|
+
i += 2;
|
|
156
|
+
} else if (one >= 1 && one <= m) {
|
|
157
|
+
if (auto v = captures.at(one - 1); v.has()) append(v.get());
|
|
158
|
+
i++;
|
|
159
|
+
} else {
|
|
160
|
+
out.push_back('$');
|
|
161
|
+
}
|
|
162
|
+
} else if (d == '<' && named.has()) {
|
|
163
|
+
size_t close = i + 2;
|
|
164
|
+
while (close < n && tmpl.unit(close) != '>') close++;
|
|
165
|
+
if (close >= n) {
|
|
166
|
+
out.push_back('$');
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
String name = slice(tmpl, i + 2, close);
|
|
170
|
+
if (auto v = named.get().get(name); v.has()) append(v.get());
|
|
171
|
+
i = close;
|
|
172
|
+
} else {
|
|
173
|
+
out.push_back('$');
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return String::fromUtf16(out);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
std::vector<RegExpMatch> collect(const String& s, const RegExp& re) {
|
|
180
|
+
std::vector<RegExpMatch> out;
|
|
181
|
+
bool global = re->global();
|
|
182
|
+
if (global) re->lastIndex = 0;
|
|
183
|
+
for (auto m = re->exec(s); m.has(); m = re->exec(s)) {
|
|
184
|
+
out.push_back(m.get());
|
|
185
|
+
if (!global) break;
|
|
186
|
+
if (m.get()->items.at(0).get().empty()) re->lastIndex = advance(s, toLength(re->lastIndex), re->unicode() || re->unicodeSets());
|
|
187
|
+
}
|
|
188
|
+
return out;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
String replaceMatches(const String& s, const std::vector<RegExpMatch>& matches, const std::function<String(const RegExpMatch&, size_t)>& replacement) {
|
|
192
|
+
std::u16string out;
|
|
193
|
+
size_t next = 0;
|
|
194
|
+
for (const auto& m : matches) {
|
|
195
|
+
String matched = m->items.at(0).get();
|
|
196
|
+
size_t position = static_cast<size_t>(std::max(0.0, std::min(m->index.get(), static_cast<double>(s.length()))));
|
|
197
|
+
String rep = replacement(m, position);
|
|
198
|
+
if (position >= next) {
|
|
199
|
+
for (size_t k = next; k < position; k++) out.push_back(s.unit(k));
|
|
200
|
+
for (size_t k = 0; k < rep.length(); k++) out.push_back(rep.unit(k));
|
|
201
|
+
next = position + matched.length();
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
for (size_t k = next; k < s.length(); k++) out.push_back(s.unit(k));
|
|
205
|
+
return String::fromUtf16(out);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
Array<Opt<String>> capturesOf(const RegExpMatch& m) { return m->items.slice(1); }
|
|
209
|
+
|
|
210
|
+
class MatchAllIter final : public IterObject<RegExpMatch> {
|
|
211
|
+
public:
|
|
212
|
+
MatchAllIter(RegExp re, String s) : re_(std::move(re)), s_(std::move(s)) {}
|
|
213
|
+
std::optional<RegExpMatch> next() override {
|
|
214
|
+
if (done_) return std::nullopt;
|
|
215
|
+
auto m = re_->exec(s_);
|
|
216
|
+
if (!m.has()) {
|
|
217
|
+
done_ = true;
|
|
218
|
+
return std::nullopt;
|
|
219
|
+
}
|
|
220
|
+
if (m.get()->items.at(0).get().empty()) re_->lastIndex = advance(s_, toLength(re_->lastIndex), re_->unicode() || re_->unicodeSets());
|
|
221
|
+
return m.get();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
private:
|
|
225
|
+
RegExp re_;
|
|
226
|
+
String s_;
|
|
227
|
+
bool done_ = false;
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
} // namespace
|
|
231
|
+
|
|
232
|
+
RegExpObject::RegExpObject(const String& source, const String& flags) : source_(source), flags_(flags), re_(compile(source, flags)) {}
|
|
233
|
+
|
|
234
|
+
bool RegExpObject::global() const { return re_->flags & LRE_FLAG_GLOBAL; }
|
|
235
|
+
bool RegExpObject::ignoreCase() const { return re_->flags & LRE_FLAG_IGNORECASE; }
|
|
236
|
+
bool RegExpObject::multiline() const { return re_->flags & LRE_FLAG_MULTILINE; }
|
|
237
|
+
bool RegExpObject::dotAll() const { return re_->flags & LRE_FLAG_DOTALL; }
|
|
238
|
+
bool RegExpObject::unicode() const { return re_->flags & LRE_FLAG_UNICODE; }
|
|
239
|
+
bool RegExpObject::unicodeSets() const { return re_->flags & LRE_FLAG_UNICODE_SETS; }
|
|
240
|
+
bool RegExpObject::sticky() const { return re_->flags & LRE_FLAG_STICKY; }
|
|
241
|
+
bool RegExpObject::hasIndices() const { return re_->flags & LRE_FLAG_INDICES; }
|
|
242
|
+
int RegExpObject::groupCount() const { return re_->captureCount - 1; }
|
|
243
|
+
|
|
244
|
+
/// EscapeRegExpPattern: `/` and line terminators escaped, `(?:)` when empty.
|
|
245
|
+
String RegExpObject::source() const {
|
|
246
|
+
if (source_.empty()) return String::fromLatin1("(?:)");
|
|
247
|
+
std::u16string out;
|
|
248
|
+
bool inClass = false;
|
|
249
|
+
for (size_t i = 0; i < source_.length(); i++) {
|
|
250
|
+
char16_t c = source_.unit(i);
|
|
251
|
+
if (c == '\\' && i + 1 < source_.length()) {
|
|
252
|
+
out.push_back(c);
|
|
253
|
+
out.push_back(source_.unit(++i));
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
if (c == '[') inClass = true;
|
|
257
|
+
else if (c == ']') inClass = false;
|
|
258
|
+
if (c == '/' && !inClass) out.append(u"\\/");
|
|
259
|
+
else if (c == '\n') out.append(u"\\n");
|
|
260
|
+
else if (c == '\r') out.append(u"\\r");
|
|
261
|
+
else if (c == 0x2028) out.append(u"\\u2028");
|
|
262
|
+
else if (c == 0x2029) out.append(u"\\u2029");
|
|
263
|
+
else out.push_back(c);
|
|
264
|
+
}
|
|
265
|
+
return String::fromUtf16(out);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/// Flags in the canonical order JavaScript reports them.
|
|
269
|
+
String RegExpObject::canonicalFlags() const {
|
|
270
|
+
std::string out;
|
|
271
|
+
if (hasIndices()) out += 'd';
|
|
272
|
+
if (global()) out += 'g';
|
|
273
|
+
if (ignoreCase()) out += 'i';
|
|
274
|
+
if (multiline()) out += 'm';
|
|
275
|
+
if (dotAll()) out += 's';
|
|
276
|
+
if (unicode()) out += 'u';
|
|
277
|
+
if (unicodeSets()) out += 'v';
|
|
278
|
+
if (sticky()) out += 'y';
|
|
279
|
+
return String::fromLatin1(out);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
String RegExpObject::toString() const { return String::fromLatin1("/") + source() + String::fromLatin1("/") + canonicalFlags(); }
|
|
283
|
+
|
|
284
|
+
Opt<RegExpMatch> RegExpObject::exec(const String& s) {
|
|
285
|
+
bool gy = global() || sticky();
|
|
286
|
+
double li = gy ? toLength(lastIndex) : 0;
|
|
287
|
+
size_t len = s.length();
|
|
288
|
+
if (li > static_cast<double>(len)) {
|
|
289
|
+
if (gy) lastIndex = 0;
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
static const uint8_t empty[2] = {0, 0};
|
|
293
|
+
const uint8_t* buf;
|
|
294
|
+
int type;
|
|
295
|
+
if (s.isOneByte()) {
|
|
296
|
+
buf = len ? reinterpret_cast<const uint8_t*>(s.latin1().data()) : empty;
|
|
297
|
+
type = 0;
|
|
298
|
+
} else {
|
|
299
|
+
buf = reinterpret_cast<const uint8_t*>(s.utf16().data());
|
|
300
|
+
type = 1;
|
|
301
|
+
}
|
|
302
|
+
std::vector<uint8_t*> capture(static_cast<size_t>(std::max(re_->allocCount, re_->captureCount * 2)));
|
|
303
|
+
int r = lre_exec(capture.data(), re_->bytecode, buf, static_cast<int>(li), static_cast<int>(len), type, nullptr);
|
|
304
|
+
if (r < 0) throwRangeError("Regular expression matching ran out of memory");
|
|
305
|
+
if (r == 0) {
|
|
306
|
+
if (gy) lastIndex = 0;
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
309
|
+
auto offset = [&](const uint8_t* p) { return static_cast<size_t>(p - buf) >> type; };
|
|
310
|
+
auto m = std::make_shared<RegExpMatchObject>();
|
|
311
|
+
for (int i = 0; i < re_->captureCount; i++) {
|
|
312
|
+
const uint8_t* a = capture[static_cast<size_t>(2 * i)];
|
|
313
|
+
const uint8_t* b = capture[static_cast<size_t>(2 * i + 1)];
|
|
314
|
+
if (a && b) m->items.push(Opt<String>(slice(s, offset(a), offset(b))));
|
|
315
|
+
else m->items.push(Opt<String>(undefined));
|
|
316
|
+
const std::string& name = re_->groupNames[static_cast<size_t>(i)];
|
|
317
|
+
if (i > 0 && !name.empty()) {
|
|
318
|
+
if (!m->groups.has()) m->groups = Dict<String>();
|
|
319
|
+
// Duplicate names in alternatives: the participating group wins.
|
|
320
|
+
if (a && b) m->groups.get().set(String::fromUtf8(name), slice(s, offset(a), offset(b)));
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
if (re_->groupNames.size() > 1 && !m->groups.has()) {
|
|
324
|
+
for (const auto& n : re_->groupNames) {
|
|
325
|
+
if (!n.empty()) {
|
|
326
|
+
m->groups = Dict<String>();
|
|
327
|
+
break;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
m->index = static_cast<double>(offset(capture[0]));
|
|
332
|
+
m->input = s;
|
|
333
|
+
if (gy) lastIndex = static_cast<double>(offset(capture[1]));
|
|
334
|
+
return m;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
RegExp makeRegExp(const String& source, Opt<String> flags) {
|
|
338
|
+
return std::make_shared<RegExpObject>(source, flags.has() ? flags.get() : String());
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
RegExp makeRegExp(const RegExp& re, Opt<String> flags) {
|
|
342
|
+
return std::make_shared<RegExpObject>(re->source(), flags.has() ? flags.get() : re->flags());
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
Opt<RegExpMatch> stringMatch(const String& s, const RegExp& re) {
|
|
346
|
+
if (!re->global()) return re->exec(s);
|
|
347
|
+
auto matches = collect(s, re);
|
|
348
|
+
if (matches.empty()) return null;
|
|
349
|
+
auto out = std::make_shared<RegExpMatchObject>();
|
|
350
|
+
for (const auto& m : matches) out->items.push(m->items.at(0));
|
|
351
|
+
return RegExpMatch(out);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
Iter<RegExpMatch> stringMatchAll(const String& s, const RegExp& re) {
|
|
355
|
+
if (!re->global()) throwTypeError("String.prototype.matchAll needs a global RegExp (the g flag)");
|
|
356
|
+
auto copy = makeRegExp(re);
|
|
357
|
+
copy->lastIndex = toLength(re->lastIndex);
|
|
358
|
+
return std::make_shared<MatchAllIter>(copy, s);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
double stringSearch(const String& s, const RegExp& re) {
|
|
362
|
+
double previous = re->lastIndex;
|
|
363
|
+
re->lastIndex = 0;
|
|
364
|
+
auto m = re->exec(s);
|
|
365
|
+
re->lastIndex = previous;
|
|
366
|
+
return m.has() ? m.get()->index.get() : -1;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
String stringReplace(const String& s, const RegExp& re, const String& replacement) {
|
|
370
|
+
auto matches = collect(s, re);
|
|
371
|
+
return replaceMatches(s, matches, [&](const RegExpMatch& m, size_t position) { return substitute(m->items.at(0).get(), s, position, capturesOf(m), m->groups, replacement); });
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
String stringReplace(const String& s, const RegExp& re, const Replacer& replacer) {
|
|
375
|
+
auto matches = collect(s, re);
|
|
376
|
+
return replaceMatches(s, matches, [&](const RegExpMatch& m, size_t position) {
|
|
377
|
+
return replacer(ReplaceCall{m->items.at(0).get(), capturesOf(m), static_cast<double>(position), s, m->groups});
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
String stringReplaceAll(const String& s, const RegExp& re, const String& replacement) {
|
|
382
|
+
if (!re->global()) throwTypeError("String.prototype.replaceAll needs a global RegExp (the g flag)");
|
|
383
|
+
return stringReplace(s, re, replacement);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
String stringReplaceAll(const String& s, const RegExp& re, const Replacer& replacer) {
|
|
387
|
+
if (!re->global()) throwTypeError("String.prototype.replaceAll needs a global RegExp (the g flag)");
|
|
388
|
+
return stringReplace(s, re, replacer);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
Array<String> stringSplit(const String& s, const RegExp& re, Opt<double> limit) {
|
|
392
|
+
String flags = re->flags();
|
|
393
|
+
bool unicode = re->unicode() || re->unicodeSets();
|
|
394
|
+
bool hasY = false;
|
|
395
|
+
for (size_t i = 0; i < flags.length(); i++) hasY = hasY || flags.unit(i) == 'y';
|
|
396
|
+
RegExp splitter = makeRegExp(re->source(), hasY ? flags : flags + String::fromLatin1("y"));
|
|
397
|
+
Array<String> out;
|
|
398
|
+
double lim = limit.has() ? static_cast<double>(toUint32(limit.get())) : 4294967295.0;
|
|
399
|
+
if (lim == 0) return out;
|
|
400
|
+
size_t size = s.length();
|
|
401
|
+
if (size == 0) {
|
|
402
|
+
if (!splitter->exec(s).has()) out.push(s);
|
|
403
|
+
return out;
|
|
404
|
+
}
|
|
405
|
+
size_t p = 0, q = 0;
|
|
406
|
+
while (q < size) {
|
|
407
|
+
splitter->lastIndex = static_cast<double>(q);
|
|
408
|
+
auto z = splitter->exec(s);
|
|
409
|
+
if (!z.has()) {
|
|
410
|
+
q = static_cast<size_t>(advance(s, static_cast<double>(q), unicode));
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
size_t e = std::min(static_cast<size_t>(toLength(splitter->lastIndex)), size);
|
|
414
|
+
if (e == p) {
|
|
415
|
+
q = static_cast<size_t>(advance(s, static_cast<double>(q), unicode));
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
out.push(slice(s, p, q));
|
|
419
|
+
if (static_cast<double>(out.size()) == lim) return out;
|
|
420
|
+
p = e;
|
|
421
|
+
const auto& items = z.get()->items;
|
|
422
|
+
for (size_t i = 1; i < items.size(); i++) {
|
|
423
|
+
auto v = items.at(i);
|
|
424
|
+
out.push(v.has() ? v.get() : String());
|
|
425
|
+
if (static_cast<double>(out.size()) == lim) return out;
|
|
426
|
+
}
|
|
427
|
+
q = p;
|
|
428
|
+
}
|
|
429
|
+
out.push(slice(s, p, size));
|
|
430
|
+
return out;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
String captureOrThrow(const Opt<String>& capture, int group) {
|
|
434
|
+
if (!capture.has()) {
|
|
435
|
+
throwTypeError(("capture group " + std::to_string(group) + " did not participate in the match; declare the callback parameter as string | undefined").c_str());
|
|
436
|
+
}
|
|
437
|
+
return capture.get();
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
} // namespace lucent
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// Lucent runtime — RegExp.
|
|
2
|
+
//
|
|
3
|
+
// Patterns compile once (cached by source and flags) with QuickJS's
|
|
4
|
+
// libregexp (third_party/quickjs), which implements ECMAScript regular
|
|
5
|
+
// expressions over Latin-1 and UTF-16 buffers, so matching needs no
|
|
6
|
+
// conversion. The algorithms around it (lastIndex, match, replace, split…)
|
|
7
|
+
// follow the ECMAScript specification.
|
|
8
|
+
#pragma once
|
|
9
|
+
|
|
10
|
+
#include <functional>
|
|
11
|
+
#include <memory>
|
|
12
|
+
|
|
13
|
+
#include "array.h"
|
|
14
|
+
#include "core.h"
|
|
15
|
+
#include "generator.h"
|
|
16
|
+
#include "jsstring.h"
|
|
17
|
+
#include "map.h"
|
|
18
|
+
|
|
19
|
+
namespace lucent {
|
|
20
|
+
|
|
21
|
+
struct CompiledRegExp;
|
|
22
|
+
|
|
23
|
+
/// The result of exec / match: the matched strings (undefined for groups
|
|
24
|
+
/// that did not participate), and for exec-style results index, input and
|
|
25
|
+
/// named groups.
|
|
26
|
+
struct RegExpMatchObject : Object {
|
|
27
|
+
Array<Opt<String>> items;
|
|
28
|
+
Opt<double> index;
|
|
29
|
+
Opt<String> input;
|
|
30
|
+
/// Named groups that participated (JavaScript's `groups`), or undefined.
|
|
31
|
+
Opt<Dict<String>> groups;
|
|
32
|
+
};
|
|
33
|
+
using RegExpMatch = Ref<RegExpMatchObject>;
|
|
34
|
+
|
|
35
|
+
class RegExpObject : public Object {
|
|
36
|
+
public:
|
|
37
|
+
/// Throws SyntaxError for an invalid pattern or flags.
|
|
38
|
+
RegExpObject(const String& source, const String& flags);
|
|
39
|
+
|
|
40
|
+
String source() const;
|
|
41
|
+
const String& flags() const { return flags_; }
|
|
42
|
+
bool global() const;
|
|
43
|
+
bool ignoreCase() const;
|
|
44
|
+
bool multiline() const;
|
|
45
|
+
bool dotAll() const;
|
|
46
|
+
bool unicode() const;
|
|
47
|
+
bool unicodeSets() const;
|
|
48
|
+
bool sticky() const;
|
|
49
|
+
bool hasIndices() const;
|
|
50
|
+
double lastIndex = 0;
|
|
51
|
+
|
|
52
|
+
Opt<RegExpMatch> exec(const String& s);
|
|
53
|
+
bool test(const String& s) { return exec(s).has(); }
|
|
54
|
+
String toString() const;
|
|
55
|
+
/// `re.flags`: the flags in canonical order ("dgimsuvy").
|
|
56
|
+
String canonicalFlags() const;
|
|
57
|
+
|
|
58
|
+
/// Number of capturing groups, not counting the whole match.
|
|
59
|
+
int groupCount() const;
|
|
60
|
+
|
|
61
|
+
// For the String methods below.
|
|
62
|
+
const std::shared_ptr<CompiledRegExp>& compiled() const { return re_; }
|
|
63
|
+
|
|
64
|
+
private:
|
|
65
|
+
String source_;
|
|
66
|
+
String flags_;
|
|
67
|
+
std::shared_ptr<CompiledRegExp> re_;
|
|
68
|
+
};
|
|
69
|
+
using RegExp = Ref<RegExpObject>;
|
|
70
|
+
|
|
71
|
+
RegExp makeRegExp(const String& source, Opt<String> flags = undefined);
|
|
72
|
+
/// `new RegExp(re, flags?)`: a copy with the same source.
|
|
73
|
+
RegExp makeRegExp(const RegExp& re, Opt<String> flags = undefined);
|
|
74
|
+
|
|
75
|
+
/// The arguments JavaScript passes a replacement function.
|
|
76
|
+
struct ReplaceCall {
|
|
77
|
+
String match;
|
|
78
|
+
Array<Opt<String>> captures;
|
|
79
|
+
double position = 0;
|
|
80
|
+
String input;
|
|
81
|
+
Opt<Dict<String>> groups;
|
|
82
|
+
};
|
|
83
|
+
using Replacer = std::function<String(const ReplaceCall&)>;
|
|
84
|
+
|
|
85
|
+
Opt<RegExpMatch> stringMatch(const String& s, const RegExp& re);
|
|
86
|
+
Iter<RegExpMatch> stringMatchAll(const String& s, const RegExp& re);
|
|
87
|
+
double stringSearch(const String& s, const RegExp& re);
|
|
88
|
+
String stringReplace(const String& s, const RegExp& re, const String& replacement);
|
|
89
|
+
String stringReplace(const String& s, const RegExp& re, const Replacer& replacer);
|
|
90
|
+
/// replaceAll requires the g flag (TypeError otherwise).
|
|
91
|
+
String stringReplaceAll(const String& s, const RegExp& re, const String& replacement);
|
|
92
|
+
String stringReplaceAll(const String& s, const RegExp& re, const Replacer& replacer);
|
|
93
|
+
Array<String> stringSplit(const String& s, const RegExp& re, Opt<double> limit = undefined);
|
|
94
|
+
|
|
95
|
+
/// A capture passed to a replacement callback parameter typed `string`.
|
|
96
|
+
String captureOrThrow(const Opt<String>& capture, int group);
|
|
97
|
+
|
|
98
|
+
/// `match[i]`: undefined past the end or for a group that did not participate.
|
|
99
|
+
inline Opt<String> matchItem(const RegExpMatch& m, double i) {
|
|
100
|
+
auto v = m->items.get(i);
|
|
101
|
+
return v.has() ? v.get() : Opt<String>(undefined);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
inline String toJsString(const RegExp& re) { return re->toString(); }
|
|
105
|
+
inline String toJsString(const RegExpMatch& m) { return m->items.join(); }
|
|
106
|
+
|
|
107
|
+
} // namespace lucent
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
#include "scheduler.h"
|
|
2
|
+
|
|
3
|
+
#include <cstdio>
|
|
4
|
+
#include <exception>
|
|
5
|
+
#include <string>
|
|
6
|
+
|
|
7
|
+
#if defined(__ANDROID__)
|
|
8
|
+
#include <android/log.h>
|
|
9
|
+
#elif defined(__APPLE__)
|
|
10
|
+
#include <os/log.h>
|
|
11
|
+
#endif
|
|
12
|
+
|
|
13
|
+
namespace lucent {
|
|
14
|
+
|
|
15
|
+
namespace {
|
|
16
|
+
/// Where each platform shows an app's errors: logcat, the unified log, and
|
|
17
|
+
/// stderr (host runs; Android and release iOS apps drop it).
|
|
18
|
+
void writeError(const std::string& message) {
|
|
19
|
+
#if defined(__ANDROID__)
|
|
20
|
+
__android_log_write(ANDROID_LOG_ERROR, "Lucent", message.c_str());
|
|
21
|
+
#elif defined(__APPLE__)
|
|
22
|
+
os_log_error(OS_LOG_DEFAULT, "%{public}s", message.c_str());
|
|
23
|
+
#endif
|
|
24
|
+
std::fprintf(stderr, "%s\n", message.c_str());
|
|
25
|
+
}
|
|
26
|
+
} // namespace
|
|
27
|
+
|
|
28
|
+
void logError(const char* message) { writeError(message); }
|
|
29
|
+
|
|
30
|
+
void reportUncaught(std::exception_ptr e, const char* where) {
|
|
31
|
+
try {
|
|
32
|
+
std::rethrow_exception(e);
|
|
33
|
+
} catch (const std::exception& x) {
|
|
34
|
+
writeError(std::string("[lucent] uncaught exception in ") + where + ": " + x.what());
|
|
35
|
+
} catch (...) {
|
|
36
|
+
writeError(std::string("[lucent] uncaught exception in ") + where);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
namespace {
|
|
41
|
+
void runGuarded(const Scheduler::Job& job) {
|
|
42
|
+
try {
|
|
43
|
+
job();
|
|
44
|
+
} catch (...) {
|
|
45
|
+
reportUncaught(std::current_exception(), "job");
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
} // namespace
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
Scheduler::Scheduler() {
|
|
52
|
+
thread_ = std::thread([this] { run(); });
|
|
53
|
+
threadId_ = thread_.get_id();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
Scheduler::~Scheduler() {
|
|
57
|
+
{
|
|
58
|
+
std::lock_guard<std::mutex> g(queueMutex_);
|
|
59
|
+
stopping_ = true;
|
|
60
|
+
}
|
|
61
|
+
cv_.notify_all();
|
|
62
|
+
if (thread_.joinable()) thread_.join();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
void Scheduler::post(Job job) {
|
|
66
|
+
{
|
|
67
|
+
std::lock_guard<std::mutex> g(queueMutex_);
|
|
68
|
+
jobs_.push_back(std::move(job));
|
|
69
|
+
}
|
|
70
|
+
cv_.notify_one();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
void Scheduler::postDelayed(double ms, Job job) {
|
|
74
|
+
if (!(ms > 0)) ms = 0;
|
|
75
|
+
auto at = std::chrono::steady_clock::now() + std::chrono::microseconds(static_cast<int64_t>(ms * 1000));
|
|
76
|
+
{
|
|
77
|
+
std::lock_guard<std::mutex> g(queueMutex_);
|
|
78
|
+
timers_.push(Timer{at, timerSeq_++, std::move(job)});
|
|
79
|
+
}
|
|
80
|
+
cv_.notify_one();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
void Scheduler::enqueueMicrotask(Job job) {
|
|
84
|
+
microtasks_.push_back(std::move(job));
|
|
85
|
+
pendingMicrotasks_++;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
void Scheduler::drainMicrotasks() {
|
|
89
|
+
while (!microtasks_.empty()) {
|
|
90
|
+
Job job = std::move(microtasks_.front());
|
|
91
|
+
microtasks_.pop_front();
|
|
92
|
+
pendingMicrotasks_--;
|
|
93
|
+
runGuarded(job);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
size_t Scheduler::pendingWork() {
|
|
98
|
+
std::lock_guard<std::mutex> g(queueMutex_);
|
|
99
|
+
return jobs_.size() + timers_.size() + running_;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
bool Scheduler::waitIdle(double timeoutMs) {
|
|
103
|
+
std::unique_lock<std::mutex> g(queueMutex_);
|
|
104
|
+
return idleCv_.wait_for(g, std::chrono::microseconds(static_cast<int64_t>(timeoutMs * 1000)),
|
|
105
|
+
[this] { return jobs_.empty() && timers_.empty() && running_ == 0; });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
void Scheduler::run() {
|
|
109
|
+
std::unique_lock<std::mutex> g(queueMutex_);
|
|
110
|
+
for (;;) {
|
|
111
|
+
if (stopping_) return;
|
|
112
|
+
auto now = std::chrono::steady_clock::now();
|
|
113
|
+
while (!timers_.empty() && timers_.top().at <= now) {
|
|
114
|
+
jobs_.push_back(std::move(const_cast<Timer&>(timers_.top()).job));
|
|
115
|
+
timers_.pop();
|
|
116
|
+
}
|
|
117
|
+
if (!jobs_.empty()) {
|
|
118
|
+
Job job = std::move(jobs_.front());
|
|
119
|
+
jobs_.pop_front();
|
|
120
|
+
running_++;
|
|
121
|
+
g.unlock();
|
|
122
|
+
{
|
|
123
|
+
std::lock_guard<LucentLock> lucent(lock_);
|
|
124
|
+
runGuarded(job);
|
|
125
|
+
drainMicrotasks();
|
|
126
|
+
}
|
|
127
|
+
g.lock();
|
|
128
|
+
running_--;
|
|
129
|
+
if (jobs_.empty() && timers_.empty() && running_ == 0) idleCv_.notify_all();
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (timers_.empty()) {
|
|
133
|
+
cv_.wait(g);
|
|
134
|
+
} else {
|
|
135
|
+
// A copy: wait_until reads the deadline after unlocking, when a
|
|
136
|
+
// postDelayed may have reallocated the heap.
|
|
137
|
+
const auto deadline = timers_.top().at;
|
|
138
|
+
cv_.wait_until(g, deadline);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
void LucentScope::handOffMicrotasks() {
|
|
144
|
+
Scheduler& s = Scheduler::instance();
|
|
145
|
+
if (s.onLucentThread()) {
|
|
146
|
+
s.drainMicrotasks();
|
|
147
|
+
} else {
|
|
148
|
+
// Continuations run on the Lucent thread, never on the JS thread.
|
|
149
|
+
s.post([] {});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
} // namespace lucent
|