Something went wrong. Try again.
javascript for 🐜's, a tiny runtime with big ambitions antjs.org
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497#include <stdlib.h>#include <stdio.h>#include <string.h>
#include "errors.h"#include "cfunc.h"#include "internal.h"#include "silver/call.h"#include "descriptors.h"#include "gc/roots.h"
#include "modules/observable.h"
static void observable_report_error(ant_t *js, ant_value_t result, const char *where) { GC_ROOT_SAVE(root_mark, js); ant_value_t reason = js_take_thrown(js, result);
GC_ROOT_PIN(js, reason); char prefix[80]; snprintf(prefix, sizeof(prefix), "Error in %s: ", where); print_error_value(js, reason, js_mkundef(), prefix);
if (Ant_Exception_Pending(js)) js_take_thrown(js, js_mkundef()); GC_ROOT_RESTORE(js, root_mark);}
static bool subscription_closed(ant_t *js, ant_value_t subscription) { ant_value_t observer = js_get_slot(subscription, SLOT_SUBSCRIPTION_OBSERVER); return vtype(observer) == kTypeUndefined;}
static void cleanup_subscription(ant_t *js, ant_value_t subscription) { ant_value_t cleanup = js_get_slot(subscription, SLOT_SUBSCRIPTION_CLEANUP); if (vtype(cleanup) == kTypeUndefined) return; if (!is_callable(cleanup)) return; js_set_slot(subscription, SLOT_SUBSCRIPTION_CLEANUP, js_mkundef()); ant_value_t result = sv_vm_call(js->vm, js, cleanup, js_mkundef(), NULL, 0, NULL, js_mkundef()); if (is_err(result)) observable_report_error(js, result, "subscription cleanup");}
static ant_value_t create_subscription(ant_t *js, ant_value_t observer) { ant_value_t subscription = Ant_Object_CreateBare(js); js_set_slot(subscription, SLOT_SUBSCRIPTION_OBSERVER, observer); js_set_slot(subscription, SLOT_SUBSCRIPTION_CLEANUP, js_mkundef()); js_set_tostringtag(js, subscription, js_mkstr(js, "Subscription", 12)); return subscription;}
static ant_value_t js_subscription_get_closed(ant_params_t) { (void)args; (void)nargs; ant_value_t subscription = js_getthis(js); if (!is_special_object(subscription)) { return js_mkerr_typed(js, JS_ERR_TYPE, "Subscription.closed getter called on non-object"); } return js_bool(subscription_closed(js, subscription));}
static ant_value_t js_subscription_unsubscribe(ant_params_t) { (void)args; (void)nargs; ant_value_t subscription = js_getthis(js); if (!is_special_object(subscription)) { return js_mkerr_typed(js, JS_ERR_TYPE, "Subscription.unsubscribe called on non-object"); } if (subscription_closed(js, subscription)) return js_mkundef(); js_set_slot(subscription, SLOT_SUBSCRIPTION_OBSERVER, js_mkundef()); cleanup_subscription(js, subscription); return js_mkundef();}
static void setup_subscription_methods(ant_t *js, ant_value_t subscription) { js_set(js, subscription, "unsubscribe", Ant_Function_Create(js_subscription_unsubscribe)); ant_value_t closed_getter = Ant_Function_Create(js_subscription_get_closed); js_set_getter_desc(js, subscription, "closed", 6, closed_getter, JS_DESC_E | JS_DESC_C);}
static ant_value_t js_subobs_get_closed(ant_params_t) { (void)args; (void)nargs; ant_value_t O = js_getthis(js); if (!is_special_object(O)) { return js_mkerr_typed(js, JS_ERR_TYPE, "SubscriptionObserver.closed getter called on non-object"); } ant_value_t subscription = js_get_slot(O, SLOT_DATA); if (!is_special_object(subscription)) { return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid SubscriptionObserver"); } return js_bool(subscription_closed(js, subscription));}
static ant_value_t js_subobs_next(ant_params_t) { ant_value_t O = js_getthis(js); if (!is_special_object(O)) return js_mkerr_typed(js, JS_ERR_TYPE, "SubscriptionObserver.next called on non-object"); ant_value_t subscription = js_get_slot(O, SLOT_DATA); if (!is_special_object(subscription)) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid SubscriptionObserver"); if (subscription_closed(js, subscription)) return js_mkundef(); ant_value_t observer = js_get_slot(subscription, SLOT_SUBSCRIPTION_OBSERVER); if (!is_special_object(observer)) return js_mkundef(); ant_value_t nextMethod = js_get(js, observer, "next"); if (is_err(nextMethod)) { observable_report_error(js, nextMethod, "observer.next"); return js_mkundef(); }
if (is_callable(nextMethod)) { ant_value_t value = (nargs > 0) ? args[0] : js_mkundef(); ant_value_t call_args[1] = {value}; ant_value_t result = sv_vm_call(js->vm, js, nextMethod, observer, call_args, 1, NULL, js_mkundef()); if (is_err(result)) observable_report_error(js, result, "observer.next"); } return js_mkundef();}
static ant_value_t js_subobs_error(ant_params_t) { ant_value_t O = js_getthis(js); if (!is_special_object(O)) { return js_mkerr_typed(js, JS_ERR_TYPE, "SubscriptionObserver.error called on non-object"); } ant_value_t subscription = js_get_slot(O, SLOT_DATA); if (!is_special_object(subscription)) { return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid SubscriptionObserver"); } if (subscription_closed(js, subscription)) return js_mkundef(); ant_value_t observer = js_get_slot(subscription, SLOT_SUBSCRIPTION_OBSERVER); js_set_slot(subscription, SLOT_SUBSCRIPTION_OBSERVER, js_mkundef()); if (is_special_object(observer)) { ant_value_t errorMethod = js_get(js, observer, "error"); if (is_err(errorMethod)) observable_report_error(js, errorMethod, "observer.error"); if (is_callable(errorMethod)) { ant_value_t exception = (nargs > 0) ? args[0] : js_mkundef(); ant_value_t call_args[1] = {exception}; ant_value_t result = sv_vm_call(js->vm, js, errorMethod, observer, call_args, 1, NULL, js_mkundef()); if (is_err(result)) observable_report_error(js, result, "observer.error"); } } cleanup_subscription(js, subscription); return js_mkundef();}
static ant_value_t js_subobs_complete(ant_params_t) { (void)args; (void)nargs; ant_value_t O = js_getthis(js); if (!is_special_object(O)) { return js_mkerr_typed(js, JS_ERR_TYPE, "SubscriptionObserver.complete called on non-object"); } ant_value_t subscription = js_get_slot(O, SLOT_DATA); if (!is_special_object(subscription)) { return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid SubscriptionObserver"); } if (subscription_closed(js, subscription)) return js_mkundef(); ant_value_t observer = js_get_slot(subscription, SLOT_SUBSCRIPTION_OBSERVER); js_set_slot(subscription, SLOT_SUBSCRIPTION_OBSERVER, js_mkundef()); if (is_special_object(observer)) { ant_value_t completeMethod = js_get(js, observer, "complete"); if (is_err(completeMethod)) observable_report_error(js, completeMethod, "observer.complete"); if (is_callable(completeMethod)) { ant_value_t result = sv_vm_call(js->vm, js, completeMethod, observer, NULL, 0, NULL, js_mkundef()); if (is_err(result)) observable_report_error(js, result, "observer.complete"); } } cleanup_subscription(js, subscription); return js_mkundef();}
static ant_value_t create_subscription_observer(ant_t *js, ant_value_t subscription) { ant_value_t subobs = Ant_Object_CreateBare(js); js_set_slot(subobs, SLOT_DATA, subscription); js_set(js, subobs, "next", Ant_Function_Create(js_subobs_next)); js_set(js, subobs, "error", Ant_Function_Create(js_subobs_error)); js_set(js, subobs, "complete", Ant_Function_Create(js_subobs_complete)); js_set_tostringtag(js, subobs, js_mkstr(js, "SubscriptionObserver", 20)); ant_value_t closed_getter = Ant_Function_Create(js_subobs_get_closed); js_set_getter_desc(js, subobs, "closed", 6, closed_getter, JS_DESC_E | JS_DESC_C); return subobs;}
static ant_value_t js_cleanup_fn(ant_params_t) { (void)args; (void)nargs; ant_value_t F = js_getcurrentfunc(js); ant_value_t subscription = js_get_slot(F, SLOT_DATA); if (!is_special_object(subscription)) return js_mkundef(); ant_value_t unsubscribe = js_get(js, subscription, "unsubscribe"); if (is_callable(unsubscribe)) { return sv_vm_call(js->vm, js, unsubscribe, subscription, NULL, 0, NULL, js_mkundef()); } return js_mkundef();}
static ant_value_t execute_subscriber(ant_t *js, ant_value_t subscriber, ant_value_t observer) { ant_value_t call_args[1] = {observer}; ant_value_t subscriberResult = sv_vm_call(js->vm, js, subscriber, js_mkundef(), call_args, 1, NULL, js_mkundef()); if (vtype(subscriberResult) == kTypeError) return subscriberResult; if (vtype(subscriberResult) == kTypeNull || vtype(subscriberResult) == kTypeUndefined) return js_mkundef(); if (is_callable(subscriberResult)) return subscriberResult; if (is_special_object(subscriberResult)) { ant_value_t result = js_get(js, subscriberResult, "unsubscribe"); if (is_err(result)) return result;
if (vtype(result) == kTypeUndefined) return js_mkerr_typed(js, JS_ERR_TYPE, "Subscriber return value must have an unsubscribe method"); ant_value_t cleanupFunction = Ant_Object_CreateBare(js); js_set_slot(cleanupFunction, SLOT_DATA, subscriberResult); js_set_slot(cleanupFunction, SLOT_CFUNC, Ant_Function_Create(js_cleanup_fn));
return js_obj_to_func(js, cleanupFunction); } return js_mkerr_typed(js, JS_ERR_TYPE, "Subscriber must return a function, an object with unsubscribe, or undefined");}
static ant_value_t js_observable_subscribe(ant_params_t) { ant_value_t O = js_getthis(js); if (!is_special_object(O)) { return js_mkerr_typed(js, JS_ERR_TYPE, "Observable.prototype.subscribe called on non-object"); } ant_value_t subscriber = js_get_slot(O, SLOT_OBSERVABLE_SUBSCRIBER); if (!is_callable(subscriber)) { return js_mkerr_typed(js, JS_ERR_TYPE, "Observable has no [[Subscriber]] internal slot"); } ant_value_t observer; if (nargs > 0 && is_callable(args[0])) { ant_value_t nextCallback = args[0]; ant_value_t errorCallback = (nargs > 1) ? args[1] : js_mkundef(); ant_value_t completeCallback = (nargs > 2) ? args[2] : js_mkundef(); observer = Ant_Object_CreateBare(js); js_set(js, observer, "next", nextCallback); js_set(js, observer, "error", errorCallback); js_set(js, observer, "complete", completeCallback); } else if (nargs > 0 && is_special_object(args[0])) { observer = args[0]; } else observer = Ant_Object_CreateBare(js); ant_value_t subscription = create_subscription(js, observer); setup_subscription_methods(js, subscription); ant_value_t start = js_get(js, observer, "start"); if (is_err(start)) observable_report_error(js, start, "observer.start"); if (is_callable(start)) { ant_value_t start_args[1] = {subscription}; ant_value_t result = sv_vm_call(js->vm, js, start, observer, start_args, 1, NULL, js_mkundef()); if (vtype(result) == kTypeError) observable_report_error(js, result, "observer.start"); if (subscription_closed(js, subscription)) return subscription; } ant_value_t subscriptionObserver = create_subscription_observer(js, subscription); ant_value_t subscriberResult = execute_subscriber(js, subscriber, subscriptionObserver); if (vtype(subscriberResult) == kTypeError) { GC_ROOT_SAVE(root_mark, js); ant_value_t thrown_error = js_take_thrown(js, subscriberResult); GC_ROOT_PIN(js, thrown_error); ant_value_t error_args[1] = {thrown_error}; ant_value_t error_method = js_get(js, subscriptionObserver, "error"); if (is_callable(error_method)) sv_vm_call(js->vm, js, error_method, subscriptionObserver, error_args, 1, NULL, js_mkundef()); GC_ROOT_RESTORE(js, root_mark); } else js_set_slot_wb(js, subscription, SLOT_SUBSCRIPTION_CLEANUP, subscriberResult); if (subscription_closed(js, subscription)) cleanup_subscription(js, subscription); return subscription;}
static ant_value_t js_observable_symbol_observable(ant_params_t) { (void)args; (void)nargs; return js_getthis(js);}
static ant_value_t js_observable_constructor(ant_params_t) { if (nargs < 1) { return js_mkerr_typed(js, JS_ERR_TYPE, "Observable constructor requires a subscriber function"); } ant_value_t subscriber = args[0]; if (!is_callable(subscriber)) { return js_mkerr_typed(js, JS_ERR_TYPE, "Observable subscriber must be a function"); } ant_value_t proto = js->builtins.observable_proto; ant_value_t observable = Ant_Object_CreateBare(js); js_set_proto_init(observable, proto); js_set_slot(observable, SLOT_OBSERVABLE_SUBSCRIBER, subscriber); return observable;}
static ant_value_t js_of_subscriber(ant_params_t) { ant_value_t F = js_getcurrentfunc(js); ant_value_t items = js_get_slot(F, SLOT_DATA); if (nargs < 1) return js_mkundef(); ant_value_t observer = args[0]; ant_value_t subscription = js_get_slot(observer, SLOT_DATA); ant_value_t length_val = js_get(js, items, "length"); int length = (vtype(length_val) == kTypeNumber) ? (int)js_getnum(length_val) : 0; for (int i = 0; i < length; i++) { char key[16]; snprintf(key, sizeof(key), "%d", i); ant_value_t value = js_get(js, items, key); ant_value_t next = js_get(js, observer, "next"); if (is_callable(next)) { ant_value_t next_args[1] = {value}; sv_vm_call(js->vm, js, next, observer, next_args, 1, NULL, js_mkundef()); } if (is_special_object(subscription) && subscription_closed(js, subscription)) return js_mkundef(); } ant_value_t complete = js_get(js, observer, "complete"); if (is_callable(complete)) sv_vm_call(js->vm, js, complete, observer, NULL, 0, NULL, js_mkundef()); return js_mkundef();}
static ant_value_t js_observable_of(ant_params_t) { ant_value_t items = js_mkarr(js); for (int i = 0; i < nargs; i++) js_arr_push(js, items, args[i]); ant_value_t subscriber_func = js_heavy_mkfun(js, js_of_subscriber, items); ant_value_t ctor_args[1] = {subscriber_func}; return js_observable_constructor(js, ctor_args, 1, js_mkundef());}
static ant_value_t js_from_delegating(ant_params_t) { ant_value_t F = js_getcurrentfunc(js); ant_value_t observable = js_get_slot(F, SLOT_DATA); if (!is_special_object(observable)) return js_mkundef(); ant_value_t subscribe = js_get(js, observable, "subscribe"); if (is_callable(subscribe)) { return sv_vm_call(js->vm, js, subscribe, observable, args, nargs, NULL, js_mkundef()); } return js_mkundef();}
static ant_value_t js_from_iteration(ant_params_t) { ant_value_t F = js_getcurrentfunc(js); ant_value_t data = js_get_slot(F, SLOT_DATA); ant_value_t iterable = js_get(js, data, "iterable"); ant_value_t iteratorMethod = js_get(js, data, "iteratorMethod"); if (nargs < 1) return js_mkundef(); ant_value_t observer = args[0]; ant_value_t subscription = js_get_slot(observer, SLOT_DATA); if (!is_callable(iteratorMethod)) return js_mkerr_typed(js, JS_ERR_TYPE, "Object is not iterable"); ant_value_t iterator = sv_vm_call(js->vm, js, iteratorMethod, iterable, NULL, 0, NULL, js_mkundef()); if (is_err(iterator)) return iterator;
if (!is_special_object(iterator)) return js_mkerr_typed(js, JS_ERR_TYPE, "Iterator must return an object"); ant_value_t nextMethod = js_getprop_fallback(js, iterator, "next"); if (!is_callable(nextMethod)) { return js_mkerr_typed(js, JS_ERR_TYPE, "Iterator must have a next method"); } while (true) { ant_value_t next = sv_vm_call(js->vm, js, nextMethod, iterator, NULL, 0, NULL, js_mkundef()); if (vtype(next) == kTypeError) return next; ant_value_t done = js_get(js, next, "done"); if (js_truthy(js, done)) { ant_value_t complete = js_get(js, observer, "complete"); if (is_callable(complete)) sv_vm_call(js->vm, js, complete, observer, NULL, 0, NULL, js_mkundef()); return js_mkundef(); } ant_value_t nextValue = js_get(js, next, "value"); ant_value_t obs_next = js_get(js, observer, "next"); if (is_callable(obs_next)) { ant_value_t next_args[1] = {nextValue}; sv_vm_call(js->vm, js, obs_next, observer, next_args, 1, NULL, js_mkundef()); } if (is_special_object(subscription) && subscription_closed(js, subscription)) { ant_value_t returnMethod = js_getprop_fallback(js, iterator, "return"); if (is_callable(returnMethod)) sv_vm_call(js->vm, js, returnMethod, iterator, NULL, 0, NULL, js_mkundef()); return js_mkundef(); } }}
static ant_value_t js_observable_from(ant_params_t) { if (nargs < 1) return js_mkerr_typed(js, JS_ERR_TYPE, "Observable.from requires an argument"); ant_value_t x = args[0]; if (vtype(x) == kTypeNull || vtype(x) == kTypeUndefined) { return js_mkerr_typed(js, JS_ERR_TYPE, "Cannot convert null or undefined to observable"); } ant_value_t observableMethod = js_get_sym(js, x, js->sym.observable_sym); if (is_callable(observableMethod)) { ant_value_t observable = sv_vm_call(js->vm, js, observableMethod, x, NULL, 0, NULL, js_mkundef()); if (is_err(observable)) return observable; if (!is_special_object(observable)) { return js_mkerr_typed(js, JS_ERR_TYPE, "@@observable must return an object"); } ant_value_t existing_subscriber = js_get_slot(observable, SLOT_OBSERVABLE_SUBSCRIBER); if (is_callable(existing_subscriber)) return observable; ant_value_t subscriber_func = js_heavy_mkfun(js, js_from_delegating, observable); ant_value_t ctor_args[1] = {subscriber_func}; return js_observable_constructor(js, ctor_args, 1, js_mkundef()); } ant_value_t iteratorMethod = js_get_sym(js, x, js->sym.iterator_sym); if (!is_callable(iteratorMethod)) return js_mkerr_typed(js, JS_ERR_TYPE, "Object is not observable or iterable"); ant_value_t data = Ant_Object_CreateBare(js); js_set(js, data, "iterable", x); js_set(js, data, "iteratorMethod", iteratorMethod); ant_value_t subscriber_func = js_heavy_mkfun(js, js_from_iteration, data); ant_value_t ctor_args[1] = {subscriber_func}; return js_observable_constructor(js, ctor_args, 1, js_mkundef());}
void init_observable_module(ant_t *js) { ant_value_t observable_ctor = Ant_Object_CreateBare(js); ant_value_t observable_proto = Ant_Object_CreateBare(js); js->builtins.observable_proto = observable_proto; defmethod(js, observable_proto, "subscribe", 9, Ant_Function_Create(js_observable_subscribe)); defmethod_sym(js, observable_proto, js->sym.observable_sym, Ant_Function_Create(js_observable_symbol_observable)); js_set_tostringtag(js, observable_proto, js_mkstr(js, "Observable", 10)); js_set_slot(observable_ctor, SLOT_CFUNC, Ant_Function_CreateWithArity(js_observable_constructor, 1)); js_setprop_readonly_nonconfigurable(js, observable_ctor, "prototype", 9, observable_proto); js_set_function_name(js, observable_ctor, "Observable", sizeof("Observable") - 1); defmethod(js, observable_ctor, "of", 2, Ant_Function_Create(js_observable_of)); defmethod(js, observable_ctor, "from", 4, Ant_Function_Create(js_observable_from)); ant_value_t Observable = js_obj_to_func(js, observable_ctor); defhidden(js, observable_proto, "constructor", 11, Observable); js_set_global_builtin(js, "Observable", Observable);}