@ugo-studio/jspp 0.1.5 → 0.1.7
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/LICENSE +25 -21
- package/README.md +2 -16
- package/dist/ast/symbols.js +3 -0
- package/dist/cli.js +7 -4
- package/dist/core/codegen/control-flow-handlers.js +28 -9
- package/dist/core/codegen/expression-handlers.js +7 -4
- package/dist/core/codegen/function-handlers.js +45 -27
- package/dist/core/codegen/helpers.js +24 -17
- package/dist/core/codegen/index.js +16 -9
- package/dist/core/codegen/statement-handlers.js +242 -58
- package/package.json +2 -1
- package/src/prelude/any_value.hpp +27 -1
- package/src/prelude/any_value_access.hpp +161 -122
- package/src/prelude/any_value_helpers.hpp +2 -0
- package/src/prelude/index.hpp +3 -0
- package/src/prelude/library/promise.hpp +6 -14
- package/src/prelude/types.hpp +6 -0
- package/src/prelude/utils/access.hpp +35 -2
- package/src/prelude/values/array.hpp +2 -2
- package/src/prelude/values/async_iterator.hpp +79 -0
- package/src/prelude/values/function.hpp +2 -1
- package/src/prelude/values/helpers/array.hpp +3 -0
- package/src/prelude/values/helpers/async_iterator.hpp +275 -0
- package/src/prelude/values/helpers/function.hpp +4 -0
- package/src/prelude/values/helpers/promise.hpp +10 -3
- package/src/prelude/values/promise.hpp +4 -8
- package/src/prelude/values/prototypes/async_iterator.hpp +50 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include "types.hpp"
|
|
4
|
+
#include "values/async_iterator.hpp"
|
|
5
|
+
#include "any_value.hpp"
|
|
6
|
+
#include "values/prototypes/async_iterator.hpp"
|
|
7
|
+
|
|
8
|
+
// --- JsAsyncIterator methods ---
|
|
9
|
+
|
|
10
|
+
template <typename T>
|
|
11
|
+
std::string jspp::JsAsyncIterator<T>::to_std_string() const
|
|
12
|
+
{
|
|
13
|
+
return "[object AsyncGenerator]";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
template <typename T>
|
|
17
|
+
jspp::AnyValue jspp::JsAsyncIterator<T>::get_property(const std::string &key, const AnyValue &thisVal)
|
|
18
|
+
{
|
|
19
|
+
auto it = props.find(key);
|
|
20
|
+
if (it == props.end())
|
|
21
|
+
{
|
|
22
|
+
if constexpr (std::is_same_v<T, AnyValue>)
|
|
23
|
+
{
|
|
24
|
+
auto proto_it = AsyncIteratorPrototypes::get(key, this);
|
|
25
|
+
if (proto_it.has_value())
|
|
26
|
+
{
|
|
27
|
+
return AnyValue::resolve_property_for_read(proto_it.value(), thisVal, key);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return AnyValue::make_undefined();
|
|
31
|
+
}
|
|
32
|
+
return AnyValue::resolve_property_for_read(it->second, thisVal, key);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
template <typename T>
|
|
36
|
+
jspp::AnyValue jspp::JsAsyncIterator<T>::set_property(const std::string &key, const AnyValue &value, const AnyValue &thisVal)
|
|
37
|
+
{
|
|
38
|
+
if constexpr (std::is_same_v<T, AnyValue>)
|
|
39
|
+
{
|
|
40
|
+
auto proto_it = AsyncIteratorPrototypes::get(key, this);
|
|
41
|
+
if (proto_it.has_value())
|
|
42
|
+
{
|
|
43
|
+
auto proto_value = proto_it.value();
|
|
44
|
+
if (proto_value.is_accessor_descriptor())
|
|
45
|
+
{
|
|
46
|
+
return AnyValue::resolve_property_for_write(proto_value, thisVal, value, key);
|
|
47
|
+
}
|
|
48
|
+
if (proto_value.is_data_descriptor() && !proto_value.as_data_descriptor()->writable)
|
|
49
|
+
{
|
|
50
|
+
return AnyValue::resolve_property_for_write(proto_value, thisVal, value, key);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
auto it = props.find(key);
|
|
56
|
+
if (it != props.end())
|
|
57
|
+
{
|
|
58
|
+
return jspp::AnyValue::resolve_property_for_write(it->second, thisVal, value, key);
|
|
59
|
+
}
|
|
60
|
+
else
|
|
61
|
+
{
|
|
62
|
+
props[key] = value;
|
|
63
|
+
return value;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
template <typename T>
|
|
68
|
+
void jspp::JsAsyncIterator<T>::resume_next()
|
|
69
|
+
{
|
|
70
|
+
if (!handle || handle.done())
|
|
71
|
+
return;
|
|
72
|
+
auto &p = handle.promise();
|
|
73
|
+
if (p.is_awaiting || p.is_running)
|
|
74
|
+
return;
|
|
75
|
+
if (p.pending_calls.empty())
|
|
76
|
+
return;
|
|
77
|
+
|
|
78
|
+
p.is_running = true;
|
|
79
|
+
|
|
80
|
+
auto &next_call = p.pending_calls.front();
|
|
81
|
+
p.current_input = next_call.second;
|
|
82
|
+
|
|
83
|
+
handle.resume();
|
|
84
|
+
|
|
85
|
+
p.is_running = false;
|
|
86
|
+
|
|
87
|
+
// After yield/return, if more calls are pending, handle them.
|
|
88
|
+
if (!p.pending_calls.empty() && !p.is_awaiting && !handle.done())
|
|
89
|
+
{
|
|
90
|
+
Scheduler::instance().enqueue([this]()
|
|
91
|
+
{ this->resume_next(); });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
template <typename T>
|
|
96
|
+
jspp::JsPromise jspp::JsAsyncIterator<T>::next(const T &val)
|
|
97
|
+
{
|
|
98
|
+
JsPromise p;
|
|
99
|
+
if (handle)
|
|
100
|
+
{
|
|
101
|
+
if (handle.done())
|
|
102
|
+
{
|
|
103
|
+
p.resolve(AnyValue::make_object({{"value", Constants::UNDEFINED}, {"done", Constants::TRUE}}));
|
|
104
|
+
}
|
|
105
|
+
else
|
|
106
|
+
{
|
|
107
|
+
handle.promise().pending_calls.push({p, val});
|
|
108
|
+
resume_next();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
else
|
|
112
|
+
{
|
|
113
|
+
p.resolve(AnyValue::make_object({{"value", Constants::UNDEFINED}, {"done", Constants::TRUE}}));
|
|
114
|
+
}
|
|
115
|
+
return p;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// --- JsAsyncIterator::promise_type methods ---
|
|
119
|
+
|
|
120
|
+
template <typename T>
|
|
121
|
+
template <typename From>
|
|
122
|
+
auto jspp::JsAsyncIterator<T>::promise_type::yield_value(From &&from)
|
|
123
|
+
{
|
|
124
|
+
if (!pending_calls.empty())
|
|
125
|
+
{
|
|
126
|
+
auto call = pending_calls.front();
|
|
127
|
+
pending_calls.pop();
|
|
128
|
+
AnyValue result = AnyValue::make_object({{"value", std::forward<From>(from)}, {"done", AnyValue::make_boolean(false)}});
|
|
129
|
+
call.first.resolve(result);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
struct YieldAwaiter
|
|
133
|
+
{
|
|
134
|
+
promise_type &p;
|
|
135
|
+
bool await_ready() { return false; }
|
|
136
|
+
void await_suspend(std::coroutine_handle<promise_type> h)
|
|
137
|
+
{
|
|
138
|
+
// Suspended at yield.
|
|
139
|
+
}
|
|
140
|
+
T await_resume() { return p.current_input; }
|
|
141
|
+
};
|
|
142
|
+
return YieldAwaiter{*this};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
template <typename T>
|
|
146
|
+
template <typename From>
|
|
147
|
+
void jspp::JsAsyncIterator<T>::promise_type::return_value(From &&from)
|
|
148
|
+
{
|
|
149
|
+
if (!pending_calls.empty())
|
|
150
|
+
{
|
|
151
|
+
auto call = pending_calls.front();
|
|
152
|
+
pending_calls.pop();
|
|
153
|
+
AnyValue result = AnyValue::make_object({{"value", std::forward<From>(from)}, {"done", Constants::TRUE}});
|
|
154
|
+
call.first.resolve(result);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
while (!pending_calls.empty())
|
|
158
|
+
{
|
|
159
|
+
auto call = pending_calls.front();
|
|
160
|
+
pending_calls.pop();
|
|
161
|
+
AnyValue result = AnyValue::make_object({{"value", Constants::UNDEFINED}, {"done", Constants::TRUE}});
|
|
162
|
+
call.first.resolve(result);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
template <typename T>
|
|
167
|
+
void jspp::JsAsyncIterator<T>::promise_type::fail_all(const AnyValue &reason)
|
|
168
|
+
{
|
|
169
|
+
while (!pending_calls.empty())
|
|
170
|
+
{
|
|
171
|
+
auto call = pending_calls.front();
|
|
172
|
+
pending_calls.pop();
|
|
173
|
+
call.first.reject(reason);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
template <typename T>
|
|
178
|
+
void jspp::JsAsyncIterator<T>::promise_type::unhandled_exception()
|
|
179
|
+
{
|
|
180
|
+
try
|
|
181
|
+
{
|
|
182
|
+
std::rethrow_exception(std::current_exception());
|
|
183
|
+
}
|
|
184
|
+
catch (const Exception &e)
|
|
185
|
+
{
|
|
186
|
+
fail_all(*e.data);
|
|
187
|
+
}
|
|
188
|
+
catch (const std::exception &e)
|
|
189
|
+
{
|
|
190
|
+
fail_all(AnyValue::make_string(e.what()));
|
|
191
|
+
}
|
|
192
|
+
catch (...)
|
|
193
|
+
{
|
|
194
|
+
fail_all(AnyValue::make_string("Unknown error in async generator"));
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
template <typename T>
|
|
199
|
+
auto jspp::JsAsyncIterator<T>::promise_type::await_transform(AnyValue value)
|
|
200
|
+
{
|
|
201
|
+
is_awaiting = true;
|
|
202
|
+
struct AsyncIterAwaiter
|
|
203
|
+
{
|
|
204
|
+
AnyValueAwaiter base_awaiter;
|
|
205
|
+
promise_type &p_ref;
|
|
206
|
+
|
|
207
|
+
bool await_ready() { return base_awaiter.await_ready(); }
|
|
208
|
+
void await_suspend(std::coroutine_handle<promise_type> h)
|
|
209
|
+
{
|
|
210
|
+
if (!base_awaiter.value.is_promise())
|
|
211
|
+
{
|
|
212
|
+
jspp::Scheduler::instance().enqueue([h]() mutable
|
|
213
|
+
{
|
|
214
|
+
auto &pr = h.promise();
|
|
215
|
+
pr.is_awaiting = false;
|
|
216
|
+
pr.is_running = true;
|
|
217
|
+
h.resume();
|
|
218
|
+
pr.is_running = false;
|
|
219
|
+
|
|
220
|
+
if (!h.done() && !pr.is_awaiting && !pr.pending_calls.empty())
|
|
221
|
+
{
|
|
222
|
+
while (!h.done() && !pr.is_awaiting && !pr.pending_calls.empty())
|
|
223
|
+
{
|
|
224
|
+
pr.is_running = true;
|
|
225
|
+
pr.current_input = pr.pending_calls.front().second;
|
|
226
|
+
h.resume();
|
|
227
|
+
pr.is_running = false;
|
|
228
|
+
}
|
|
229
|
+
} });
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
auto p = base_awaiter.value.as_promise();
|
|
233
|
+
p->then(
|
|
234
|
+
[h](AnyValue v) mutable
|
|
235
|
+
{
|
|
236
|
+
auto &pr = h.promise();
|
|
237
|
+
pr.is_awaiting = false;
|
|
238
|
+
pr.is_running = true;
|
|
239
|
+
h.resume();
|
|
240
|
+
pr.is_running = false;
|
|
241
|
+
|
|
242
|
+
// After resume, if we suspended at a yield and have more calls, loop.
|
|
243
|
+
if (!h.done() && !pr.is_awaiting && !pr.pending_calls.empty())
|
|
244
|
+
{
|
|
245
|
+
// We need to call resume_next, but we don't have the iterator.
|
|
246
|
+
// Actually, the loop in resume_next handles this.
|
|
247
|
+
// But wait, who calls resume_next?
|
|
248
|
+
// If we are here, we are in a microtask.
|
|
249
|
+
// Let's just manually continue if needed.
|
|
250
|
+
while (!h.done() && !pr.is_awaiting && !pr.pending_calls.empty())
|
|
251
|
+
{
|
|
252
|
+
pr.is_running = true;
|
|
253
|
+
pr.current_input = pr.pending_calls.front().second;
|
|
254
|
+
h.resume();
|
|
255
|
+
pr.is_running = false;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
[h](AnyValue e) mutable
|
|
260
|
+
{
|
|
261
|
+
auto &pr = h.promise();
|
|
262
|
+
pr.is_awaiting = false;
|
|
263
|
+
pr.is_running = true;
|
|
264
|
+
h.resume();
|
|
265
|
+
pr.is_running = false;
|
|
266
|
+
// Errors handled via await_resume/exception throw
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
AnyValue await_resume()
|
|
270
|
+
{
|
|
271
|
+
return base_awaiter.await_resume();
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
return AsyncIterAwaiter{AnyValueAwaiter{std::move(value)}, *this};
|
|
275
|
+
}
|
|
@@ -32,6 +32,10 @@ namespace jspp
|
|
|
32
32
|
{
|
|
33
33
|
return AnyValue::make_promise((*func)(thisVal, args));
|
|
34
34
|
}
|
|
35
|
+
else if (std::function<jspp::JsAsyncIterator<jspp::AnyValue>(const AnyValue &, std::span<const AnyValue>)> *func = std::get_if<3>(&callable))
|
|
36
|
+
{
|
|
37
|
+
return AnyValue::from_async_iterator((*func)(thisVal, args));
|
|
38
|
+
}
|
|
35
39
|
else
|
|
36
40
|
{
|
|
37
41
|
return AnyValue::make_undefined();
|
|
@@ -92,6 +92,10 @@ namespace jspp {
|
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
inline auto JsPromise::operator co_await() const {
|
|
96
|
+
return AnyValueAwaiter{AnyValue::make_promise(*this)};
|
|
97
|
+
}
|
|
98
|
+
|
|
95
99
|
inline std::string JsPromise::to_std_string() const {
|
|
96
100
|
return "[object Promise]";
|
|
97
101
|
}
|
|
@@ -144,17 +148,20 @@ namespace jspp {
|
|
|
144
148
|
return AnyValueAwaiter{value};
|
|
145
149
|
}
|
|
146
150
|
|
|
151
|
+
inline auto JsPromisePromiseType::await_transform(const JsPromise& value) {
|
|
152
|
+
return AnyValueAwaiter{AnyValue::make_promise(value)};
|
|
153
|
+
}
|
|
154
|
+
|
|
147
155
|
// --- AnyValueAwaiter ---
|
|
148
156
|
|
|
149
157
|
inline bool AnyValueAwaiter::await_ready() {
|
|
150
|
-
|
|
151
|
-
// Always suspend for promises to ensure microtask interleaving, even if already resolved.
|
|
158
|
+
// Always suspend to ensure microtask interleaving, even if already resolved or not a promise.
|
|
152
159
|
return false;
|
|
153
160
|
}
|
|
154
161
|
|
|
155
162
|
inline void AnyValueAwaiter::await_suspend(std::coroutine_handle<> h) {
|
|
156
163
|
if (!value.is_promise()) {
|
|
157
|
-
h.resume();
|
|
164
|
+
jspp::Scheduler::instance().enqueue([h]() mutable { h.resume(); });
|
|
158
165
|
return;
|
|
159
166
|
}
|
|
160
167
|
auto p = value.as_promise();
|
|
@@ -46,6 +46,8 @@ namespace jspp
|
|
|
46
46
|
std::string to_std_string() const;
|
|
47
47
|
AnyValue get_property(const std::string& key, const AnyValue& thisVal);
|
|
48
48
|
AnyValue set_property(const std::string& key, const AnyValue& value, const AnyValue& thisVal);
|
|
49
|
+
|
|
50
|
+
auto operator co_await() const;
|
|
49
51
|
};
|
|
50
52
|
|
|
51
53
|
struct JsPromisePromiseType {
|
|
@@ -61,13 +63,7 @@ namespace jspp
|
|
|
61
63
|
|
|
62
64
|
// await_transform for AnyValue
|
|
63
65
|
auto await_transform(const AnyValue& value);
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
// Awaiter for AnyValue
|
|
67
|
-
struct AnyValueAwaiter {
|
|
68
|
-
const AnyValue& value; // Reference to the value being awaited
|
|
69
|
-
bool await_ready();
|
|
70
|
-
void await_suspend(std::coroutine_handle<> h);
|
|
71
|
-
AnyValue await_resume();
|
|
66
|
+
// await_transform for JsPromise
|
|
67
|
+
auto await_transform(const JsPromise& value);
|
|
72
68
|
};
|
|
73
69
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include "types.hpp"
|
|
4
|
+
#include "values/async_iterator.hpp"
|
|
5
|
+
#include "any_value.hpp"
|
|
6
|
+
#include "exception.hpp"
|
|
7
|
+
#include "utils/operators.hpp"
|
|
8
|
+
|
|
9
|
+
namespace jspp
|
|
10
|
+
{
|
|
11
|
+
namespace AsyncIteratorPrototypes
|
|
12
|
+
{
|
|
13
|
+
inline std::optional<AnyValue> get(const std::string &key, JsAsyncIterator<AnyValue> *self)
|
|
14
|
+
{
|
|
15
|
+
// --- toString() method ---
|
|
16
|
+
if (key == "toString" || key == WellKnownSymbols::toStringTag->key)
|
|
17
|
+
{
|
|
18
|
+
return AnyValue::make_function([self](const AnyValue &thisVal, std::span<const AnyValue>) -> AnyValue
|
|
19
|
+
{ return AnyValue::make_string(self->to_std_string()); },
|
|
20
|
+
key);
|
|
21
|
+
}
|
|
22
|
+
// --- [Symbol.asyncIterator]() method ---
|
|
23
|
+
// For async iterators, the async iterator is itself (similar to sync iterators)
|
|
24
|
+
if (key == WellKnownSymbols::asyncIterator->key)
|
|
25
|
+
{
|
|
26
|
+
// We return 'this'. Since we can't easily create a shared_ptr from raw pointer,
|
|
27
|
+
// we rely on the context to hold the reference or implement better shared_from_this strategy.
|
|
28
|
+
// For now, assume it works like Iterator.
|
|
29
|
+
return AnyValue::make_function([self](const AnyValue &thisVal, std::span<const AnyValue>) -> AnyValue
|
|
30
|
+
{
|
|
31
|
+
// This is slightly dangerous as we create a new shared_ptr from raw.
|
|
32
|
+
// TODO: fix lifetime management
|
|
33
|
+
return thisVal; },
|
|
34
|
+
key);
|
|
35
|
+
}
|
|
36
|
+
// --- next() method ---
|
|
37
|
+
if (key == "next")
|
|
38
|
+
{
|
|
39
|
+
return AnyValue::make_function([self](const AnyValue &thisVal, std::span<const AnyValue> args) -> AnyValue
|
|
40
|
+
{
|
|
41
|
+
AnyValue val = args.empty() ? AnyValue::make_undefined() : args[0];
|
|
42
|
+
auto res = self->next(val);
|
|
43
|
+
return AnyValue::make_promise(res); },
|
|
44
|
+
key);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return std::nullopt;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|