@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,236 @@
|
|
|
1
|
+
// Lucent runtime — Promise<T> and async functions (C++20 coroutines).
|
|
2
|
+
//
|
|
3
|
+
// A Lucent `async function` is a C++ coroutine returning Promise<T>. Like
|
|
4
|
+
// JavaScript, the body starts running immediately and every `await`
|
|
5
|
+
// suspends, resuming later from the microtask queue (never inline), so the
|
|
6
|
+
// interleaving matches JavaScript's.
|
|
7
|
+
//
|
|
8
|
+
// Coroutine parameters are always taken by value: a coroutine frame must not
|
|
9
|
+
// hold references to its caller's locals.
|
|
10
|
+
#pragma once
|
|
11
|
+
|
|
12
|
+
#include <coroutine>
|
|
13
|
+
#include <exception>
|
|
14
|
+
#include <functional>
|
|
15
|
+
#include <memory>
|
|
16
|
+
#include <optional>
|
|
17
|
+
#include <tuple>
|
|
18
|
+
#include <vector>
|
|
19
|
+
|
|
20
|
+
#include "array.h"
|
|
21
|
+
#include "core.h"
|
|
22
|
+
#include "jserror.h"
|
|
23
|
+
#include "scheduler.h"
|
|
24
|
+
|
|
25
|
+
namespace lucent {
|
|
26
|
+
|
|
27
|
+
template <class T>
|
|
28
|
+
class Promise;
|
|
29
|
+
|
|
30
|
+
namespace detail {
|
|
31
|
+
|
|
32
|
+
template <class T>
|
|
33
|
+
using Stored = std::conditional_t<std::is_void_v<T>, Undefined, T>;
|
|
34
|
+
|
|
35
|
+
template <class T>
|
|
36
|
+
struct PromiseState {
|
|
37
|
+
enum class Status : uint8_t { Pending, Fulfilled, Rejected };
|
|
38
|
+
Status status = Status::Pending;
|
|
39
|
+
std::optional<Stored<T>> value;
|
|
40
|
+
Error error;
|
|
41
|
+
std::vector<std::function<void()>> waiters;
|
|
42
|
+
|
|
43
|
+
void fulfill(Stored<T> v) {
|
|
44
|
+
if (status != Status::Pending) return;
|
|
45
|
+
value = std::move(v);
|
|
46
|
+
status = Status::Fulfilled;
|
|
47
|
+
flush();
|
|
48
|
+
}
|
|
49
|
+
void reject(Error e) {
|
|
50
|
+
if (status != Status::Pending) return;
|
|
51
|
+
error = std::move(e);
|
|
52
|
+
status = Status::Rejected;
|
|
53
|
+
flush();
|
|
54
|
+
}
|
|
55
|
+
/// Runs `f` as a microtask once settled. Callers hold the Lucent lock.
|
|
56
|
+
void onSettled(std::function<void()> f) {
|
|
57
|
+
if (status == Status::Pending) waiters.push_back(std::move(f));
|
|
58
|
+
else Scheduler::instance().enqueueMicrotask(std::move(f));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private:
|
|
62
|
+
void flush() {
|
|
63
|
+
auto ws = std::move(waiters);
|
|
64
|
+
waiters.clear();
|
|
65
|
+
for (auto& w : ws) Scheduler::instance().enqueueMicrotask(std::move(w));
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
template <class T>
|
|
70
|
+
struct PromiseTypeBase {
|
|
71
|
+
std::shared_ptr<PromiseState<T>> state = std::make_shared<PromiseState<T>>();
|
|
72
|
+
Promise<T> get_return_object();
|
|
73
|
+
std::suspend_never initial_suspend() noexcept { return {}; }
|
|
74
|
+
// The frame frees itself when the body finishes; the Promise handle only
|
|
75
|
+
// shares the settled state.
|
|
76
|
+
std::suspend_never final_suspend() noexcept { return {}; }
|
|
77
|
+
void unhandled_exception() { state->reject(currentError(std::current_exception())); }
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
template <class T>
|
|
81
|
+
struct PromiseType : PromiseTypeBase<T> {
|
|
82
|
+
void return_value(T v) { this->state->fulfill(std::move(v)); }
|
|
83
|
+
};
|
|
84
|
+
template <>
|
|
85
|
+
struct PromiseType<void> : PromiseTypeBase<void> {
|
|
86
|
+
void return_void() { this->state->fulfill(undefined); }
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
} // namespace detail
|
|
90
|
+
|
|
91
|
+
/// Promise<T>: a handle to a settled-or-pending value. Copies share state.
|
|
92
|
+
template <class T>
|
|
93
|
+
class Promise {
|
|
94
|
+
public:
|
|
95
|
+
using promise_type = detail::PromiseType<T>;
|
|
96
|
+
using State = detail::PromiseState<T>;
|
|
97
|
+
using value_type = T;
|
|
98
|
+
|
|
99
|
+
Promise() : s_(std::make_shared<State>()) {}
|
|
100
|
+
explicit Promise(std::shared_ptr<State> s) : s_(std::move(s)) {}
|
|
101
|
+
|
|
102
|
+
static Promise resolved(detail::Stored<T> v) {
|
|
103
|
+
Promise p;
|
|
104
|
+
p.s_->fulfill(std::move(v));
|
|
105
|
+
return p;
|
|
106
|
+
}
|
|
107
|
+
static Promise resolved()
|
|
108
|
+
requires std::is_void_v<T>
|
|
109
|
+
{
|
|
110
|
+
Promise p;
|
|
111
|
+
p.s_->fulfill(undefined);
|
|
112
|
+
return p;
|
|
113
|
+
}
|
|
114
|
+
static Promise rejected(Error e) {
|
|
115
|
+
Promise p;
|
|
116
|
+
p.s_->reject(std::move(e));
|
|
117
|
+
return p;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
void resolve(detail::Stored<T> v) const { s_->fulfill(std::move(v)); }
|
|
121
|
+
void reject(Error e) const { s_->reject(std::move(e)); }
|
|
122
|
+
bool settled() const { return s_->status != State::Status::Pending; }
|
|
123
|
+
bool fulfilled() const { return s_->status == State::Status::Fulfilled; }
|
|
124
|
+
const detail::Stored<T>& value() const { return *s_->value; }
|
|
125
|
+
const Error& error() const { return s_->error; }
|
|
126
|
+
void onSettled(std::function<void()> f) const { s_->onSettled(std::move(f)); }
|
|
127
|
+
|
|
128
|
+
// Awaitable: JavaScript never resumes an await synchronously.
|
|
129
|
+
bool await_ready() const noexcept { return false; }
|
|
130
|
+
void await_suspend(std::coroutine_handle<> h) const {
|
|
131
|
+
s_->onSettled([h]() mutable { h.resume(); });
|
|
132
|
+
}
|
|
133
|
+
T await_resume() const {
|
|
134
|
+
if (s_->status == State::Status::Rejected) throw Exception(s_->error);
|
|
135
|
+
if constexpr (!std::is_void_v<T>) return *s_->value;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const void* identity() const { return s_.get(); }
|
|
139
|
+
friend bool strictEquals(const Promise& a, const Promise& b) { return a.s_ == b.s_; }
|
|
140
|
+
friend String toJsString(const Promise&) { return String::fromLatin1("[object Promise]"); }
|
|
141
|
+
|
|
142
|
+
private:
|
|
143
|
+
std::shared_ptr<State> s_;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
template <class T>
|
|
147
|
+
Promise<T> detail::PromiseTypeBase<T>::get_return_object() {
|
|
148
|
+
return Promise<T>(state);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
template <class T>
|
|
152
|
+
struct IsPromise : std::false_type {};
|
|
153
|
+
template <class T>
|
|
154
|
+
struct IsPromise<Promise<T>> : std::true_type {};
|
|
155
|
+
|
|
156
|
+
/// `await x` where x is not a promise.
|
|
157
|
+
template <class T>
|
|
158
|
+
Promise<T> resolvedPromise(T v) {
|
|
159
|
+
return Promise<T>::resolved(std::move(v));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
namespace detail {
|
|
163
|
+
/// Promise.all's bookkeeping: `out` rejects with the first input to reject,
|
|
164
|
+
/// and fulfils with `finish(values)` in the reaction of the last input to
|
|
165
|
+
/// fulfil. Every input is observed at once, as in JavaScript.
|
|
166
|
+
template <class R, class Values, class Finish>
|
|
167
|
+
struct AllState {
|
|
168
|
+
Promise<R> out;
|
|
169
|
+
Values values;
|
|
170
|
+
size_t remaining;
|
|
171
|
+
Finish finish;
|
|
172
|
+
|
|
173
|
+
template <class T>
|
|
174
|
+
void watch(const Promise<T>& p, std::optional<Stored<T>>& slot, const std::shared_ptr<AllState>& self) {
|
|
175
|
+
p.onSettled([p, &slot, self] {
|
|
176
|
+
if (!p.fulfilled()) {
|
|
177
|
+
self->out.reject(p.error());
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
slot = p.value();
|
|
181
|
+
if (--self->remaining == 0) self->out.resolve(self->finish(self->values));
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
} // namespace detail
|
|
186
|
+
|
|
187
|
+
/// Promise.all over promises of one type.
|
|
188
|
+
template <class T>
|
|
189
|
+
Promise<Array<T>> promiseAll(Array<Promise<T>> promises) {
|
|
190
|
+
using Values = std::vector<std::optional<T>>;
|
|
191
|
+
auto finish = [](Values& vs) {
|
|
192
|
+
Array<T> r;
|
|
193
|
+
for (auto& v : vs) r.push(std::move(*v));
|
|
194
|
+
return r;
|
|
195
|
+
};
|
|
196
|
+
using State = detail::AllState<Array<T>, Values, decltype(finish)>;
|
|
197
|
+
auto st = std::make_shared<State>(State{{}, Values(promises.size()), promises.size(), finish});
|
|
198
|
+
if (st->remaining == 0) st->out.resolve(Array<T>{});
|
|
199
|
+
size_t i = 0;
|
|
200
|
+
for (const auto& p : promises.items()) st->watch(p, st->values[i++], st);
|
|
201
|
+
return st->out;
|
|
202
|
+
}
|
|
203
|
+
inline Promise<void> promiseAllVoid(Array<Promise<void>> promises) {
|
|
204
|
+
using Values = std::vector<std::optional<Undefined>>;
|
|
205
|
+
auto finish = [](Values&) { return undefined; };
|
|
206
|
+
using State = detail::AllState<void, Values, decltype(finish)>;
|
|
207
|
+
auto st = std::make_shared<State>(State{{}, Values(promises.size()), promises.size(), finish});
|
|
208
|
+
if (st->remaining == 0) st->out.resolve(undefined);
|
|
209
|
+
size_t i = 0;
|
|
210
|
+
for (const auto& p : promises.items()) st->watch(p, st->values[i++], st);
|
|
211
|
+
return st->out;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/// Promise.all over a tuple of promises of different types.
|
|
215
|
+
template <class... Ts>
|
|
216
|
+
Promise<std::tuple<detail::Stored<Ts>...>> promiseAllTuple(std::tuple<Promise<Ts>...> ps) {
|
|
217
|
+
using Values = std::tuple<std::optional<detail::Stored<Ts>>...>;
|
|
218
|
+
auto finish = [](Values& vs) {
|
|
219
|
+
return std::apply([](auto&... v) { return std::tuple<detail::Stored<Ts>...>{std::move(*v)...}; }, vs);
|
|
220
|
+
};
|
|
221
|
+
using State = detail::AllState<std::tuple<detail::Stored<Ts>...>, Values, decltype(finish)>;
|
|
222
|
+
auto st = std::make_shared<State>(State{{}, Values{}, sizeof...(Ts), finish});
|
|
223
|
+
if constexpr (sizeof...(Ts) == 0) {
|
|
224
|
+
st->out.resolve({});
|
|
225
|
+
} else {
|
|
226
|
+
[&]<size_t... I>(std::index_sequence<I...>) {
|
|
227
|
+
(st->watch(std::get<I>(ps), std::get<I>(st->values), st), ...);
|
|
228
|
+
}(std::index_sequence_for<Ts...>{});
|
|
229
|
+
}
|
|
230
|
+
return st->out;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/// `await delay(ms)` — resolves after `ms` milliseconds on the Lucent thread.
|
|
234
|
+
Promise<void> delay(double ms);
|
|
235
|
+
|
|
236
|
+
} // namespace lucent
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
#include <cstdio>
|
|
2
|
+
|
|
3
|
+
#include "async.h"
|
|
4
|
+
#include "bytes.h"
|
|
5
|
+
#include "generator.h"
|
|
6
|
+
#include "console.h"
|
|
7
|
+
|
|
8
|
+
#if defined(__ANDROID__)
|
|
9
|
+
#include <android/log.h>
|
|
10
|
+
#elif defined(__APPLE__)
|
|
11
|
+
#include <os/log.h>
|
|
12
|
+
#endif
|
|
13
|
+
|
|
14
|
+
namespace lucent {
|
|
15
|
+
|
|
16
|
+
Array<String> split(const String& s, const String& separator) { return split(s, separator, 4294967295.0); }
|
|
17
|
+
|
|
18
|
+
Array<String> split(const String& s, const String& separator, double limitValue) {
|
|
19
|
+
Array<String> out;
|
|
20
|
+
double lim = std::isnan(limitValue) ? 0 : std::trunc(limitValue);
|
|
21
|
+
size_t limit = lim <= 0 ? 0 : static_cast<size_t>(std::min(lim, 4294967295.0));
|
|
22
|
+
if (limit == 0) return out;
|
|
23
|
+
size_t n = s.length(), m = separator.length();
|
|
24
|
+
if (m == 0) {
|
|
25
|
+
for (size_t i = 0; i < n && out.size() < limit; i++) out.push(String::fromCodeUnit(s.unit(i)));
|
|
26
|
+
return out;
|
|
27
|
+
}
|
|
28
|
+
size_t pos = 0;
|
|
29
|
+
for (;;) {
|
|
30
|
+
size_t at = s.find(separator, pos);
|
|
31
|
+
if (at == std::string::npos) break;
|
|
32
|
+
out.push(s.sub(pos, at));
|
|
33
|
+
if (out.size() >= limit) return out;
|
|
34
|
+
pos = at + m;
|
|
35
|
+
}
|
|
36
|
+
out.push(s.sub(pos, n));
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
Array<String> splitCodePoints(const String& s) {
|
|
41
|
+
Array<String> out;
|
|
42
|
+
size_t n = s.length();
|
|
43
|
+
for (size_t i = 0; i < n; i++) {
|
|
44
|
+
char16_t c = s.unit(i);
|
|
45
|
+
if (c >= 0xD800 && c <= 0xDBFF && i + 1 < n && s.unit(i + 1) >= 0xDC00 && s.unit(i + 1) <= 0xDFFF) {
|
|
46
|
+
out.push(s.sub(i, i + 2));
|
|
47
|
+
i++;
|
|
48
|
+
} else {
|
|
49
|
+
out.push(String::fromCodeUnit(c));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
Bytes utf8Encode(const String& s) {
|
|
56
|
+
std::string u = s.toUtf8();
|
|
57
|
+
return Bytes::copy(reinterpret_cast<const uint8_t*>(u.data()), u.size());
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
String utf8Decode(const Bytes& b) {
|
|
61
|
+
return String::fromUtf8(std::string_view(reinterpret_cast<const char*>(b.data()), b.size()));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
Promise<void> delay(double ms) {
|
|
65
|
+
Promise<void> p;
|
|
66
|
+
Scheduler::instance().postDelayed(ms, [p] { p.resolve(undefined); });
|
|
67
|
+
return p;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
namespace {
|
|
71
|
+
ConsoleSink& sink() {
|
|
72
|
+
static ConsoleSink s;
|
|
73
|
+
return s;
|
|
74
|
+
}
|
|
75
|
+
} // namespace
|
|
76
|
+
|
|
77
|
+
void setConsoleSink(ConsoleSink s) { sink() = std::move(s); }
|
|
78
|
+
|
|
79
|
+
void consoleWrite(ConsoleLevel level, const String& message) {
|
|
80
|
+
if (sink()) {
|
|
81
|
+
sink()(level, message);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
std::string text = message.toUtf8();
|
|
85
|
+
#if defined(__ANDROID__)
|
|
86
|
+
int prio = level == ConsoleLevel::Error ? ANDROID_LOG_ERROR : level == ConsoleLevel::Warn ? ANDROID_LOG_WARN : ANDROID_LOG_INFO;
|
|
87
|
+
__android_log_print(prio, "Lucent", "%s", text.c_str());
|
|
88
|
+
#elif defined(__APPLE__)
|
|
89
|
+
os_log_type_t type = level == ConsoleLevel::Error ? OS_LOG_TYPE_ERROR : OS_LOG_TYPE_DEFAULT;
|
|
90
|
+
os_log_with_type(OS_LOG_DEFAULT, type, "[Lucent] %{public}s", text.c_str());
|
|
91
|
+
#else
|
|
92
|
+
std::fprintf(level == ConsoleLevel::Log ? stdout : stderr, "%s\n", text.c_str());
|
|
93
|
+
std::fflush(level == ConsoleLevel::Log ? stdout : stderr);
|
|
94
|
+
#endif
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
} // namespace lucent
|
|
98
|
+
|
|
99
|
+
#include "json.h"
|
|
100
|
+
|
|
101
|
+
namespace lucent {
|
|
102
|
+
|
|
103
|
+
void JsonWriter::quote(const String& s) {
|
|
104
|
+
static const char* hex = "0123456789abcdef";
|
|
105
|
+
out.push_back(u'"');
|
|
106
|
+
size_t n = s.length();
|
|
107
|
+
for (size_t i = 0; i < n; i++) {
|
|
108
|
+
char16_t c = s.unit(i);
|
|
109
|
+
switch (c) {
|
|
110
|
+
case u'"': out.append(u"\\\""); break;
|
|
111
|
+
case u'\\': out.append(u"\\\\"); break;
|
|
112
|
+
case u'\b': out.append(u"\\b"); break;
|
|
113
|
+
case u'\f': out.append(u"\\f"); break;
|
|
114
|
+
case u'\n': out.append(u"\\n"); break;
|
|
115
|
+
case u'\r': out.append(u"\\r"); break;
|
|
116
|
+
case u'\t': out.append(u"\\t"); break;
|
|
117
|
+
default: {
|
|
118
|
+
bool lone = false;
|
|
119
|
+
if (c >= 0xD800 && c <= 0xDBFF) {
|
|
120
|
+
lone = !(i + 1 < n && s.unit(i + 1) >= 0xDC00 && s.unit(i + 1) <= 0xDFFF);
|
|
121
|
+
if (!lone) {
|
|
122
|
+
out.push_back(c);
|
|
123
|
+
out.push_back(s.unit(++i));
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
} else if (c >= 0xDC00 && c <= 0xDFFF) {
|
|
127
|
+
lone = true;
|
|
128
|
+
}
|
|
129
|
+
if (c < 0x20 || lone) {
|
|
130
|
+
out.append(u"\\u");
|
|
131
|
+
out.push_back(hex[(c >> 12) & 15]);
|
|
132
|
+
out.push_back(hex[(c >> 8) & 15]);
|
|
133
|
+
out.push_back(hex[(c >> 4) & 15]);
|
|
134
|
+
out.push_back(hex[c & 15]);
|
|
135
|
+
} else {
|
|
136
|
+
out.push_back(c);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
out.push_back(u'"');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
Iter<String> iterOf(const String& s) { return iterOf(splitCodePoints(s)); }
|
|
145
|
+
|
|
146
|
+
namespace {
|
|
147
|
+
class BytesIter final : public IterObject<double> {
|
|
148
|
+
public:
|
|
149
|
+
explicit BytesIter(Bytes b) : b_(std::move(b)) {}
|
|
150
|
+
std::optional<double> next() override {
|
|
151
|
+
if (i_ >= b_.size()) return std::nullopt;
|
|
152
|
+
return b_.at(i_++);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private:
|
|
156
|
+
Bytes b_;
|
|
157
|
+
size_t i_ = 0;
|
|
158
|
+
};
|
|
159
|
+
} // namespace
|
|
160
|
+
|
|
161
|
+
Iter<double> iterOf(const Bytes& b) { return std::make_shared<BytesIter>(b); }
|
|
162
|
+
|
|
163
|
+
} // namespace lucent
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// Lucent runtime — Uint8Array.
|
|
2
|
+
#pragma once
|
|
3
|
+
|
|
4
|
+
#include <cmath>
|
|
5
|
+
#include <cstdint>
|
|
6
|
+
#include <memory>
|
|
7
|
+
#include <vector>
|
|
8
|
+
|
|
9
|
+
#include "array.h"
|
|
10
|
+
#include "core.h"
|
|
11
|
+
#include "jsstring.h"
|
|
12
|
+
|
|
13
|
+
namespace lucent {
|
|
14
|
+
|
|
15
|
+
/// A Uint8Array: a view (offset, length) over a shared byte buffer.
|
|
16
|
+
/// `subarray` shares the buffer; `slice` copies, as in JavaScript.
|
|
17
|
+
class Bytes {
|
|
18
|
+
public:
|
|
19
|
+
Bytes() : buf_(std::make_shared<std::vector<uint8_t>>()) {}
|
|
20
|
+
explicit Bytes(double length) {
|
|
21
|
+
if (!(length >= 0) || std::trunc(length) != length || length > 2147483647.0) throwRangeError("Invalid typed array length");
|
|
22
|
+
buf_ = std::make_shared<std::vector<uint8_t>>(static_cast<size_t>(length));
|
|
23
|
+
len_ = static_cast<size_t>(length);
|
|
24
|
+
}
|
|
25
|
+
explicit Bytes(std::vector<uint8_t> data) : buf_(std::make_shared<std::vector<uint8_t>>(std::move(data))) { len_ = buf_->size(); }
|
|
26
|
+
static Bytes fromArray(const Array<double>& values) {
|
|
27
|
+
std::vector<uint8_t> out;
|
|
28
|
+
out.reserve(values.size());
|
|
29
|
+
for (double v : values.items()) out.push_back(toUint8(v));
|
|
30
|
+
return Bytes(std::move(out));
|
|
31
|
+
}
|
|
32
|
+
static Bytes copy(const uint8_t* data, size_t n) { return Bytes(std::vector<uint8_t>(data, data + n)); }
|
|
33
|
+
|
|
34
|
+
static uint8_t toUint8(double v) { return static_cast<uint8_t>(toUint32(v) & 0xFF); }
|
|
35
|
+
|
|
36
|
+
size_t size() const { return len_; }
|
|
37
|
+
double length() const { return static_cast<double>(len_); }
|
|
38
|
+
double byteLength() const { return length(); }
|
|
39
|
+
uint8_t* data() { return buf_->data() + off_; }
|
|
40
|
+
const uint8_t* data() const { return buf_->data() + off_; }
|
|
41
|
+
|
|
42
|
+
Opt<double> get(double index) const {
|
|
43
|
+
if (index >= 0 && index < static_cast<double>(len_) && std::trunc(index) == index) return static_cast<double>(data()[static_cast<size_t>(index)]);
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
double at(size_t i) const { return static_cast<double>(data()[i]); }
|
|
47
|
+
/// Out-of-range writes are ignored, as for typed arrays in JavaScript.
|
|
48
|
+
void set(double index, double value) {
|
|
49
|
+
if (index >= 0 && index < static_cast<double>(len_) && std::trunc(index) == index) data()[static_cast<size_t>(index)] = toUint8(value);
|
|
50
|
+
}
|
|
51
|
+
void setFrom(const Bytes& src, double offset = 0) {
|
|
52
|
+
if (!(offset >= 0) || static_cast<size_t>(offset) + src.len_ > len_) throwRangeError("offset is out of bounds");
|
|
53
|
+
std::vector<uint8_t> tmp(src.data(), src.data() + src.len_);
|
|
54
|
+
std::copy(tmp.begin(), tmp.end(), data() + static_cast<size_t>(offset));
|
|
55
|
+
}
|
|
56
|
+
void setFrom(const Array<double>& src, double offset = 0) {
|
|
57
|
+
if (!(offset >= 0) || static_cast<size_t>(offset) + src.size() > len_) throwRangeError("offset is out of bounds");
|
|
58
|
+
size_t o = static_cast<size_t>(offset);
|
|
59
|
+
for (size_t i = 0; i < src.size(); i++) data()[o + i] = toUint8(src.at(i));
|
|
60
|
+
}
|
|
61
|
+
Bytes subarray(double start) const { return subarray(start, length()); }
|
|
62
|
+
Bytes subarray(double start, double end) const {
|
|
63
|
+
size_t a = detail::relativeIndex(start, len_), b = detail::relativeIndex(end, len_);
|
|
64
|
+
Bytes v = *this;
|
|
65
|
+
v.off_ = off_ + a;
|
|
66
|
+
v.len_ = b > a ? b - a : 0;
|
|
67
|
+
return v;
|
|
68
|
+
}
|
|
69
|
+
Bytes slice() const { return copy(data(), len_); }
|
|
70
|
+
Bytes slice(double start) const { return slice(start, length()); }
|
|
71
|
+
Bytes slice(double start, double end) const {
|
|
72
|
+
size_t a = detail::relativeIndex(start, len_), b = detail::relativeIndex(end, len_);
|
|
73
|
+
return b > a ? copy(data() + a, b - a) : Bytes();
|
|
74
|
+
}
|
|
75
|
+
Bytes& fill(double v) {
|
|
76
|
+
std::fill(data(), data() + len_, toUint8(v));
|
|
77
|
+
return *this;
|
|
78
|
+
}
|
|
79
|
+
double indexOf(double v) const {
|
|
80
|
+
for (size_t i = 0; i < len_; i++) {
|
|
81
|
+
if (static_cast<double>(data()[i]) == v) return static_cast<double>(i);
|
|
82
|
+
}
|
|
83
|
+
return -1;
|
|
84
|
+
}
|
|
85
|
+
bool includes(double v) const { return indexOf(v) >= 0; }
|
|
86
|
+
template <class F>
|
|
87
|
+
void forEach(F&& f) const {
|
|
88
|
+
for (size_t i = 0; i < len_; i++) invokeCallback(f, at(i), static_cast<double>(i), *this);
|
|
89
|
+
}
|
|
90
|
+
template <class F>
|
|
91
|
+
Bytes map(F&& f) const {
|
|
92
|
+
std::vector<uint8_t> out(len_);
|
|
93
|
+
for (size_t i = 0; i < len_; i++) out[i] = toUint8(invokeCallback(f, at(i), static_cast<double>(i), *this));
|
|
94
|
+
return Bytes(std::move(out));
|
|
95
|
+
}
|
|
96
|
+
template <class U, class F>
|
|
97
|
+
U reduce(F&& f, U init) const {
|
|
98
|
+
for (size_t i = 0; i < len_; i++) init = U(invokeCallback(f, init, at(i), static_cast<double>(i), *this));
|
|
99
|
+
return init;
|
|
100
|
+
}
|
|
101
|
+
String join(const String& sep = String::fromLatin1(",")) const {
|
|
102
|
+
String out;
|
|
103
|
+
for (size_t i = 0; i < len_; i++) {
|
|
104
|
+
if (i > 0) out += sep;
|
|
105
|
+
out += numberToString(at(i));
|
|
106
|
+
}
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
109
|
+
Array<double> toArray() const {
|
|
110
|
+
std::vector<double> out(len_);
|
|
111
|
+
for (size_t i = 0; i < len_; i++) out[i] = at(i);
|
|
112
|
+
return Array<double>(std::move(out));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const void* identity() const { return this->buf_.get(); }
|
|
116
|
+
friend bool strictEquals(const Bytes& a, const Bytes& b) { return a.buf_ == b.buf_ && a.off_ == b.off_ && a.len_ == b.len_; }
|
|
117
|
+
friend String toJsString(const Bytes& b) { return b.join(); }
|
|
118
|
+
|
|
119
|
+
private:
|
|
120
|
+
std::shared_ptr<std::vector<uint8_t>> buf_;
|
|
121
|
+
size_t off_ = 0;
|
|
122
|
+
size_t len_ = 0;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
/// TextEncoder().encode / TextDecoder().decode for UTF-8.
|
|
126
|
+
Bytes utf8Encode(const String& s);
|
|
127
|
+
String utf8Decode(const Bytes& b);
|
|
128
|
+
|
|
129
|
+
} // namespace lucent
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Lucent runtime — console.log / warn / error.
|
|
2
|
+
#pragma once
|
|
3
|
+
|
|
4
|
+
#include <functional>
|
|
5
|
+
|
|
6
|
+
#include "jsstring.h"
|
|
7
|
+
|
|
8
|
+
namespace lucent {
|
|
9
|
+
|
|
10
|
+
enum class ConsoleLevel { Log, Warn, Error };
|
|
11
|
+
using ConsoleSink = std::function<void(ConsoleLevel, const String&)>;
|
|
12
|
+
|
|
13
|
+
/// Replaces the platform log (os_log / logcat / stdout), e.g. in tests.
|
|
14
|
+
void setConsoleSink(ConsoleSink sink);
|
|
15
|
+
void consoleWrite(ConsoleLevel level, const String& message);
|
|
16
|
+
|
|
17
|
+
} // namespace lucent
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// Lucent runtime — core value model.
|
|
2
|
+
//
|
|
3
|
+
// Every Lucent value is one of:
|
|
4
|
+
// * a primitive held by value: double (number), bool (boolean), String
|
|
5
|
+
// (an immutable, shared UTF-16 string);
|
|
6
|
+
// * a handle to a shared heap object (Array, Map, Set, Dict, Bytes, Fn,
|
|
7
|
+
// Promise, and Ref<T> for structs and classes). Copying a handle aliases
|
|
8
|
+
// the object, exactly like assigning an object in JavaScript;
|
|
9
|
+
// * Opt<T> for `T | undefined | null`, and Union<...> for other unions.
|
|
10
|
+
//
|
|
11
|
+
// Objects are reference counted. There is no cycle collector: a cycle of
|
|
12
|
+
// strong references is kept alive until the process ends.
|
|
13
|
+
#pragma once
|
|
14
|
+
|
|
15
|
+
#include <cstddef>
|
|
16
|
+
#include <cstdint>
|
|
17
|
+
#include <memory>
|
|
18
|
+
#include <optional>
|
|
19
|
+
#include <type_traits>
|
|
20
|
+
#include <utility>
|
|
21
|
+
#include <variant>
|
|
22
|
+
|
|
23
|
+
namespace lucent {
|
|
24
|
+
|
|
25
|
+
template <class T>
|
|
26
|
+
using Ref = std::shared_ptr<T>;
|
|
27
|
+
|
|
28
|
+
/// Base of every struct and class instance.
|
|
29
|
+
struct Object : std::enable_shared_from_this<Object> {
|
|
30
|
+
virtual ~Object() = default;
|
|
31
|
+
/// Stable identity handed to JavaScript the first time the object crosses
|
|
32
|
+
/// the boundary (0 = never crossed). Only touched on the JS thread.
|
|
33
|
+
uint64_t jsIdentity = 0;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
struct Undefined {
|
|
37
|
+
friend constexpr bool operator==(Undefined, Undefined) { return true; }
|
|
38
|
+
};
|
|
39
|
+
struct Null {
|
|
40
|
+
friend constexpr bool operator==(Null, Null) { return true; }
|
|
41
|
+
};
|
|
42
|
+
inline constexpr Undefined undefined{};
|
|
43
|
+
inline constexpr Null null{};
|
|
44
|
+
|
|
45
|
+
[[noreturn]] void throwTypeError(const char* message);
|
|
46
|
+
[[noreturn]] void throwRangeError(const char* message);
|
|
47
|
+
|
|
48
|
+
/// Placed where TypeScript proved control cannot reach (e.g. after an
|
|
49
|
+
/// exhaustive switch). Throws instead of running off the end of a function.
|
|
50
|
+
[[noreturn]] inline void unreachable() { throwTypeError("Reached code the type checker proved unreachable"); }
|
|
51
|
+
|
|
52
|
+
/// `T | undefined | null`. Remembers which of the two absent values it holds
|
|
53
|
+
/// so `x === null` and `x === undefined` behave as in JavaScript.
|
|
54
|
+
template <class T>
|
|
55
|
+
class Opt {
|
|
56
|
+
public:
|
|
57
|
+
using ValueType = T;
|
|
58
|
+
enum class State : uint8_t { Undefined, Null, Value };
|
|
59
|
+
|
|
60
|
+
Opt() = default;
|
|
61
|
+
Opt(Undefined) {}
|
|
62
|
+
Opt(Null) : state_(State::Null) {}
|
|
63
|
+
Opt(const T& v) : value_(v), state_(State::Value) {}
|
|
64
|
+
Opt(T&& v) : value_(std::move(v)), state_(State::Value) {}
|
|
65
|
+
template <class U,
|
|
66
|
+
std::enable_if_t<!std::is_same_v<std::decay_t<U>, Opt> &&
|
|
67
|
+
!std::is_same_v<std::decay_t<U>, T> &&
|
|
68
|
+
!std::is_same_v<std::decay_t<U>, Undefined> &&
|
|
69
|
+
!std::is_same_v<std::decay_t<U>, Null> &&
|
|
70
|
+
std::is_constructible_v<T, U&&>,
|
|
71
|
+
int> = 0>
|
|
72
|
+
Opt(U&& v) : value_(T(std::forward<U>(v))), state_(State::Value) {}
|
|
73
|
+
|
|
74
|
+
bool has() const { return state_ == State::Value; }
|
|
75
|
+
bool isUndefined() const { return state_ == State::Undefined; }
|
|
76
|
+
bool isNull() const { return state_ == State::Null; }
|
|
77
|
+
State state() const { return state_; }
|
|
78
|
+
|
|
79
|
+
/// The `!` operator: a checked unwrap.
|
|
80
|
+
const T& value() const {
|
|
81
|
+
if (state_ != State::Value) throwTypeError(state_ == State::Null ? "Unexpected null value" : "Unexpected undefined value");
|
|
82
|
+
return *value_;
|
|
83
|
+
}
|
|
84
|
+
T& value() {
|
|
85
|
+
if (state_ != State::Value) throwTypeError(state_ == State::Null ? "Unexpected null value" : "Unexpected undefined value");
|
|
86
|
+
return *value_;
|
|
87
|
+
}
|
|
88
|
+
/// Unchecked access after a narrowing check the compiler has proven.
|
|
89
|
+
const T& get() const { return *value_; }
|
|
90
|
+
T& get() { return *value_; }
|
|
91
|
+
|
|
92
|
+
template <class U>
|
|
93
|
+
T valueOr(U&& fallback) const {
|
|
94
|
+
return has() ? *value_ : T(std::forward<U>(fallback));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private:
|
|
98
|
+
std::optional<T> value_;
|
|
99
|
+
State state_ = State::Undefined;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
template <class T>
|
|
103
|
+
struct IsOpt : std::false_type {};
|
|
104
|
+
template <class T>
|
|
105
|
+
struct IsOpt<Opt<T>> : std::true_type {};
|
|
106
|
+
|
|
107
|
+
template <class T>
|
|
108
|
+
struct IsRef : std::false_type {};
|
|
109
|
+
template <class T>
|
|
110
|
+
struct IsRef<std::shared_ptr<T>> : std::true_type {};
|
|
111
|
+
|
|
112
|
+
template <class... Ts>
|
|
113
|
+
using Union = std::variant<Ts...>;
|
|
114
|
+
|
|
115
|
+
/// Boxes a local that a closure captures and later mutates, so the closure and
|
|
116
|
+
/// the enclosing function share one variable, as in JavaScript.
|
|
117
|
+
template <class T>
|
|
118
|
+
class Box {
|
|
119
|
+
public:
|
|
120
|
+
Box() : p_(std::make_shared<T>()) {}
|
|
121
|
+
explicit Box(T v) : p_(std::make_shared<T>(std::move(v))) {}
|
|
122
|
+
T& operator*() const { return *p_; }
|
|
123
|
+
T* operator->() const { return p_.get(); }
|
|
124
|
+
|
|
125
|
+
private:
|
|
126
|
+
std::shared_ptr<T> p_;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
} // namespace lucent
|