Something went wrong. Try again.
javascript for 🐜's, a tiny runtime with big ambitions antjs.org
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626#include <stdlib.h>#include <string.h>
#include "ant.h"#include "cfunc.h"#include "ptr.h"#include "errors.h"#include "internal.h"#include "interrupt.h"#include "sugar.h"
#include "gc/roots.h"#include "silver/call.h"#include "modules/generator.h"#include "modules/iterator.h"#include "modules/timer.h"
static constexpr uint32_t GENERATOR_NATIVE_TAG = Ant_NativeTag("GENR");
typedef enum { GEN_SUSPENDED_START = 0, GEN_SUSPENDED_YIELD = 1, GEN_EXECUTING = 2, GEN_COMPLETED = 3,} generator_state_t;
typedef struct generator_request { sv_resume_kind_t kind; ant_value_t value; ant_value_t promise; struct generator_request *next;} generator_request_t;
typedef struct generator_data { coroutine_t *coro; generator_state_t state; generator_request_t *queue_head; generator_request_t *queue_tail; bool is_async;} generator_data_t;
static ant_value_t generator_resume_kind( ant_t *js, ant_value_t gen, ant_value_t resume_value, sv_resume_kind_t resume_kind);
static ant_value_t generator_result(ant_t *js, bool done, ant_value_t value) { return Ant_Iterator_CreateResult(js, value, done);}
static generator_data_t *generator_data(ant_value_t gen) { return (generator_data_t *)js_get_native(gen, GENERATOR_NATIVE_TAG);}
static generator_state_t generator_state(ant_value_t gen) { generator_data_t *data = generator_data(gen); return data ? data->state : GEN_COMPLETED;}
static void generator_set_state(ant_value_t gen, generator_state_t state) { generator_data_t *data = generator_data(gen); if (data) data->state = state;}
static bool generator_is_async(ant_value_t gen) { generator_data_t *data = generator_data(gen); return data && data->is_async;}
static ant_value_t generator_async_wrap_result(ant_t *js, ant_value_t result) { if (js_terminating_error(js, result)) return result; ant_value_t promise = js_mkpromise(js); if (is_err(promise)) return promise;
if (is_err(result)) { ant_value_t reject_value = js_take_thrown(js, result); js_reject_promise(js, promise, reject_value); } else js_resolve_promise(js, promise, result);
return promise;}
static generator_request_t *generator_dequeue_request(generator_data_t *data) { if (!data || !data->queue_head) return NULL; generator_request_t *req = data->queue_head; data->queue_head = req->next; if (!data->queue_head) data->queue_tail = NULL; req->next = NULL; return req;}
static void generator_free_queue(generator_data_t *data) { if (!data) return; generator_request_t *req = data->queue_head; while (req) { generator_request_t *next = req->next; free(req); req = next; } data->queue_head = NULL; data->queue_tail = NULL;}
static ant_value_t generator_enqueue_request( ant_t *js, ant_value_t gen, sv_resume_kind_t kind, ant_value_t value) { generator_data_t *data = generator_data(gen); if (!data || !data->is_async) return js_mkerr_typed(js, JS_ERR_TYPE, "Generator is already executing");
ant_value_t promise = js_mkpromise(js); if (is_err(promise)) return promise;
generator_request_t *req = (generator_request_t *)calloc(1, sizeof(*req)); if (!req) return js_mkerr(js, "out of memory for generator request");
*req = (generator_request_t){ .kind = kind, .value = value, .promise = promise, .next = NULL, };
if (data->queue_tail) data->queue_tail->next = req; else data->queue_head = req; data->queue_tail = req; return promise;}
static void generator_settle_request_promise(ant_t *js, ant_value_t promise, ant_value_t result) { if (is_err(result)) { ant_value_t reject_value = js_take_thrown(js, result); js_reject_promise(js, promise, reject_value); } else js_resolve_promise(js, promise, result);}
static void generator_process_queue(ant_t *js, ant_value_t gen) { generator_data_t *data = generator_data(gen); if (!data || !data->is_async) return;
GC_ROOT_SAVE(root_mark, js); GC_ROOT_PIN(js, gen);
while (data->state != GEN_EXECUTING && data->queue_head) { generator_request_t *req = generator_dequeue_request(data); if (!req) break; GC_ROOT_PIN(js, req->value); GC_ROOT_PIN(js, req->promise); coroutine_t *coro = data->coro; if (coro) coro->async_promise = req->promise; ant_value_t result = generator_resume_kind(js, gen, req->value, req->kind); GC_ROOT_PIN(js, result); if (vtype(result) != kTypePromise || result != req->promise) { generator_settle_request_promise(js, req->promise, result); js_maybe_drain_microtasks_after_async_settle(js); } free(req); data = generator_data(gen); if (!data) break; }
GC_ROOT_RESTORE(js, root_mark);}
static coroutine_t *generator_coro(ant_value_t gen) { generator_data_t *data = generator_data(gen); return data ? data->coro : NULL;}
static void generator_clear_coro(ant_value_t gen, coroutine_t *coro) { generator_data_t *data = generator_data(gen); if (data && data->coro == coro) data->coro = NULL; if (coro) { coro->owner_gen = js_mkundef(); coroutine_unhold(coro, CORO_HOLD_GENERATOR); }}
// TODO: collapsecoroutine_t *generator_get_coro_for_gc(ant_value_t gen) { return generator_coro(gen);}
void generator_mark_for_gc(ant_t *js, ant_value_t gen) { generator_data_t *data = generator_data(gen); if (!data) return; for (generator_request_t *req = data->queue_head; req; req = req->next) { gc_mark_value(js, req->value); gc_mark_value(js, req->promise); }}
void Ant_Generator_SettleYield(ant_t *js, ant_value_t gen, ant_value_t value) { coroutine_t *coro = generator_coro(gen); if (!coro || vtype(coro->async_promise) != kTypePromise) return;
GC_ROOT_SAVE(root_mark, js); GC_ROOT_PIN(js, gen); ant_value_t pending = coro->async_promise; GC_ROOT_PIN(js, pending); ant_value_t out = generator_result(js, false, value); GC_ROOT_PIN(js, out);
coro->async_promise = js_mkundef(); generator_set_state(gen, GEN_SUSPENDED_YIELD); js_resolve_promise(js, pending, out); js_maybe_drain_microtasks_after_async_settle(js); generator_process_queue(js, gen);
GC_ROOT_RESTORE(js, root_mark);}
static void generator_queue_yield_tick(ant_t *js, ant_value_t gen, coroutine_t *coro, ant_value_t value) { coro->yield_tick = false; generator_set_state(gen, GEN_EXECUTING); if (!queue_generator_yield_job(js, gen, value)) Ant_Generator_SettleYield(js, gen, value);}
bool generator_resume_pending_request(ant_t *js, coroutine_t *coro, ant_value_t result) { if (!coro || !coro->is_generator || vtype(coro->async_promise) != kTypePromise) return false;
ant_value_t gen = coro->owner_gen; generator_data_t *data = generator_data(gen); if (!data || !data->is_async) return false;
GC_ROOT_SAVE(root_mark, js); GC_ROOT_PIN(js, gen); GC_ROOT_PIN(js, result);
ant_value_t pending = coro->async_promise; GC_ROOT_PIN(js, pending);
if (is_err(result)) { ant_value_t reject_value = js_take_thrown(js, result); GC_ROOT_PIN(js, reject_value); coro->async_promise = js_mkundef(); generator_set_state(gen, GEN_COMPLETED); generator_clear_coro(gen, coro); js_reject_promise(js, pending, reject_value); js_maybe_drain_microtasks_after_async_settle(js); generator_process_queue(js, gen); GC_ROOT_RESTORE(js, root_mark); return true; }
if (coro->act && coro->act->frame_count > 0) { if (coro->await_registered) { generator_set_state(gen, GEN_EXECUTING); GC_ROOT_RESTORE(js, root_mark); return true; }
if (coro->yield_tick) { generator_queue_yield_tick(js, gen, coro, result); GC_ROOT_RESTORE(js, root_mark); return true; }
ant_value_t out = generator_result(js, false, result); GC_ROOT_PIN(js, out); coro->async_promise = js_mkundef(); generator_set_state(gen, GEN_SUSPENDED_YIELD); js_resolve_promise(js, pending, out); js_maybe_drain_microtasks_after_async_settle(js); generator_process_queue(js, gen); GC_ROOT_RESTORE(js, root_mark); return true; }
ant_value_t out = generator_result(js, true, result); GC_ROOT_PIN(js, out); coro->async_promise = js_mkundef(); generator_set_state(gen, GEN_COMPLETED); generator_clear_coro(gen, coro); js_resolve_promise(js, pending, out); js_maybe_drain_microtasks_after_async_settle(js); generator_process_queue(js, gen); GC_ROOT_RESTORE(js, root_mark); return true;}
static void generator_finalize(ant_t *js, ant_object_t *obj) { ant_value_t gen = js_obj_from_ptr(obj); generator_data_t *data = (generator_data_t *)js_get_native(gen, GENERATOR_NATIVE_TAG); if (!data) return; if (data->coro) generator_clear_coro(gen, data->coro); js_clear_native(gen, GENERATOR_NATIVE_TAG); generator_free_queue(data); free(data);}
static ant_value_t generator_resume_kind_inner( ant_t *js, ant_value_t gen, ant_value_t resume_value, sv_resume_kind_t resume_kind) { GC_ROOT_SAVE(root_mark, js); GC_ROOT_PIN(js, gen); GC_ROOT_PIN(js, resume_value); coroutine_t *coro = generator_coro(gen);
if (!coro || (!coro->act && generator_state(gen) != GEN_SUSPENDED_START)) { generator_set_state(gen, GEN_COMPLETED); if (resume_kind == SV_RESUME_THROW) { GC_ROOT_RESTORE(js, root_mark); return js_throw(js, resume_value); } ant_value_t out = generator_result( js, true, resume_kind == SV_RESUME_RETURN ? resume_value : js_mkundef() ); GC_ROOT_RESTORE(js, root_mark); return out; }
generator_state_t state = generator_state(gen); if (state == GEN_EXECUTING) { ant_value_t queued = generator_enqueue_request(js, gen, resume_kind, resume_value); GC_ROOT_RESTORE(js, root_mark); return queued; } if (state == GEN_COMPLETED) { if (resume_kind == SV_RESUME_THROW) { GC_ROOT_RESTORE(js, root_mark); return js_throw(js, resume_value); } ant_value_t out = generator_result( js, true, resume_kind == SV_RESUME_RETURN ? resume_value : js_mkundef() ); GC_ROOT_RESTORE(js, root_mark); return out; } if (state == GEN_SUSPENDED_START && resume_kind == SV_RESUME_THROW) { generator_set_state(gen, GEN_COMPLETED); generator_clear_coro(gen, coro); GC_ROOT_RESTORE(js, root_mark); return js_throw(js, resume_value); } if (state == GEN_SUSPENDED_START && resume_kind == SV_RESUME_RETURN) { generator_set_state(gen, GEN_COMPLETED); generator_clear_coro(gen, coro); ant_value_t out = generator_result(js, true, resume_value); GC_ROOT_RESTORE(js, root_mark); return out; }
generator_set_state(gen, GEN_EXECUTING); coroutine_t *saved_active = js->active_async_coro; coro->active_parent = saved_active; coro->active_prev = NULL; if (saved_active) saved_active->active_prev = coro; js->active_async_coro = coro; coroutine_hold(coro, CORO_HOLD_ACTIVE);
sv_vm_t *exec_vm = js->vm; ant_value_t result; if (state == GEN_SUSPENDED_START) { sv_closure_t *closure = (vtype(coro->async_func) == kTypeFunction) ? js_func_closure(coro->async_func) : NULL; if (!closure || !closure->func) result = js_mkerr(js, "invalid generator function"); else result = sv_execute_closure_entry( exec_vm, closure, coro->async_func, coro->super_val, coro->new_target, coro->this_val, coro->args, coro->nargs, NULL ); } else if (coro->act && sv_activation_install(exec_vm, coro->act)) { exec_vm->suspend.value = resume_value; exec_vm->suspend.is_error = (resume_kind == SV_RESUME_THROW); exec_vm->suspend.kind = resume_kind; exec_vm->suspend.pending = true; result = sv_resume_suspended(exec_vm); } else result = js_mkerr(js, "generator activation lost");
bool suspended_now = true; bool yielded_here = exec_vm->suspend.active && exec_vm->suspend.entry_fp >= 0; if (yielded_here) { sv_activation_t *act = sv_activation_capture(exec_vm, exec_vm->suspend.entry_fp, coro->act); if (act) { coro->act = act; gc_remember_coroutine(js, coro); } else { sv_activation_discard(exec_vm, exec_vm->suspend.entry_fp); suspended_now = false; result = js_mkerr(js, "out of memory capturing generator activation"); } } if (suspended_now) suspended_now = coro->act && coro->act->frame_count > 0; GC_ROOT_PIN(js, result); js->active_async_coro = saved_active; if (saved_active) saved_active->active_prev = NULL; coro->active_parent = NULL; coro->active_prev = NULL; coroutine_unhold(coro, CORO_HOLD_ACTIVE);
if (is_err(result)) { generator_set_state(gen, GEN_COMPLETED); generator_clear_coro(gen, coro); GC_ROOT_RESTORE(js, root_mark); return result; }
if (suspended_now) { if (generator_is_async(gen) && (coro->await_registered || coro->yield_tick)) { generator_set_state(gen, GEN_EXECUTING); if (vtype(coro->async_promise) != kTypePromise) { coro->async_promise = js_mkpromise(js); GC_ROOT_PIN(js, coro->async_promise); } if (coro->yield_tick) generator_queue_yield_tick(js, gen, coro, result); ant_value_t out = coro->async_promise; GC_ROOT_RESTORE(js, root_mark); return out; } generator_set_state(gen, GEN_SUSPENDED_YIELD); ant_value_t out = generator_result(js, false, result); GC_ROOT_RESTORE(js, root_mark); return out; }
generator_set_state(gen, GEN_COMPLETED); generator_clear_coro(gen, coro); ant_value_t out = generator_result(js, true, result); GC_ROOT_RESTORE(js, root_mark); return out;}
static ant_value_t generator_resume_kind( ant_t *js, ant_value_t gen, ant_value_t resume_value, sv_resume_kind_t resume_kind) { coroutine_t *coro = generator_coro(gen); if (coro) coroutine_retain(coro); ant_value_t result = generator_resume_kind_inner(js, gen, resume_value, resume_kind); if (coro) coroutine_release(coro); return result;}
static ant_value_t generator_resume(ant_t *js, ant_value_t gen, ant_value_t resume_value) { return generator_resume_kind(js, gen, resume_value, SV_RESUME_NEXT);}
static ant_value_t generator_next(ant_params_t) { ant_value_t gen = js->this_val; if (vtype(gen) != kTypeGenerator) return js_mkerr_typed(js, JS_ERR_TYPE, "Generator.prototype.next called on incompatible receiver"); ant_value_t resume_value = nargs > 0 ? args[0] : js_mkundef(); ant_value_t result = generator_resume(js, gen, resume_value); if (generator_is_async(gen) && vtype(result) == kTypePromise) return result; return generator_is_async(gen) ? generator_async_wrap_result(js, result) : result;}
static ant_value_t generator_return(ant_params_t) { ant_value_t gen = js->this_val; if (vtype(gen) != kTypeGenerator) return js_mkerr_typed(js, JS_ERR_TYPE, "Generator.prototype.return called on incompatible receiver");
generator_state_t state = generator_state(gen); if (state == GEN_EXECUTING && !generator_is_async(gen)) return js_mkerr_typed(js, JS_ERR_TYPE, "Generator is already executing"); ant_value_t value = nargs > 0 ? args[0] : js_mkundef(); ant_value_t result = generator_resume_kind(js, gen, value, SV_RESUME_RETURN); if (generator_is_async(gen) && vtype(result) == kTypePromise) return result; return generator_is_async(gen) ? generator_async_wrap_result(js, result) : result;}
static ant_value_t generator_throw(ant_params_t) { ant_value_t gen = js->this_val; if (vtype(gen) != kTypeGenerator) return js_mkerr_typed(js, JS_ERR_TYPE, "Generator.prototype.throw called on incompatible receiver"); generator_state_t state = generator_state(gen); if (state == GEN_EXECUTING && !generator_is_async(gen)) return js_mkerr_typed(js, JS_ERR_TYPE, "Generator is already executing"); ant_value_t value = nargs > 0 ? args[0] : js_mkundef(); ant_value_t result = generator_resume_kind(js, gen, value, SV_RESUME_THROW); if (generator_is_async(gen) && vtype(result) == kTypePromise) return result; return generator_is_async(gen) ? generator_async_wrap_result(js, result) : result;}
void init_generator_module(ant_t *js) { ant_value_t generator_func_proto = js_get_slot(js->global, SLOT_GENERATOR_PROTO); ant_value_t async_generator_func_proto = js_get_slot(js->global, SLOT_ASYNC_GENERATOR_PROTO); mkprop( js, generator_func_proto, js->sym.toStringTag_sym, ANT_STRING("GeneratorFunction"), ANT_PROP_ATTR_CONFIGURABLE); mkprop( js, async_generator_func_proto, js->sym.toStringTag_sym, ANT_STRING("AsyncGeneratorFunction"), ANT_PROP_ATTR_CONFIGURABLE);
ant_value_t proto = js->sym.generator_proto; js_set_proto_wb(js, proto, js->sym.iterator_proto); defmethod(js, proto, "next", 4, Ant_Function_CreateWithArity(generator_next, 1)); defmethod(js, proto, "return", 6, Ant_Function_CreateWithArity(generator_return, 1)); defmethod(js, proto, "throw", 5, Ant_Function_CreateWithArity(generator_throw, 1)); mkprop(js, proto, js->sym.toStringTag_sym, ANT_STRING("Generator"), ANT_PROP_ATTR_CONFIGURABLE);
ant_value_t async_proto = js->sym.async_generator_proto; js_set_proto_wb(js, async_proto, js->sym.async_iterator_proto); defmethod(js, async_proto, "next", 4, Ant_Function_CreateWithArity(generator_next, 1)); defmethod(js, async_proto, "return", 6, Ant_Function_CreateWithArity(generator_return, 1)); defmethod(js, async_proto, "throw", 5, Ant_Function_CreateWithArity(generator_throw, 1)); mkprop(js, async_proto, js->sym.toStringTag_sym, ANT_STRING("AsyncGenerator"), ANT_PROP_ATTR_CONFIGURABLE);}
ant_value_t sv_call_generator_closure_dispatch( sv_vm_t *caller_vm, ant_t *js, sv_closure_t *closure, ant_value_t callee_func, ant_value_t super_val, ant_value_t new_target, ant_value_t this_val, ant_value_t *args, int argc) { if (!closure || !closure->func) return js_mkerr(js, "invalid generator function"); coroutine_t *coro = (coroutine_t *)calloc(1, sizeof(coroutine_t)); if (!coro) return js_mkerr(js, "out of memory for generator");
ant_value_t *copied_args = NULL; if (argc > 0 && args) { copied_args = (ant_value_t *)calloc(1, sizeof(ant_value_t) * (size_t)argc); if (!copied_args) { free(coro); return js_mkerr(js, "out of memory for generator args"); } memcpy(copied_args, args, sizeof(ant_value_t) * (size_t)argc); }
ant_value_t gen = js_mkgenerator(js); if (is_err(gen)) { if (copied_args) free(copied_args); free(coro); return gen; }
generator_data_t *data = (generator_data_t *)calloc(1, sizeof(*data)); if (!data) { if (copied_args) free(copied_args); free(coro); return js_mkerr(js, "out of memory for generator data"); }
*coro = (coroutine_t){ .js = js, .is_generator = true, .this_val = this_val, .super_val = super_val, .new_target = new_target, .awaited_promise = js_mkundef(), .async_func = callee_func, .owner_gen = gen, .args = copied_args, .nargs = argc, .active_parent = NULL, .async_promise = js_mkundef(), .refcount = 1, .hold_bits = 0, .await_registered = false, };
*data = (generator_data_t){ .coro = coro, .state = GEN_SUSPENDED_START, .is_async = closure->func->is_async, }; coroutine_hold(coro, CORO_HOLD_GENERATOR); coroutine_release(coro);
js_set_native(gen, data, GENERATOR_NATIVE_TAG); js_set_finalizer(gen, generator_finalize);
ant_value_t prototype_target = js_resolve_bound_target(callee_func); ant_value_t instance_proto = js_get(js, prototype_target, "prototype"); if (is_object_type(instance_proto)) js_set_proto_wb(js, gen, instance_proto); else if (data->is_async && is_object_type(js->sym.async_generator_proto)) js_set_proto_wb(js, gen, js->sym.async_generator_proto);
return gen;}