Run iterator helpers on untyped iterators

In JavaScript, a receiver without an inferred type, such as an untyped
parameter's xs.values(), is the runtime's checked iterator. Its only
method was next(), so xs.values().filter(f), .map(f) and the other
iterator helpers threw "TypeError: ... is not a function" where Node
runs them, and [].values().reduce(f) threw the same instead of Node's
"Reduce of a done iterator with no initial value".

The native array, string, Map and Set iterators now answer the
Iterator.prototype methods. map, filter, take, drop and flatMap make
helper objects (SCR_DYNH_ITERATOR_HELPER) that pull lazily and support
next(), return(), for...of and spreads, print as
"Object [Iterator Helper] {}" and tag as "[object Iterator Helper]".
toArray, forEach, reduce, some, every and find drive the iterator.

The helpers close the iterator they were made from where Node does:
when a callback throws, when take's budget is spent, on return(), and
before the error for an invalid argument. flatMap closes its inner
iterator first. An error from a close that runs because of another
error is dropped. Invalid arguments throw Node's messages, such as
"number 5 is not a function" and "x must be positive".
This commit is contained in:
Chris Tate
2026-10-18 19:42:27 -05:00
parent 7b2b864bdd
commit ec79cf2789
5 changed files with 786 additions and 3 deletions
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/6070-iterator-helpers-untyped.js"
],
"diags": []
}
+1
View File
@@ -173,6 +173,7 @@ typedef enum {
SCR_DYNH_BROADCAST_CHANNEL, /* worker_threads BroadcastChannel (scr_worker_events.c) */
SCR_DYNH_HASH, /* node:crypto Hash (scr_lib.c) */
SCR_DYNH_HMAC, /* node:crypto Hmac (scr_lib.c) */
SCR_DYNH_ITERATOR_HELPER, /* an iterator helper object over a checked iterator (scr_json.c) */
SCR_DYNH_COUNT,
} ScrDynHandleTag;
+688 -3
View File
@@ -75,7 +75,7 @@ static bool scr_builtin_iterable(const ScrDyn *value) {
return value->kind == SCR_DYN_ARR || value->kind == SCR_DYN_STR || value->kind == SCR_DYN_BYTES ||
(scr_dyn_generator(value) && value->v.typed_ref.type_key[0] != 'a') ||
(value->kind == SCR_DYN_TYPED_REF && scr_dyn_ref_is_array(value)) ||
(value->kind == SCR_DYN_HANDLE && (value->v.handle.tag == SCR_DYNH_ITERATOR || value->v.handle.tag == SCR_DYNH_MAP || value->v.handle.tag == SCR_DYNH_SET ||
(value->kind == SCR_DYN_HANDLE && (value->v.handle.tag == SCR_DYNH_ITERATOR || value->v.handle.tag == SCR_DYNH_ITERATOR_HELPER || value->v.handle.tag == SCR_DYNH_MAP || value->v.handle.tag == SCR_DYNH_SET ||
scr_dyn_handle_ops_of(value)->iter_pack || scr_dyn_handle_ops_of(value)->iter_step));
}
static bool scr_iterator_object(const ScrDyn *value) {
@@ -4657,8 +4657,16 @@ bool scr_dyn_iterator_step_done(const ScrDyn *value) {
return ((ScrNativeIterator *)value->v.handle.ptr)->step_done;
}
static int scr_ih_method_index(const char *name, size_t len);
static ScrDyn *scr_iterator_helper_method(ScrDyn *self, int index, ScrDyn *const *args, size_t argc);
static ScrDyn *scr_ih_method_function(int index);
static ScrDyn *scr_native_iterator_get(void *ptr, const char *key, size_t len) {
if (len != 4 || memcmp(key, "next", 4)) return NULL;
if (len != 4 || memcmp(key, "next", 4)) {
/* The Iterator.prototype helper methods. */
int index = scr_ih_method_index(key, len);
return index >= 0 ? scr_ih_method_function(index) : NULL;
}
unsigned kind = ((ScrNativeIterator *)ptr)->kind;
if (kind == 4) return scr_iterator_function(10, &scr_native_handle_iterator_next_call, "next");
return kind == 3 ? scr_iterator_function(8, &scr_native_set_iterator_next_call, "next")
@@ -4669,8 +4677,9 @@ static ScrDyn *scr_native_iterator_get(void *ptr, const char *key, size_t len) {
static ScrDyn *scr_native_iterator_invoke(void *ptr, ScrDyn *self, const char *method,
ScrDyn *const *args, size_t argc, const char *what) {
(void)self;
if (!strcmp(method, "next")) return scr_native_iterator_next(ptr);
int index = scr_ih_method_index(method, strlen(method));
if (index >= 0) return scr_iterator_helper_method(self, index, args, argc);
ScrDyn *missing = scr_dyn_undefined();
return scr_dyn_call(missing, args, argc, what);
}
@@ -4700,6 +4709,682 @@ static ScrDyn *scr_native_collection_iterator(ScrDyn *source, unsigned selection
return result;
}
/* ── iterator helpers on checked iterators (ES2025) ─────────────────────────
* The Iterator.prototype methods over a checked iterator object: a native
* array, string, Map or Set iterator, or a helper object made here. Typed
* code lowers its helpers statically; these answer untyped JavaScript
* receivers. The stages (map, filter, take, drop, flatMap) make helper
* objects that pull lazily; the consuming methods (toArray, forEach, reduce,
* some, every, find) drive the iterator to completion or to their answer.
* Closing follows IteratorClose: a helper closes the iterator it was made
* from when a callback throws, when its budget is spent, and on return();
* an error from a close that runs because of another error is dropped. */
static ScrDyn *scr_iterator_call(const ScrDyn *method, const ScrDyn *receiver, const char *name);
static ScrDyn *scr_iterator_method(const ScrDyn *source);
enum { SCR_IH_MAP, SCR_IH_FILTER, SCR_IH_TAKE, SCR_IH_DROP, SCR_IH_FLATMAP };
enum { SCR_IH_START, SCR_IH_YIELDED, SCR_IH_RUNNING, SCR_IH_DONE };
typedef struct {
size_t rc;
unsigned kind;
unsigned state;
ScrDyn *iterated; /* the iterator the helper was made from */
ScrDyn *next; /* its next method, read when the helper was made */
ScrDyn *fn; /* the map, filter or flatMap callback */
double remaining; /* the take or drop budget */
double counter;
ScrDyn *inner; /* flatMap's current inner iterator */
ScrDyn *inner_next;
} ScrIterHelper;
static const char *const scr_ih_method_names[] = {
"map", "filter", "take", "drop", "flatMap", "reduce", "toArray", "forEach", "some", "every", "find",
};
#define SCR_IH_METHOD_COUNT (sizeof scr_ih_method_names / sizeof scr_ih_method_names[0])
/* The index of an Iterator.prototype helper method name, or -1. */
static int scr_ih_method_index(const char *name, size_t len) {
for (size_t i = 0; i < SCR_IH_METHOD_COUNT; i++) {
if (strlen(scr_ih_method_names[i]) == len && !memcmp(scr_ih_method_names[i], name, len)) return (int)i;
}
return -1;
}
/* V8's description of a value in "<value> is not a function": the type,
* with the value for primitives that print one. */
static void scr_ih_describe_type(ScrJsonBuf *b, const ScrDyn *v) {
switch (v->kind) {
case SCR_DYN_UNDEF: scr_jb_puts(b, "undefined"); return;
case SCR_DYN_NULL: scr_jb_puts(b, "object null"); return;
case SCR_DYN_BOOL: scr_jb_puts(b, v->v.b ? "boolean true" : "boolean false"); return;
case SCR_DYN_NUM: {
scr_jb_puts(b, "number ");
ScrStr *n = scr_f64_to_scrstr(v->v.num);
scr_jb_write(b, n->data, n->len);
scr_str_release(n);
return;
}
case SCR_DYN_STR:
scr_jb_puts(b, "string \"");
scr_jb_write(b, v->v.str->data, v->v.str->len);
scr_jb_putc(b, '"');
return;
case SCR_DYN_SYMBOL: scr_jb_puts(b, "symbol"); return;
case SCR_DYN_BIGINT: scr_jb_puts(b, "bigint"); return;
default: scr_jb_puts(b, "object"); return;
}
}
static bool scr_ih_require_callable(const ScrDyn *fn) {
if (scr_dyn_is_callable(fn)) return true;
ScrJsonBuf b;
scr_jb_init(&b);
scr_ih_describe_type(&b, fn);
scr_jb_puts(&b, " is not a function");
scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&b));
return false;
}
/* V8's text for the value in "<value> must be positive": numbers, strings
* raw, undefined by name,
* arrays and objects by their default string or constructor name. */
static void scr_ih_describe_value(ScrJsonBuf *b, const ScrDyn *v) {
if (v->kind == SCR_DYN_ARR) {
scr_jb_puts(b, "[object Array]");
return;
}
if (v->kind == SCR_DYN_OBJ) {
scr_jb_puts(b, "#<Object>");
return;
}
if (v->kind == SCR_DYN_HANDLE) {
scr_jb_puts(b, "#<");
scr_jb_puts(b, scr_dyn_handle_ops_of(v)->cls);
scr_jb_putc(b, '>');
return;
}
if (v->kind == SCR_DYN_STR) {
scr_jb_write(b, v->v.str->data, v->v.str->len);
return;
}
if (v->kind == SCR_DYN_NUM) {
ScrStr *n = scr_f64_to_scrstr(v->v.num);
scr_jb_write(b, n->data, n->len);
scr_str_release(n);
return;
}
scr_jb_puts(b, v->kind == SCR_DYN_UNDEF ? "undefined" : "NaN");
}
/* ToIntegerOrInfinity of a take/drop budget with Node's RangeError for NaN
* and negative budgets. Answers false with the exception pending. */
static bool scr_ih_budget(const ScrDyn *limit, double *out) {
double n = scr_dyn_number_coerce(limit);
if (scr_exc_pending()) return false;
if (n != n) {
ScrJsonBuf b;
scr_jb_init(&b);
scr_ih_describe_value(&b, limit);
scr_jb_puts(&b, " must be positive");
scr_throw_error(SCR_ERR_RANGE, scr_jb_finish(&b));
return false;
}
double t = n < 9007199254740992.0 && n > -9007199254740992.0 ? n - fmod(n, 1.0) : n;
if (t < 0) {
ScrJsonBuf b;
scr_jb_init(&b);
scr_ih_describe_value(&b, limit);
scr_jb_puts(&b, " must be positive");
scr_throw_error(SCR_ERR_RANGE, scr_jb_finish(&b));
return false;
}
*out = t == 0 ? 0 : t;
return true;
}
/* IteratorClose(iterator, completion). `abrupt` is a throw completion: the
* pending error stays and anything the return() call raises is dropped.
* Otherwise errors from return() propagate, and a non-object result is
* Node's TypeError. Answers false when an exception is pending after. */
static bool scr_ih_close(ScrDyn *iterator, bool abrupt) {
ScrCaught *first = abrupt ? scr_exc_take() : NULL;
ScrDyn *method = scr_iterator_read(iterator, "return", 6);
bool ok = method != NULL;
if (ok && method->kind != SCR_DYN_UNDEF && method->kind != SCR_DYN_NULL) {
ScrDyn *result = scr_iterator_call(method, iterator, "return");
if (!result) ok = false;
else {
if (!abrupt && !scr_iterator_object(result)) {
static const char message[] = "Iterator result is not an object";
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
ok = false;
}
scr_dyn_release(result);
}
}
scr_dyn_release(method);
if (first) {
if (scr_exc_pending()) {
ScrCaught *dropped = scr_exc_take();
if (dropped) scr_caught_release(dropped);
}
scr_rethrow(first);
scr_caught_release(first);
return false;
}
return ok && !scr_exc_pending();
}
/* IteratorStepValue over (iterator, next): +1 value with *done false, or
* *done true (the answer is then undefined); NULL with the exception
* pending when next() throws or answers a non-object. */
static ScrDyn *scr_ih_step(ScrDyn *iterator, ScrDyn *next, bool *done) {
*done = false;
if (scr_dyn_iterator_can_step(iterator, next)) {
ScrDyn *value = scr_dyn_iterator_step(iterator);
if (!value) return NULL;
*done = scr_dyn_iterator_step_done(iterator);
return value;
}
ScrDyn *raw = scr_iterator_call(next, iterator, "next");
if (!raw) return NULL;
ScrDyn *result = scr_dyn_iterator_result(raw);
scr_dyn_release(raw);
if (!result) return NULL;
ScrDyn *finished = scr_iterator_read(result, "done", 4);
if (!finished) {
scr_dyn_release(result);
return NULL;
}
*done = scr_dyn_truthy(finished);
scr_dyn_release(finished);
ScrDyn *value = *done ? scr_dyn_retain(scr_dyn_undefined()) : scr_iterator_read(result, "value", 5);
scr_dyn_release(result);
return value;
}
/* GetIteratorDirect: the receiver must be an object; its next method is
* read once. +1 next, or NULL with the TypeError pending. */
static ScrDyn *scr_ih_direct(const ScrDyn *self, const char *method) {
if (!scr_iterator_object(self)) {
ScrJsonBuf b;
scr_jb_init(&b);
scr_jb_puts(&b, "Iterator.prototype.");
scr_jb_puts(&b, method);
scr_jb_puts(&b, " called on non-object");
scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&b));
return NULL;
}
return scr_iterator_read(self, "next", 4);
}
static ScrDyn *scr_ih_result(ScrDyn *value, bool done) {
ScrDyn *result = scr_dyn_new_obj();
scr_dyn_obj_set(result, "value", 5, value);
scr_dyn_obj_set(result, "done", 4, scr_dyn_new_bool(done));
return result;
}
static void *scr_ih_retain(void *ptr) {
((ScrIterHelper *)ptr)->rc++;
return ptr;
}
static void scr_ih_release(void *ptr) {
ScrIterHelper *h = ptr;
if (--h->rc) return;
scr_dyn_release(h->iterated);
scr_dyn_release(h->next);
if (h->fn) scr_dyn_release(h->fn);
if (h->inner) scr_dyn_release(h->inner);
if (h->inner_next) scr_dyn_release(h->inner_next);
free(h);
}
static void scr_ih_clear_inner(ScrIterHelper *h) {
if (h->inner) scr_dyn_release(h->inner);
if (h->inner_next) scr_dyn_release(h->inner_next);
h->inner = NULL;
h->inner_next = NULL;
}
/* GetIteratorFlattenable(value, reject-primitives) for flatMap's callback
* result: +1 iterator with *next_out +1, or NULL with the TypeError pending. */
static ScrDyn *scr_ih_flattenable(ScrDyn *value, ScrDyn **next_out) {
if (!scr_iterator_object(value)) {
static const char message[] = "Iterator.prototype.flatMap called on non-object";
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
return NULL;
}
ScrDyn *method = scr_iterator_method(value);
if (!method) return NULL;
ScrDyn *iterator;
if (method->kind == SCR_DYN_UNDEF || method->kind == SCR_DYN_NULL) {
iterator = scr_dyn_retain(value);
} else {
iterator = scr_iterator_call(method, value, "value[Symbol.iterator]");
if (iterator && !scr_iterator_object(iterator)) {
scr_dyn_release(iterator);
static const char message[] = "Iterator result is not an object";
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
iterator = NULL;
}
}
scr_dyn_release(method);
if (!iterator) return NULL;
ScrDyn *next = scr_iterator_read(iterator, "next", 4);
if (!next) {
scr_dyn_release(iterator);
return NULL;
}
*next_out = next;
return iterator;
}
/* Calls a stage callback with (value, counter); +1 result or NULL. */
static ScrDyn *scr_ih_callback(ScrDyn *fn, ScrDyn *value, double counter) {
ScrDyn *index = scr_dyn_new_num(counter);
ScrDyn *args[2] = { value, index };
ScrDyn *undefined = scr_dyn_retain(scr_dyn_undefined());
scr_dyn_this_push_dyn(undefined);
ScrDyn *result = scr_dyn_call(fn, args, 2, "callback");
scr_dyn_this_pop();
scr_dyn_release(undefined);
scr_dyn_release(index);
return result;
}
/* The helper's next value: +1 value with *done false, *done true, or NULL
* with the exception pending (the helper is then done). */
static ScrDyn *scr_ih_advance(ScrIterHelper *h, bool *done) {
*done = false;
for (;;) {
if (h->kind == SCR_IH_FLATMAP && h->inner) {
bool inner_done;
ScrDyn *value = scr_ih_step(h->inner, h->inner_next, &inner_done);
if (!value) {
scr_ih_close(h->iterated, true);
return NULL;
}
if (!inner_done) return value;
scr_dyn_release(value);
scr_ih_clear_inner(h);
continue;
}
if (h->kind == SCR_IH_TAKE) {
if (h->remaining == 0) {
*done = true;
if (!scr_ih_close(h->iterated, false)) return NULL;
return scr_dyn_retain(scr_dyn_undefined());
}
if (h->remaining != INFINITY) h->remaining--;
}
if (h->kind == SCR_IH_DROP) {
while (h->remaining > 0) {
if (h->remaining != INFINITY) h->remaining--;
bool skipped_done;
ScrDyn *skipped = scr_ih_step(h->iterated, h->next, &skipped_done);
if (!skipped) return NULL;
scr_dyn_release(skipped);
if (skipped_done) {
*done = true;
return scr_dyn_retain(scr_dyn_undefined());
}
}
}
bool source_done;
ScrDyn *value = scr_ih_step(h->iterated, h->next, &source_done);
if (!value) return NULL;
if (source_done) {
*done = true;
return value;
}
if (h->kind == SCR_IH_TAKE || h->kind == SCR_IH_DROP) return value;
ScrDyn *mapped = scr_ih_callback(h->fn, value, h->counter);
if (!mapped) {
scr_dyn_release(value);
scr_ih_close(h->iterated, true);
return NULL;
}
h->counter++;
if (h->kind == SCR_IH_MAP) {
scr_dyn_release(value);
return mapped;
}
if (h->kind == SCR_IH_FILTER) {
bool keep = scr_dyn_truthy(mapped);
scr_dyn_release(mapped);
if (keep) return value;
scr_dyn_release(value);
continue;
}
/* flatMap */
scr_dyn_release(value);
ScrDyn *inner_next = NULL;
ScrDyn *inner = scr_ih_flattenable(mapped, &inner_next);
scr_dyn_release(mapped);
if (!inner) {
scr_ih_close(h->iterated, true);
return NULL;
}
h->inner = inner;
h->inner_next = inner_next;
}
}
static void scr_ih_running_error(void) {
static const char message[] = "Generator is already running";
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
}
static ScrDyn *scr_ih_next(ScrIterHelper *h) {
if (h->state == SCR_IH_RUNNING) {
scr_ih_running_error();
return NULL;
}
if (h->state == SCR_IH_DONE) return scr_ih_result(scr_dyn_retain(scr_dyn_undefined()), true);
h->state = SCR_IH_RUNNING;
scr_ih_retain(h);
bool done;
ScrDyn *value = scr_ih_advance(h, &done);
ScrDyn *result = NULL;
if (!value || done) {
h->state = SCR_IH_DONE;
scr_ih_clear_inner(h);
if (value) {
scr_dyn_release(value);
result = scr_ih_result(scr_dyn_retain(scr_dyn_undefined()), true);
}
} else {
h->state = SCR_IH_YIELDED;
result = scr_ih_result(value, false);
}
scr_ih_release(h);
return result;
}
/* %IteratorHelperPrototype%.return: an unstarted or suspended helper
* completes and closes the iterator it was made from (flatMap closes its
* inner iterator first). */
static ScrDyn *scr_ih_return(ScrIterHelper *h) {
if (h->state == SCR_IH_RUNNING) {
scr_ih_running_error();
return NULL;
}
if (h->state != SCR_IH_DONE) {
h->state = SCR_IH_DONE;
scr_ih_retain(h);
bool ok = true;
if (h->inner) {
ScrDyn *inner = scr_dyn_retain(h->inner);
scr_ih_clear_inner(h);
if (!scr_ih_close(inner, false)) {
scr_ih_close(h->iterated, true);
ok = false;
}
scr_dyn_release(inner);
}
if (ok) ok = scr_ih_close(h->iterated, false);
scr_ih_release(h);
if (!ok) return NULL;
}
return scr_ih_result(scr_dyn_retain(scr_dyn_undefined()), true);
}
static ScrDyn *scr_iterator_helper_method(ScrDyn *self, int index, ScrDyn *const *args, size_t argc);
static ScrDyn *scr_ih_invoke(void *ptr, ScrDyn *self, const char *method, ScrDyn *const *args,
size_t argc, const char *what) {
if (!strcmp(method, "next")) return scr_ih_next(ptr);
if (!strcmp(method, "return")) return scr_ih_return(ptr);
int index = scr_ih_method_index(method, strlen(method));
if (index >= 0) return scr_iterator_helper_method(self, index, args, argc);
return scr_dyn_call(scr_dyn_undefined(), args, argc, what);
}
static ScrDyn *scr_ih_method_function(int index);
static ScrDyn *scr_ih_next_call(ScrClosure *clo, ScrDyn *const *args, size_t argc) {
(void)clo; (void)args; (void)argc;
ScrDyn *self = scr_dyn_this_get();
ScrDyn *result = NULL;
if (self->kind == SCR_DYN_HANDLE && self->v.handle.tag == SCR_DYNH_ITERATOR_HELPER) {
result = scr_ih_next(self->v.handle.ptr);
} else {
static const char message[] = "Method Iterator Helper.prototype.next called on incompatible receiver";
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
}
scr_dyn_release(self);
return result;
}
static ScrDyn *scr_ih_return_call(ScrClosure *clo, ScrDyn *const *args, size_t argc) {
(void)clo; (void)args; (void)argc;
ScrDyn *self = scr_dyn_this_get();
ScrDyn *result = NULL;
if (self->kind == SCR_DYN_HANDLE && self->v.handle.tag == SCR_DYNH_ITERATOR_HELPER) {
result = scr_ih_return(self->v.handle.ptr);
} else {
static const char message[] = "Method Iterator Helper.prototype.return called on incompatible receiver";
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
}
scr_dyn_release(self);
return result;
}
static SCR_TL ScrDyn *scr_ih_functions[SCR_IH_METHOD_COUNT + 2];
static SCR_TL bool scr_ih_functions_registered;
static void scr_ih_functions_cleanup(void) {
for (size_t i = 0; i < SCR_IH_METHOD_COUNT + 2; i++) {
scr_dyn_release(scr_ih_functions[i]);
scr_ih_functions[i] = NULL;
}
}
static ScrDyn *scr_ih_function(size_t slot, ScrDynThunk thunk, const char *name) {
if (!scr_ih_functions_registered) {
scr_ih_functions_registered = true;
scr_atexit(scr_ih_functions_cleanup);
}
if (!scr_ih_functions[slot])
scr_ih_functions[slot] = scr_dyn_new_func(scr_closure_new(NULL, 0), thunk, 0, "native:iterator", name);
return scr_dyn_retain(scr_ih_functions[slot]);
}
static ScrDyn *scr_ih_get(void *ptr, const char *key, size_t len) {
(void)ptr;
if (len == 4 && !memcmp(key, "next", 4)) return scr_ih_function(SCR_IH_METHOD_COUNT, &scr_ih_next_call, "next");
if (len == 6 && !memcmp(key, "return", 6))
return scr_ih_function(SCR_IH_METHOD_COUNT + 1, &scr_ih_return_call, "return");
int index = scr_ih_method_index(key, len);
return index >= 0 ? scr_ih_method_function(index) : NULL;
}
static ScrDyn *scr_ih_inspect_view(void *ptr) {
(void)ptr;
return scr_dyn_new_obj();
}
static ScrDyn *scr_ih_new(unsigned kind, ScrDyn *iterated, ScrDyn *next, ScrDyn *fn, double remaining) {
static const ScrDynHandleOps ops = {
.cls = "Object",
.retain = &scr_ih_retain,
.release = &scr_ih_release,
.invoke = &scr_ih_invoke,
.get = &scr_ih_get,
.inspect_view = &scr_ih_inspect_view,
.to_string_tag = "Iterator Helper",
.inspect_view_empty = true,
};
scr_dyn_handle_install(SCR_DYNH_ITERATOR_HELPER, &ops);
ScrIterHelper *h = malloc(sizeof *h);
if (!h) scr_json_oom();
*h = (ScrIterHelper){ .rc = 1, .kind = kind, .state = SCR_IH_START, .iterated = scr_dyn_retain(iterated),
.next = next, .fn = fn ? scr_dyn_retain(fn) : NULL, .remaining = remaining };
ScrDyn *result = scr_dyn_new_handle(h, SCR_DYNH_ITERATOR_HELPER);
scr_ih_release(h);
return result;
}
/* The consuming methods: drive (self, next) with `fn`. */
static ScrDyn *scr_ih_consume(ScrDyn *self, ScrDyn *next, int index, ScrDyn *fn, ScrDyn *const *args,
size_t argc) {
const char *name = scr_ih_method_names[index];
bool reduce = !strcmp(name, "reduce");
ScrDyn *out = !strcmp(name, "toArray") ? scr_dyn_new_arr() : NULL;
ScrDyn *acc = NULL;
double counter = 0;
if (reduce && argc >= 2) acc = scr_dyn_retain(args[1]);
else if (reduce) {
bool done;
ScrDyn *first = scr_ih_step(self, next, &done);
if (!first) return NULL;
if (done) {
scr_dyn_release(first);
static const char message[] = "Reduce of a done iterator with no initial value";
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
return NULL;
}
acc = first;
counter = 1;
}
for (;;) {
bool done;
ScrDyn *value = scr_ih_step(self, next, &done);
if (!value) {
if (out) scr_dyn_release(out);
if (acc) scr_dyn_release(acc);
return NULL;
}
if (done) {
scr_dyn_release(value);
break;
}
if (out) {
scr_dyn_arr_push(out, value);
continue;
}
ScrDyn *answer;
if (reduce) {
ScrDyn *index_value = scr_dyn_new_num(counter);
ScrDyn *call_args[3] = { acc, value, index_value };
ScrDyn *undefined = scr_dyn_retain(scr_dyn_undefined());
scr_dyn_this_push_dyn(undefined);
answer = scr_dyn_call(fn, call_args, 3, "callback");
scr_dyn_this_pop();
scr_dyn_release(undefined);
scr_dyn_release(index_value);
} else {
answer = scr_ih_callback(fn, value, counter);
}
counter++;
if (!answer) {
scr_dyn_release(value);
if (acc) scr_dyn_release(acc);
scr_ih_close(self, true);
return NULL;
}
if (reduce) {
scr_dyn_release(acc);
acc = answer;
scr_dyn_release(value);
continue;
}
bool truthy = scr_dyn_truthy(answer);
scr_dyn_release(answer);
if (!strcmp(name, "forEach")) {
scr_dyn_release(value);
continue;
}
bool stop = !strcmp(name, "every") ? !truthy : truthy;
if (!stop) {
scr_dyn_release(value);
continue;
}
if (!scr_ih_close(self, false)) {
scr_dyn_release(value);
return NULL;
}
if (!strcmp(name, "find")) return value;
scr_dyn_release(value);
return scr_dyn_new_bool(!strcmp(name, "some"));
}
if (out) return out;
if (reduce) return acc;
if (!strcmp(name, "some") || !strcmp(name, "every")) return scr_dyn_new_bool(!strcmp(name, "every"));
return scr_dyn_retain(scr_dyn_undefined());
}
/* An Iterator.prototype helper method called on `self` (borrowed). */
static ScrDyn *scr_iterator_helper_method(ScrDyn *self, int index, ScrDyn *const *args, size_t argc) {
const char *name = scr_ih_method_names[index];
ScrDyn *next = scr_ih_direct(self, name);
if (!next) return NULL;
ScrDyn *arg = argc > 0 ? args[0] : scr_dyn_undefined();
if (!strcmp(name, "take") || !strcmp(name, "drop")) {
double budget;
if (!scr_ih_budget(arg, &budget)) {
scr_dyn_release(next);
scr_ih_close(self, true);
return NULL;
}
return scr_ih_new(!strcmp(name, "take") ? SCR_IH_TAKE : SCR_IH_DROP, self, next, NULL, budget);
}
if (!strcmp(name, "toArray")) {
ScrDyn *result = scr_ih_consume(self, next, index, NULL, args, argc);
scr_dyn_release(next);
return result;
}
if (!scr_ih_require_callable(arg)) {
scr_dyn_release(next);
scr_ih_close(self, true);
return NULL;
}
if (index < 5) {
unsigned kind = !strcmp(name, "map") ? SCR_IH_MAP : !strcmp(name, "filter") ? SCR_IH_FILTER : SCR_IH_FLATMAP;
return scr_ih_new(kind, self, next, arg, 0);
}
ScrDyn *result = scr_ih_consume(self, next, index, arg, args, argc);
scr_dyn_release(next);
return result;
}
static ScrDyn *scr_ih_method_call(int index, ScrDyn *const *args, size_t argc) {
ScrDyn *self = scr_dyn_this_get();
ScrDyn *result = scr_iterator_helper_method(self, index, args, argc);
scr_dyn_release(self);
return result;
}
#define SCR_IH_THUNK(i) \
static ScrDyn *scr_ih_method_call_##i(ScrClosure *clo, ScrDyn *const *args, size_t argc) { \
(void)clo; \
return scr_ih_method_call(i, args, argc); \
}
SCR_IH_THUNK(0)
SCR_IH_THUNK(1)
SCR_IH_THUNK(2)
SCR_IH_THUNK(3)
SCR_IH_THUNK(4)
SCR_IH_THUNK(5)
SCR_IH_THUNK(6)
SCR_IH_THUNK(7)
SCR_IH_THUNK(8)
SCR_IH_THUNK(9)
SCR_IH_THUNK(10)
static ScrDyn *scr_ih_method_function(int index) {
static const ScrDynThunk thunks[SCR_IH_METHOD_COUNT] = {
&scr_ih_method_call_0, &scr_ih_method_call_1, &scr_ih_method_call_2, &scr_ih_method_call_3,
&scr_ih_method_call_4, &scr_ih_method_call_5, &scr_ih_method_call_6, &scr_ih_method_call_7,
&scr_ih_method_call_8, &scr_ih_method_call_9, &scr_ih_method_call_10,
};
return scr_ih_function((size_t)index, thunks[index], scr_ih_method_names[index]);
}
ScrDyn *scr_dyn_native_handle_iterator(ScrDyn *source, unsigned selection) {
return scr_native_collection_iterator(source, selection);
}
@@ -0,0 +1,89 @@
// Iterator helpers on untyped iterators in JavaScript: the receivers come
// from parameters without inferred types, so the helpers run on the
// runtime's iterator objects.
const log = [];
function flush(what) {
console.log(what, "|", log.join(" "));
log.length = 0;
}
function values(xs) {
return xs.values();
}
function keysOf(m) {
return m.keys();
}
function charsOf(s) {
return s[Symbol.iterator]();
}
// Stages and consuming methods.
const evens = values([1, 2, 3, 4, 6]).filter((x) => x % 2 === 0);
console.log(evens.next(), evens.take(1).toArray(), [...evens]);
console.log(values([1, 2, 3]).map((x, i) => x * 10 + i).toArray(), values([]).map((x) => x).toArray());
console.log(values([1, 2, 3]).reduce((a, b) => a + b), values([1, 2]).reduce((a, b, i) => a + b * i, 100));
console.log(values([5, 6, 7]).drop(1).toArray(), values([5, 6, 7]).take(Infinity).toArray());
console.log(values(new Set([1, 3, 5])).find((x) => x > 2), values(new Set()).find(() => true));
console.log(values([1, 2, 3]).some((x) => x === 2), values([1, 2, 3]).every((x) => x < 3));
console.log(keysOf(new Map([["a", 1], ["b", 2]])).map((k) => k.toUpperCase()).toArray());
console.log(charsOf("a-b-c").filter((c) => c !== "-").toArray().join(""));
const seen = [];
values(["p", "q"]).forEach((v, i) => seen.push(v + i));
console.log(seen.join(","));
// Helper objects.
const doubled = values([1, 2, 3]).map((x) => x * 2);
console.log(typeof doubled.next, typeof doubled.return, typeof doubled.map, typeof doubled.throw);
console.log(doubled.next(), doubled.return(), doubled.next());
for (const x of values([1, 2, 3]).drop(1)) log.push(`of${x}`);
flush("for...of");
console.log([...values([4, 5]).flatMap((x) => [x, x])]);
console.log(Object.prototype.toString.call(doubled), String(doubled));
console.log(doubled);
let self = null;
self = values([1, 2]).map((x) => {
if (x === 2) {
try {
self.next();
} catch (e) {
log.push(`${e.name}: ${e.message}`);
}
}
return x;
});
flush(self.toArray());
// flatMap closes an inner iterator it leaves early.
function inner(label) {
let n = 0;
return {
next() {
n++;
return { value: `${label}${n}`, done: n > 3 };
},
return() {
log.push(`${label}:closed`);
return { done: true };
},
};
}
flush(values([1, 2]).flatMap((x) => inner(`i${x}`)).take(2).toArray());
const stepping = values([1]).flatMap(() => inner("j"));
stepping.next();
flush([stepping.return(), stepping.next()]);
// Node's errors for invalid arguments.
function attempt(f) {
try {
console.log(f());
} catch (e) {
console.log(`${e.name}: ${e.message}`);
}
}
attempt(() => values([1]).take("x"));
attempt(() => values([1]).drop(-1));
attempt(() => values([1]).map(5));
attempt(() => values([1]).filter(null));
attempt(() => values([1]).reduce(undefined));
attempt(() => values([1]).forEach("f"));
attempt(() => values([]).reduce((a, b) => a));
attempt(() => values([1, 2]).flatMap((x) => x).toArray());
+2
View File
@@ -2222,6 +2222,7 @@
"6043-string-index-narrowed-union.ts": 2983,
"605-closures-forward-capture-tdz.ts": 3277,
"6051-generic-class-factory-results.ts": 4363,
"6070-iterator-helpers-untyped.js": 4050,
"6100-record-get-or-create.ts": 3771,
"6101-record-get-or-create-unchecked/main.ts": 2107,
"6103-typeof-builtin-bindings.cjs": 2202,
@@ -5280,6 +5281,7 @@
"6043-string-index-narrowed-union.ts": 3627,
"605-closures-forward-capture-tdz.ts": 4638,
"6051-generic-class-factory-results.ts": 46581,
"6070-iterator-helpers-untyped.js": 6900,
"6100-record-get-or-create.ts": 3173,
"6101-record-get-or-create-unchecked/main.ts": 3228,
"6103-typeof-builtin-bindings.cjs": 1796,