MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
1#include <stdlib.h>
2#include <stdio.h>
3#include <string.h>
4
5#include "errors.h"
6#include "internal.h"
7#include "runtime.h"
8#include "silver/engine.h"
9#include "descriptors.h"
10
11#include "modules/symbol.h"
12#include "modules/observable.h"
13
14
15
16static bool subscription_closed(ant_t *js, ant_value_t subscription) {
17 ant_value_t observer = js_get_slot(subscription, SLOT_SUBSCRIPTION_OBSERVER);
18 return vtype(observer) == T_UNDEF;
19}
20
21static void cleanup_subscription(ant_t *js, ant_value_t subscription) {
22 ant_value_t cleanup = js_get_slot(subscription, SLOT_SUBSCRIPTION_CLEANUP);
23 if (vtype(cleanup) == T_UNDEF) return;
24 if (!is_callable(cleanup)) return;
25
26 js_set_slot(subscription, SLOT_SUBSCRIPTION_CLEANUP, js_mkundef());
27 ant_value_t result = sv_vm_call(js->vm, js, cleanup, js_mkundef(), NULL, 0, NULL, false);
28
29 if (vtype(result) == T_ERR) fprintf(stderr, "Error in subscription cleanup: %s\n", js_str(js, result));
30}
31
32static ant_value_t create_subscription(ant_t *js, ant_value_t observer) {
33 ant_value_t subscription = js_mkobj(js);
34 js_set_slot(subscription, SLOT_SUBSCRIPTION_OBSERVER, observer);
35 js_set_slot(subscription, SLOT_SUBSCRIPTION_CLEANUP, js_mkundef());
36 js_set_sym(js, subscription, get_toStringTag_sym(), js_mkstr(js, "Subscription", 12));
37 return subscription;
38}
39
40static ant_value_t js_subscription_get_closed(ant_t *js, ant_value_t *args, int nargs) {
41 (void)args; (void)nargs;
42 ant_value_t subscription = js_getthis(js);
43
44 if (!is_special_object(subscription)) {
45 return js_mkerr_typed(js, JS_ERR_TYPE, "Subscription.closed getter called on non-object");
46 }
47 return js_bool(subscription_closed(js, subscription));
48}
49
50static ant_value_t js_subscription_unsubscribe(ant_t *js, ant_value_t *args, int nargs) {
51 (void)args; (void)nargs;
52 ant_value_t subscription = js_getthis(js);
53
54 if (!is_special_object(subscription)) {
55 return js_mkerr_typed(js, JS_ERR_TYPE, "Subscription.unsubscribe called on non-object");
56 }
57
58 if (subscription_closed(js, subscription)) return js_mkundef();
59
60 js_set_slot(subscription, SLOT_SUBSCRIPTION_OBSERVER, js_mkundef());
61 cleanup_subscription(js, subscription);
62
63 return js_mkundef();
64}
65
66static void setup_subscription_methods(ant_t *js, ant_value_t subscription) {
67 js_set(js, subscription, "unsubscribe", js_mkfun(js_subscription_unsubscribe));
68 ant_value_t closed_getter = js_mkfun(js_subscription_get_closed);
69 js_set_getter_desc(js, subscription, "closed", 6, closed_getter, JS_DESC_E | JS_DESC_C);
70}
71
72static ant_value_t js_subobs_get_closed(ant_t *js, ant_value_t *args, int nargs) {
73 (void)args; (void)nargs;
74 ant_value_t O = js_getthis(js);
75
76 if (!is_special_object(O)) {
77 return js_mkerr_typed(js, JS_ERR_TYPE, "SubscriptionObserver.closed getter called on non-object");
78 }
79
80 ant_value_t subscription = js_get_slot(O, SLOT_DATA);
81 if (!is_special_object(subscription)) {
82 return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid SubscriptionObserver");
83 }
84
85 return js_bool(subscription_closed(js, subscription));
86}
87
88static ant_value_t js_subobs_next(ant_t *js, ant_value_t *args, int nargs) {
89 ant_value_t O = js_getthis(js);
90
91 if (!is_special_object(O)) {
92 return js_mkerr_typed(js, JS_ERR_TYPE, "SubscriptionObserver.next called on non-object");
93 }
94
95 ant_value_t subscription = js_get_slot(O, SLOT_DATA);
96 if (!is_special_object(subscription)) {
97 return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid SubscriptionObserver");
98 }
99
100 if (subscription_closed(js, subscription)) return js_mkundef();
101
102 ant_value_t observer = js_get_slot(subscription, SLOT_SUBSCRIPTION_OBSERVER);
103 if (!is_special_object(observer)) return js_mkundef();
104
105 ant_value_t nextMethod = js_get(js, observer, "next");
106 if (is_callable(nextMethod)) {
107 ant_value_t value = (nargs > 0) ? args[0] : js_mkundef();
108 ant_value_t call_args[1] = {value};
109 ant_value_t result = sv_vm_call(js->vm, js, nextMethod, observer, call_args, 1, NULL, false);
110 if (vtype(result) == T_ERR) fprintf(stderr, "Error in observer.next: %s\n", js_str(js, result));
111 }
112
113 return js_mkundef();
114}
115
116static ant_value_t js_subobs_error(ant_t *js, ant_value_t *args, int nargs) {
117 ant_value_t O = js_getthis(js);
118
119 if (!is_special_object(O)) {
120 return js_mkerr_typed(js, JS_ERR_TYPE, "SubscriptionObserver.error called on non-object");
121 }
122
123 ant_value_t subscription = js_get_slot(O, SLOT_DATA);
124 if (!is_special_object(subscription)) {
125 return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid SubscriptionObserver");
126 }
127
128 if (subscription_closed(js, subscription)) return js_mkundef();
129
130 ant_value_t observer = js_get_slot(subscription, SLOT_SUBSCRIPTION_OBSERVER);
131 js_set_slot(subscription, SLOT_SUBSCRIPTION_OBSERVER, js_mkundef());
132
133 if (is_special_object(observer)) {
134 ant_value_t errorMethod = js_get(js, observer, "error");
135 if (is_callable(errorMethod)) {
136 ant_value_t exception = (nargs > 0) ? args[0] : js_mkundef();
137 ant_value_t call_args[1] = {exception};
138 ant_value_t result = sv_vm_call(js->vm, js, errorMethod, observer, call_args, 1, NULL, false);
139 if (vtype(result) == T_ERR) fprintf(stderr, "Error in observer.error: %s\n", js_str(js, result));
140 }
141 }
142
143 cleanup_subscription(js, subscription);
144 return js_mkundef();
145}
146
147static ant_value_t js_subobs_complete(ant_t *js, ant_value_t *args, int nargs) {
148 (void)args; (void)nargs;
149 ant_value_t O = js_getthis(js);
150
151 if (!is_special_object(O)) {
152 return js_mkerr_typed(js, JS_ERR_TYPE, "SubscriptionObserver.complete called on non-object");
153 }
154
155 ant_value_t subscription = js_get_slot(O, SLOT_DATA);
156 if (!is_special_object(subscription)) {
157 return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid SubscriptionObserver");
158 }
159
160 if (subscription_closed(js, subscription)) return js_mkundef();
161
162 ant_value_t observer = js_get_slot(subscription, SLOT_SUBSCRIPTION_OBSERVER);
163 js_set_slot(subscription, SLOT_SUBSCRIPTION_OBSERVER, js_mkundef());
164
165 if (is_special_object(observer)) {
166 ant_value_t completeMethod = js_get(js, observer, "complete");
167 if (is_callable(completeMethod)) {
168 ant_value_t result = sv_vm_call(js->vm, js, completeMethod, observer, NULL, 0, NULL, false);
169 if (vtype(result) == T_ERR) fprintf(stderr, "Error in observer.complete: %s\n", js_str(js, result));
170 }
171 }
172
173 cleanup_subscription(js, subscription);
174 return js_mkundef();
175}
176
177static ant_value_t create_subscription_observer(ant_t *js, ant_value_t subscription) {
178 ant_value_t subobs = js_mkobj(js);
179
180 js_set_slot(subobs, SLOT_DATA, subscription);
181 js_set(js, subobs, "next", js_mkfun(js_subobs_next));
182 js_set(js, subobs, "error", js_mkfun(js_subobs_error));
183 js_set(js, subobs, "complete", js_mkfun(js_subobs_complete));
184 js_set_sym(js, subobs, get_toStringTag_sym(), js_mkstr(js, "SubscriptionObserver", 20));
185
186 ant_value_t closed_getter = js_mkfun(js_subobs_get_closed);
187 js_set_getter_desc(js, subobs, "closed", 6, closed_getter, JS_DESC_E | JS_DESC_C);
188
189 return subobs;
190}
191
192static ant_value_t js_cleanup_fn(ant_t *js, ant_value_t *args, int nargs) {
193 (void)args; (void)nargs;
194 ant_value_t F = js_getcurrentfunc(js);
195 ant_value_t subscription = js_get_slot(F, SLOT_DATA);
196
197 if (!is_special_object(subscription)) return js_mkundef();
198
199 ant_value_t unsubscribe = js_get(js, subscription, "unsubscribe");
200 if (is_callable(unsubscribe)) {
201 return sv_vm_call(js->vm, js, unsubscribe, subscription, NULL, 0, NULL, false);
202 }
203
204 return js_mkundef();
205}
206
207static ant_value_t execute_subscriber(ant_t *js, ant_value_t subscriber, ant_value_t observer) {
208 ant_value_t call_args[1] = {observer};
209 ant_value_t subscriberResult = sv_vm_call(js->vm, js, subscriber, js_mkundef(), call_args, 1, NULL, false);
210
211 if (vtype(subscriberResult) == T_ERR) return subscriberResult;
212 if (vtype(subscriberResult) == T_NULL || vtype(subscriberResult) == T_UNDEF) return js_mkundef();
213 if (is_callable(subscriberResult)) return subscriberResult;
214
215 if (is_special_object(subscriberResult)) {
216 ant_value_t result = js_get(js, subscriberResult, "unsubscribe");
217 if (vtype(result) == T_UNDEF) {
218 return js_mkerr_typed(js, JS_ERR_TYPE, "Subscriber return value must have an unsubscribe method");
219 }
220
221 ant_value_t cleanupFunction = js_mkobj(js);
222 js_set_slot(cleanupFunction, SLOT_DATA, subscriberResult);
223 js_set_slot(cleanupFunction, SLOT_CFUNC, js_mkfun(js_cleanup_fn));
224 return js_obj_to_func(cleanupFunction);
225 }
226
227 return js_mkerr_typed(js, JS_ERR_TYPE, "Subscriber must return a function, an object with unsubscribe, or undefined");
228}
229
230static ant_value_t js_observable_subscribe(ant_t *js, ant_value_t *args, int nargs) {
231 ant_value_t O = js_getthis(js);
232
233 if (!is_special_object(O)) {
234 return js_mkerr_typed(js, JS_ERR_TYPE, "Observable.prototype.subscribe called on non-object");
235 }
236
237 ant_value_t subscriber = js_get_slot(O, SLOT_OBSERVABLE_SUBSCRIBER);
238 if (!is_callable(subscriber)) {
239 return js_mkerr_typed(js, JS_ERR_TYPE, "Observable has no [[Subscriber]] internal slot");
240 }
241
242 ant_value_t observer;
243
244 if (nargs > 0 && is_callable(args[0])) {
245 ant_value_t nextCallback = args[0];
246 ant_value_t errorCallback = (nargs > 1) ? args[1] : js_mkundef();
247 ant_value_t completeCallback = (nargs > 2) ? args[2] : js_mkundef();
248
249 observer = js_mkobj(js);
250 js_set(js, observer, "next", nextCallback);
251 js_set(js, observer, "error", errorCallback);
252 js_set(js, observer, "complete", completeCallback);
253 } else if (nargs > 0 && is_special_object(args[0])) {
254 observer = args[0];
255 } else observer = js_mkobj(js);
256
257 ant_value_t subscription = create_subscription(js, observer);
258 setup_subscription_methods(js, subscription);
259
260 ant_value_t start = js_get(js, observer, "start");
261 if (is_callable(start)) {
262 ant_value_t start_args[1] = {subscription};
263 ant_value_t result = sv_vm_call(js->vm, js, start, observer, start_args, 1, NULL, false);
264 if (vtype(result) == T_ERR) {
265 fprintf(stderr, "Error in observer.start: %s\n", js_str(js, result));
266 }
267 if (subscription_closed(js, subscription)) return subscription;
268 }
269
270 ant_value_t subscriptionObserver = create_subscription_observer(js, subscription);
271 ant_value_t subscriberResult = execute_subscriber(js, subscriber, subscriptionObserver);
272
273 if (vtype(subscriberResult) == T_ERR) {
274 ant_value_t thrown_error = js->thrown_value;
275 js->thrown_value = js_mkundef();
276 js->thrown_exists = false;
277
278 ant_value_t error_args[1] = {thrown_error};
279 ant_value_t error_method = js_get(js, subscriptionObserver, "error");
280 if (is_callable(error_method)) sv_vm_call(js->vm, js, error_method, subscriptionObserver, error_args, 1, NULL, false);
281 } else js_set_slot_wb(js, subscription, SLOT_SUBSCRIPTION_CLEANUP, subscriberResult);
282
283 if (subscription_closed(js, subscription)) cleanup_subscription(js, subscription);
284
285 return subscription;
286}
287
288static ant_value_t js_observable_symbol_observable(ant_t *js, ant_value_t *args, int nargs) {
289 (void)args; (void)nargs;
290 return js_getthis(js);
291}
292
293static ant_value_t js_observable_constructor(ant_t *js, ant_value_t *args, int nargs) {
294 if (nargs < 1) {
295 return js_mkerr_typed(js, JS_ERR_TYPE, "Observable constructor requires a subscriber function");
296 }
297
298 ant_value_t subscriber = args[0];
299 if (!is_callable(subscriber)) {
300 return js_mkerr_typed(js, JS_ERR_TYPE, "Observable subscriber must be a function");
301 }
302
303 ant_value_t proto = js_get_ctor_proto(js, "Observable", 10);
304 ant_value_t observable = js_mkobj(js);
305
306 js_set_proto_init(observable, proto);
307 js_set_slot(observable, SLOT_OBSERVABLE_SUBSCRIBER, subscriber);
308
309 return observable;
310}
311
312static ant_value_t js_of_subscriber(ant_t *js, ant_value_t *args, int nargs) {
313 ant_value_t F = js_getcurrentfunc(js);
314 ant_value_t items = js_get_slot(F, SLOT_DATA);
315
316 if (nargs < 1) return js_mkundef();
317
318 ant_value_t observer = args[0];
319 ant_value_t subscription = js_get_slot(observer, SLOT_DATA);
320
321 ant_value_t length_val = js_get(js, items, "length");
322 int length = (vtype(length_val) == T_NUM) ? (int)js_getnum(length_val) : 0;
323
324 for (int i = 0; i < length; i++) {
325 char key[16];
326 snprintf(key, sizeof(key), "%d", i);
327 ant_value_t value = js_get(js, items, key);
328
329 ant_value_t next = js_get(js, observer, "next");
330 if (is_callable(next)) {
331 ant_value_t next_args[1] = {value};
332 sv_vm_call(js->vm, js, next, observer, next_args, 1, NULL, false);
333 }
334
335 if (is_special_object(subscription) && subscription_closed(js, subscription)) return js_mkundef();
336 }
337
338 ant_value_t complete = js_get(js, observer, "complete");
339 if (is_callable(complete)) sv_vm_call(js->vm, js, complete, observer, NULL, 0, NULL, false);
340
341 return js_mkundef();
342}
343
344static ant_value_t js_observable_of(ant_t *js, ant_value_t *args, int nargs) {
345 ant_value_t items = js_mkarr(js);
346 for (int i = 0; i < nargs; i++) js_arr_push(js, items, args[i]);
347
348 ant_value_t subscriber_func = js_heavy_mkfun(js, js_of_subscriber, items);
349 ant_value_t ctor_args[1] = {subscriber_func};
350
351 return js_observable_constructor(js, ctor_args, 1);
352}
353
354static ant_value_t js_from_delegating(ant_t *js, ant_value_t *args, int nargs) {
355 ant_value_t F = js_getcurrentfunc(js);
356
357 ant_value_t observable = js_get_slot(F, SLOT_DATA);
358 if (!is_special_object(observable)) return js_mkundef();
359
360 ant_value_t subscribe = js_get(js, observable, "subscribe");
361 if (is_callable(subscribe)) {
362 return sv_vm_call(js->vm, js, subscribe, observable, args, nargs, NULL, false);
363 }
364
365 return js_mkundef();
366}
367
368static ant_value_t js_from_iteration(ant_t *js, ant_value_t *args, int nargs) {
369 ant_value_t F = js_getcurrentfunc(js);
370 ant_value_t data = js_get_slot(F, SLOT_DATA);
371
372 ant_value_t iterable = js_get(js, data, "iterable");
373 ant_value_t iteratorMethod = js_get(js, data, "iteratorMethod");
374
375 if (nargs < 1) return js_mkundef();
376
377 ant_value_t observer = args[0];
378 ant_value_t subscription = js_get_slot(observer, SLOT_DATA);
379
380 if (!is_callable(iteratorMethod)) {
381 return js_mkerr_typed(js, JS_ERR_TYPE, "Object is not iterable");
382 }
383
384 ant_value_t iterator = sv_vm_call(js->vm, js, iteratorMethod, iterable, NULL, 0, NULL, false);
385 if (!is_special_object(iterator)) {
386 return js_mkerr_typed(js, JS_ERR_TYPE, "Iterator must return an object");
387 }
388
389 ant_value_t nextMethod = js_getprop_fallback(js, iterator, "next");
390 if (!is_callable(nextMethod)) {
391 return js_mkerr_typed(js, JS_ERR_TYPE, "Iterator must have a next method");
392 }
393
394 while (true) {
395 ant_value_t next = sv_vm_call(js->vm, js, nextMethod, iterator, NULL, 0, NULL, false);
396 if (vtype(next) == T_ERR) return next;
397
398 ant_value_t done = js_get(js, next, "done");
399 if (js_truthy(js, done)) {
400 ant_value_t complete = js_get(js, observer, "complete");
401 if (is_callable(complete)) sv_vm_call(js->vm, js, complete, observer, NULL, 0, NULL, false);
402 return js_mkundef();
403 }
404
405 ant_value_t nextValue = js_get(js, next, "value");
406 ant_value_t obs_next = js_get(js, observer, "next");
407 if (is_callable(obs_next)) {
408 ant_value_t next_args[1] = {nextValue};
409 sv_vm_call(js->vm, js, obs_next, observer, next_args, 1, NULL, false);
410 }
411
412 if (is_special_object(subscription) && subscription_closed(js, subscription)) {
413 ant_value_t returnMethod = js_getprop_fallback(js, iterator, "return");
414 if (is_callable(returnMethod)) sv_vm_call(js->vm, js, returnMethod, iterator, NULL, 0, NULL, false);
415 return js_mkundef();
416 }
417 }
418}
419
420static ant_value_t js_observable_from(ant_t *js, ant_value_t *args, int nargs) {
421 if (nargs < 1) return js_mkerr_typed(js, JS_ERR_TYPE, "Observable.from requires an argument");
422 ant_value_t x = args[0];
423
424 if (vtype(x) == T_NULL || vtype(x) == T_UNDEF) {
425 return js_mkerr_typed(js, JS_ERR_TYPE, "Cannot convert null or undefined to observable");
426 }
427
428 ant_value_t observableMethod = js_get_sym(js, x, get_observable_sym());
429
430 if (is_callable(observableMethod)) {
431 ant_value_t observable = sv_vm_call(js->vm, js, observableMethod, x, NULL, 0, NULL, false);
432
433 if (!is_special_object(observable)) {
434 return js_mkerr_typed(js, JS_ERR_TYPE, "@@observable must return an object");
435 }
436
437 ant_value_t existing_subscriber = js_get_slot(observable, SLOT_OBSERVABLE_SUBSCRIBER);
438 if (is_callable(existing_subscriber)) return observable;
439
440 ant_value_t subscriber_func = js_heavy_mkfun(js, js_from_delegating, observable);
441 ant_value_t ctor_args[1] = {subscriber_func};
442
443 return js_observable_constructor(js, ctor_args, 1);
444 }
445
446 ant_value_t iteratorMethod = js_get_sym(js, x, get_iterator_sym());
447 if (!is_callable(iteratorMethod)) return js_mkerr_typed(js, JS_ERR_TYPE, "Object is not observable or iterable");
448
449 ant_value_t data = js_mkobj(js);
450 js_set(js, data, "iterable", x);
451 js_set(js, data, "iteratorMethod", iteratorMethod);
452
453 ant_value_t subscriber_func = js_heavy_mkfun(js, js_from_iteration, data);
454 ant_value_t ctor_args[1] = {subscriber_func};
455
456 return js_observable_constructor(js, ctor_args, 1);
457}
458
459void init_observable_module(void) {
460 ant_t *js = rt->js;
461 ant_value_t global = js_glob(js);
462
463 ant_value_t observable_ctor = js_mkobj(js);
464 ant_value_t observable_proto = js_mkobj(js);
465
466 js_set(js, observable_proto, "subscribe", js_mkfun(js_observable_subscribe));
467 js_set_sym(js, observable_proto, get_observable_sym(), js_mkfun(js_observable_symbol_observable));
468 js_set_sym(js, observable_proto, get_toStringTag_sym(), js_mkstr(js, "Observable", 10));
469
470 js_set_slot(observable_ctor, SLOT_CFUNC, js_mkfun(js_observable_constructor));
471 js_mkprop_fast(js, observable_ctor, "prototype", 9, observable_proto);
472 js_mkprop_fast(js, observable_ctor, "name", 4, ANT_STRING("Observable"));
473 js_set_descriptor(js, observable_ctor, "name", 4, 0);
474 js_set(js, observable_ctor, "of", js_mkfun(js_observable_of));
475 js_set(js, observable_ctor, "from", js_mkfun(js_observable_from));
476
477 ant_value_t Observable = js_obj_to_func(observable_ctor);
478 js_set(js, observable_proto, "constructor", Observable);
479 js_set(js, global, "Observable", Observable);
480}