From 40dd0a99a15f46098f126944f4436b6c77b41fe7 Mon Sep 17 00:00:00 2001 From: Fabrice Bellard Date: Tue, 13 Oct 2026 18:09:44 +0200 Subject: [PATCH] added iterator chunking (initial patch by saghul) --- quickjs.c | 134 +++++++++++++++++++++++++++++++++++++++++++++++++-- test262.conf | 2 +- 2 files changed, 132 insertions(+), 4 deletions(-) diff --git a/quickjs.c b/quickjs.c index 01876e2..7e77735 100644 --- a/quickjs.c +++ b/quickjs.c @@ -45480,6 +45480,7 @@ static JSValue js_iterator_zip(JSContext *ctx, JSValueConst this_val, } typedef enum JSIteratorHelperKindEnum { + JS_ITERATOR_HELPER_KIND_CHUNKS, JS_ITERATOR_HELPER_KIND_DROP, JS_ITERATOR_HELPER_KIND_EVERY, JS_ITERATOR_HELPER_KIND_FILTER, @@ -45489,6 +45490,7 @@ typedef enum JSIteratorHelperKindEnum { JS_ITERATOR_HELPER_KIND_MAP, JS_ITERATOR_HELPER_KIND_SOME, JS_ITERATOR_HELPER_KIND_TAKE, + JS_ITERATOR_HELPER_KIND_WINDOWS, } JSIteratorHelperKindEnum; typedef struct JSIteratorHelperData { @@ -45497,10 +45499,14 @@ typedef struct JSIteratorHelperData { JSValue func; // predicate (filter) or mapper (flatMap, map) JSValue inner; // innerValue (flatMap) JSValue inner_next; // innerValue next method (flatMap) + JSValue *buf; // buffered values (chunks, windows) int64_t count; // limit (drop, take) or counter (filter, map, flatMap) + int buf_count; // number of values in |buf| + int buf_size; // allocated size of |buf| JSIteratorHelperKindEnum kind : 8; uint8_t executing : 1; uint8_t done : 1; + uint8_t allow_partial : 1; // yield a partial final window (windows) } JSIteratorHelperData; static JSValue js_create_iterator_helper(JSContext *ctx, JSValueConst this_val, @@ -45510,13 +45516,55 @@ static JSValue js_create_iterator_helper(JSContext *ctx, JSValueConst this_val, JSValue obj, method; int64_t count; JSIteratorHelperData *it; + bool allow_partial; if (!JS_IsObject(this_val)) return JS_ThrowTypeErrorNotAnObject(ctx); func = JS_UNDEFINED; count = 0; + allow_partial = FALSE; switch(magic) { + case JS_ITERATOR_HELPER_KIND_CHUNKS: + case JS_ITERATOR_HELPER_KIND_WINDOWS: + { + const char *name; + double d; + BOOL ok; + + name = magic == JS_ITERATOR_HELPER_KIND_CHUNKS ? "chunkSize" + : "windowSize"; + // note: no coercion, the argument must be an integral number + d = NAN; + if (JS_IsNumber(argv[0]) && JS_ToFloat64(ctx, &d, argv[0])) + goto fail; + if (!isfinite(d) || trunc(d) != d) { + JS_ThrowTypeError(ctx, "%s is not an integral number", name); + goto fail; + } + if (d < 1 || d > UINT32_MAX) { + JS_ThrowRangeError(ctx, "%s out of range", name); + goto fail; + } + count = (int64_t)d; + if (magic == JS_ITERATOR_HELPER_KIND_WINDOWS && argc > 1 && + !JS_IsUndefined(argv[1])) { + ok = FALSE; + if (JS_IsString(argv[1])) { + const char *str = JS_ToCString(ctx, argv[1]); + if (!str) + goto fail; + allow_partial = !strcmp(str, "allow-partial"); + ok = allow_partial || !strcmp(str, "only-full"); + JS_FreeCString(ctx, str); + } + if (!ok) { + JS_ThrowTypeError(ctx, "bad undersized option"); + goto fail; + } + } + } + break; case JS_ITERATOR_HELPER_KIND_DROP: case JS_ITERATOR_HELPER_KIND_TAKE: { @@ -45569,9 +45617,13 @@ static JSValue js_create_iterator_helper(JSContext *ctx, JSValueConst this_val, it->next = method; it->inner = JS_UNDEFINED; it->inner_next = JS_UNDEFINED; + it->buf = NULL; it->count = count; + it->buf_count = 0; + it->buf_size = 0; it->executing = 0; it->done = 0; + it->allow_partial = allow_partial; JS_SetOpaque(obj, it); return obj; range_error: @@ -46009,6 +46061,9 @@ static void js_iterator_helper_finalizer(JSRuntime *rt, JSValue val) JS_FreeValueRT(rt, it->next); JS_FreeValueRT(rt, it->inner); JS_FreeValueRT(rt, it->inner_next); + while (it->buf_count > 0) + JS_FreeValueRT(rt, it->buf[--it->buf_count]); + js_free_rt(rt, it->buf); js_free_rt(rt, it); } } @@ -46024,6 +46079,8 @@ static void js_iterator_helper_mark(JSRuntime *rt, JSValueConst val, JS_MarkValue(rt, it->next, mark_func); JS_MarkValue(rt, it->inner, mark_func); JS_MarkValue(rt, it->inner_next, mark_func); + for (int i = 0; i < it->buf_count; i++) + JS_MarkValue(rt, it->buf[i], mark_func); } } @@ -46069,10 +46126,70 @@ static JSValue js_iterator_helper_next(JSContext *ctx, JSValueConst this_val, } if (JS_IteratorClose(ctx, it->obj, JS_IsException(ret))) ret = JS_EXCEPTION; + it->done = TRUE; goto done; } switch (it->kind) { + case JS_ITERATOR_HELPER_KIND_CHUNKS: + case JS_ITERATOR_HELPER_KIND_WINDOWS: + { + BOOL windows = it->kind == JS_ITERATOR_HELPER_KIND_WINDOWS; + JSValue item; + + for (;;) { + item = JS_IteratorNext(ctx, it->obj, it->next, pdone); + if (JS_IsException(item)) + goto fail_no_close; + if (*pdone) { + JS_FreeValue(ctx, item); + /* the underlying iterator is done: yield what's left in + the buffer, if anything, then stop */ + it->done = TRUE; + ret = JS_UNDEFINED; + if (it->buf_count < 1) + break; + if (windows) { + /* a full window was already yielded */ + if (!it->allow_partial || it->buf_count >= it->count) + break; + ret = js_create_array(ctx, it->buf_count, (JSValueConst *)it->buf); + } else { + ret = js_create_array_free(ctx, it->buf_count, it->buf); + it->buf_count = 0; // ownership transferred + } + if (JS_IsException(ret)) + goto fail_no_close; + *pdone = FALSE; + goto done; + } + if (windows && it->buf_count == it->count) { + /* slide the window */ + JS_FreeValue(ctx, it->buf[0]); + it->buf_count--; + memmove(it->buf, &it->buf[1], + it->buf_count * sizeof(*it->buf)); + } + if (js_resize_array(ctx, (void **)&it->buf, sizeof(*it->buf), + &it->buf_size, it->buf_count + 1)) { + JS_FreeValue(ctx, item); + goto fail_no_close; + } + it->buf[it->buf_count++] = item; + if (it->buf_count == it->count) { + if (windows) { + ret = js_create_array(ctx, it->buf_count, (JSValueConst *)it->buf); + } else { + ret = js_create_array_free(ctx, it->buf_count, it->buf); + it->buf_count = 0; // ownership transferred + } + if (JS_IsException(ret)) + goto fail_no_close; + break; + } + } + } + break; case JS_ITERATOR_HELPER_KIND_DROP: { JSValue item; @@ -46084,7 +46201,7 @@ static JSValue js_iterator_helper_next(JSContext *ctx, JSValueConst this_val, JS_FreeValue(ctx, item); if (*pdone) { ret = JS_UNDEFINED; - goto done; + goto done_drop; } } @@ -46092,6 +46209,7 @@ static JSValue js_iterator_helper_next(JSContext *ctx, JSValueConst this_val, if (JS_IsException(item)) goto fail_no_close; ret = item; + done_drop: ; } break; case JS_ITERATOR_HELPER_KIND_FILTER: @@ -46184,7 +46302,7 @@ static JSValue js_iterator_helper_next(JSContext *ctx, JSValueConst this_val, goto fail_no_close; if (*pdone) { ret = item; - goto done; + break; } index_val = JS_NewInt64(ctx, it->count++); args[0] = item; @@ -46218,15 +46336,23 @@ static JSValue js_iterator_helper_next(JSContext *ctx, JSValueConst this_val, abort(); } - done: it->done = *pdone; + done: it->executing = 0; + if (it->done && it->buf) { + while (it->buf_count > 0) + JS_FreeValue(ctx, it->buf[--it->buf_count]); + js_free(ctx, it->buf); + it->buf = NULL; + it->buf_size = 0; + } return ret; fail: /* close the iterator object, preserving pending exception */ JS_IteratorClose(ctx, it->obj, TRUE); fail_no_close: ret = JS_EXCEPTION; + it->done = TRUE; goto done; } @@ -46238,11 +46364,13 @@ static const JSCFunctionListEntry js_iterator_funcs[] = { }; static const JSCFunctionListEntry js_iterator_proto_funcs[] = { + JS_CFUNC_MAGIC_DEF("chunks", 1, js_create_iterator_helper, JS_ITERATOR_HELPER_KIND_CHUNKS ), JS_CFUNC_MAGIC_DEF("drop", 1, js_create_iterator_helper, JS_ITERATOR_HELPER_KIND_DROP ), JS_CFUNC_MAGIC_DEF("filter", 1, js_create_iterator_helper, JS_ITERATOR_HELPER_KIND_FILTER ), JS_CFUNC_MAGIC_DEF("flatMap", 1, js_create_iterator_helper, JS_ITERATOR_HELPER_KIND_FLAT_MAP ), JS_CFUNC_MAGIC_DEF("map", 1, js_create_iterator_helper, JS_ITERATOR_HELPER_KIND_MAP ), JS_CFUNC_MAGIC_DEF("take", 1, js_create_iterator_helper, JS_ITERATOR_HELPER_KIND_TAKE ), + JS_CFUNC_MAGIC_DEF("windows", 1, js_create_iterator_helper, JS_ITERATOR_HELPER_KIND_WINDOWS ), JS_CFUNC_MAGIC_DEF("every", 1, js_iterator_proto_func, JS_ITERATOR_HELPER_KIND_EVERY ), JS_CFUNC_MAGIC_DEF("find", 1, js_iterator_proto_func, JS_ITERATOR_HELPER_KIND_FIND), JS_CFUNC_MAGIC_DEF("forEach", 1, js_iterator_proto_func, JS_ITERATOR_HELPER_KIND_FOR_EACH ), diff --git a/test262.conf b/test262.conf index fa2954f..713f73e 100644 --- a/test262.conf +++ b/test262.conf @@ -149,7 +149,7 @@ Intl.RelativeTimeFormat Intl.Segmenter IsHTMLDDA Iterator.prototype.join -iterator-chunking=skip +iterator-chunking iterator-helpers iterator-includes iterator-sequencing -- 2.47.3