From f5d710bdc0cd4ab51fb26302a6e391c2d17dbb5b Mon Sep 17 00:00:00 2001 From: "Artem S. Povalyukhin" Date: Sun, 25 Oct 2020 18:29:15 +0300 Subject: [PATCH] Introduced initial iterator support. --- auto/sources | 1 + src/njs_array.c | 54 +++++++ src/njs_builtin.c | 9 ++ src/njs_iterator.c | 299 +++++++++++++++++++++++++++++++++++++++ src/njs_iterator.h | 21 +++ src/njs_main.h | 1 + src/njs_string.c | 27 ++++ src/njs_typed_array.c | 59 ++++++++ src/njs_value.h | 1 + src/njs_vm.h | 4 +- src/test/njs_unit_test.c | 132 +++++++++++++++++ 11 files changed, 607 insertions(+), 1 deletion(-) create mode 100644 src/njs_iterator.c create mode 100644 src/njs_iterator.h diff --git a/auto/sources b/auto/sources index 0c59df99..9391a01e 100644 --- a/auto/sources +++ b/auto/sources @@ -58,6 +58,7 @@ NJS_LIB_SRCS=" \ src/njs_query_string.c \ src/njs_encoding.c \ src/njs_buffer.c \ + src/njs_iterator.c \ " NJS_LIB_TEST_SRCS=" \ diff --git a/src/njs_array.c b/src/njs_array.c index ddffee18..76ebb02e 100644 --- a/src/njs_array.c +++ b/src/njs_array.c @@ -3351,6 +3351,24 @@ njs_array_prototype_copy_within(njs_vm_t *vm, njs_value_t *args, } +static njs_int_t +njs_array_prototype_iterator_obj(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t kind) +{ + njs_int_t ret; + njs_value_t *this; + + this = njs_argument(args, 0); + + ret = njs_value_to_object(vm, this); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + return njs_array_iterator_create(vm, this, &vm->retval, kind); +} + + static const njs_object_prop_t njs_array_prototype_properties[] = { { @@ -3384,6 +3402,15 @@ static const njs_object_prop_t njs_array_prototype_properties[] = .configurable = 1, }, + { + .type = NJS_PROPERTY, + .name = njs_string("entries"), + .value = njs_native_function2(njs_array_prototype_iterator_obj, 0, + NJS_ENUM_BOTH), + .writable = 1, + .configurable = 1, + }, + { .type = NJS_PROPERTY, .name = njs_string("every"), @@ -3463,6 +3490,15 @@ static const njs_object_prop_t njs_array_prototype_properties[] = .configurable = 1, }, + { + .type = NJS_PROPERTY, + .name = njs_string("keys"), + .value = njs_native_function2(njs_array_prototype_iterator_obj, 0, + NJS_ENUM_KEYS), + .writable = 1, + .configurable = 1, + }, + { .type = NJS_PROPERTY, .name = njs_string("lastIndexOf"), @@ -3579,6 +3615,24 @@ static const njs_object_prop_t njs_array_prototype_properties[] = .writable = 1, .configurable = 1, }, + + { + .type = NJS_PROPERTY, + .name = njs_string("values"), + .value = njs_native_function2(njs_array_prototype_iterator_obj, 0, + NJS_ENUM_VALUES), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_wellknown_symbol(NJS_SYMBOL_ITERATOR), + .value = njs_native_function2(njs_array_prototype_iterator_obj, 0, + NJS_ENUM_VALUES), + .writable = 1, + .configurable = 1, + }, }; diff --git a/src/njs_builtin.c b/src/njs_builtin.c index 252796d3..2280cfa2 100644 --- a/src/njs_builtin.c +++ b/src/njs_builtin.c @@ -85,6 +85,8 @@ static const njs_object_type_init_t *const /* Hidden types. */ + &njs_iterator_type_init, + &njs_array_iterator_type_init, &njs_dirent_type_init, &njs_hash_type_init, &njs_hmac_type_init, @@ -294,6 +296,10 @@ njs_builtin_objects_create(njs_vm_t *vm) constructor = shared->constructors; for (i = NJS_OBJ_TYPE_OBJECT; i < NJS_OBJ_TYPE_MAX; i++) { + if (njs_object_type_init[i]->constructor_props == NULL) { + continue; + } + constructor[i] = njs_object_type_init[i]->constructor; constructor[i].object.shared = 0; @@ -360,6 +366,9 @@ njs_builtin_objects_clone(njs_vm_t *vm, njs_value_t *global) vm->prototypes[i].object.__proto__ = typed_array_prototype; } + vm->prototypes[NJS_OBJ_TYPE_ARRAY_ITERATOR].object.__proto__ = + &vm->prototypes[NJS_OBJ_TYPE_ITERATOR].object; + vm->prototypes[NJS_OBJ_TYPE_BUFFER].object.__proto__ = &vm->prototypes[NJS_OBJ_TYPE_UINT8_ARRAY].object; diff --git a/src/njs_iterator.c b/src/njs_iterator.c new file mode 100644 index 00000000..8379f611 --- /dev/null +++ b/src/njs_iterator.c @@ -0,0 +1,299 @@ + +/* + * Copyright (C) Artem S. Povalyukhin + * Copyright (C) NGINX, Inc. + */ + + +#include + + +struct njs_value_iterator_s { + njs_value_t target; + int64_t next; + njs_object_enum_t kind; +}; + + +typedef struct njs_value_iterator_s njs_array_iterator_t; + + +static const njs_value_t string_done = njs_string("done"); +static const njs_value_t string_value = njs_string("value"); + + +njs_int_t +njs_array_iterator_create(njs_vm_t *vm, const njs_value_t *target, + njs_value_t *retval, njs_object_enum_t kind) +{ + njs_object_value_t *ov; + njs_array_iterator_t *it; + + ov = njs_mp_alloc(vm->mem_pool, sizeof(njs_object_value_t)); + if (njs_slow_path(ov == NULL)) { + njs_memory_error(vm); + return NJS_ERROR; + } + + njs_lvlhsh_init(&ov->object.hash); + njs_lvlhsh_init(&ov->object.shared_hash); + ov->object.type = NJS_OBJECT_VALUE; + ov->object.shared = 0; + ov->object.extensible = 1; + ov->object.error_data = 0; + ov->object.fast_array = 0; + + ov->object.__proto__ = + &vm->prototypes[NJS_OBJ_TYPE_ARRAY_ITERATOR].object; + ov->object.slots = NULL; + + it = njs_mp_alloc(vm->mem_pool, sizeof(njs_array_iterator_t)); + if (njs_slow_path(it == NULL)) { + njs_memory_error(vm); + return NJS_ERROR; + } + + /* GC retain it->target */ + it->target = *target; + it->next = 0; + it->kind = kind; + + njs_set_data(&ov->value, it, NJS_DATA_TAG_ARRAY_ITERATOR); + njs_set_object_value(retval, ov); + + return NJS_OK; +} + + +njs_int_t +njs_array_iterator_next(njs_vm_t *vm, njs_value_t *iterator, + njs_value_t *retval) +{ + int64_t length; + njs_int_t ret; + njs_array_t *array, *entry; + njs_typed_array_t *tarray; + const njs_value_t *value; + njs_array_iterator_t *it; + + if (njs_slow_path(!njs_is_valid(njs_object_value(iterator)))) { + return NJS_DECLINED; + } + + it = njs_object_data(iterator); + value = &njs_value_undefined; + + if (njs_is_fast_array(&it->target)) { + array = njs_array(&it->target); + length = array->length; + + if (it->next >= length) { + goto release; + } + + if (it->kind > NJS_ENUM_KEYS && njs_is_valid(&array->start[it->next])) { + value = &array->start[it->next]; + } + + } else if (njs_is_typed_array(&it->target)) { + tarray = njs_typed_array(&it->target); + + if (njs_slow_path(njs_is_detached_buffer(tarray->buffer))) { + njs_type_error(vm, "detached buffer"); + return NJS_ERROR; + } + + length = njs_typed_array_length(tarray); + + if (it->next >= length) { + goto release; + } + + if (it->kind > NJS_ENUM_KEYS) { + njs_set_number(retval, njs_typed_array_prop(tarray, it->next)); + value = retval; + } + + } else { + ret = njs_object_length(vm, &it->target, &length); + if (njs_slow_path(ret == NJS_ERROR)) { + return ret; + } + + if (it->next >= length) { + goto release; + } + + if (it->kind > NJS_ENUM_KEYS) { + ret = njs_value_property_i64(vm, &it->target, it->next, retval); + if (njs_slow_path(ret == NJS_ERROR)) { + return ret; + } + + value = njs_is_valid(retval) ? retval + : &njs_value_undefined; + } + } + + switch (it->kind) { + case NJS_ENUM_KEYS: + njs_set_number(retval, it->next++); + break; + + case NJS_ENUM_VALUES: + it->next++; + *retval = *value; + break; + + case NJS_ENUM_BOTH: + entry = njs_array_alloc(vm, 0, 2, 0); + if (njs_slow_path(entry == NULL)) { + return NJS_ERROR; + } + + njs_set_number(&entry->start[0], it->next++); + entry->start[1] = *value; + + njs_set_array(retval, entry); + break; + + default: + njs_internal_error(vm, "invalid enum kind"); + return NJS_ERROR; + } + + return NJS_OK; + +release: + + /* GC release it->target */ + njs_mp_free(vm->mem_pool, it); + njs_set_invalid(njs_object_value(iterator)); + + return NJS_DECLINED; +} + + +static njs_int_t +njs_iterator_prototype_get_this(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused) +{ + vm->retval = args[0]; + + return NJS_OK; +} + + +static const njs_object_prop_t njs_iterator_prototype_properties[] = +{ + { + .type = NJS_PROPERTY, + .name = njs_wellknown_symbol(NJS_SYMBOL_ITERATOR), + .value = njs_native_function(njs_iterator_prototype_get_this, 0), + .configurable = 1, + .writable = 1, + }, +}; + + +static const njs_object_init_t njs_iterator_prototype_init = { + njs_iterator_prototype_properties, + njs_nitems(njs_iterator_prototype_properties), +}; + + +const njs_object_type_init_t njs_iterator_type_init = { + .prototype_props = &njs_iterator_prototype_init, + .prototype_value = { .object = { .type = NJS_OBJECT } }, +}; + + +static njs_int_t +njs_array_iterator_prototype_next(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t tag) +{ + njs_int_t ret; + njs_bool_t check; + njs_value_t *this; + njs_object_t *object; + njs_object_prop_t *prop_value, *prop_done; + + this = njs_argument(args, 0); + + check = njs_is_object_value(this) + && (njs_is_object_data(this, NJS_DATA_TAG_ARRAY_ITERATOR) + || !njs_is_valid(njs_object_value(this))); + + if (njs_slow_path(!check)) { + njs_type_error(vm, "Method [Array Iterator].prototype.next" + " called on incompatible receiver"); + return NJS_ERROR; + } + + object = njs_object_alloc(vm); + if (njs_slow_path(object == NULL)) { + return NJS_ERROR; + } + + njs_set_object(&vm->retval, object); + + prop_value = njs_object_property_add(vm, &vm->retval, + njs_value_arg(&string_value), 0); + if (njs_slow_path(prop_value == NULL)) { + return NJS_ERROR; + } + + prop_done = njs_object_property_add(vm, &vm->retval, + njs_value_arg(&string_done), 0); + if (njs_slow_path(prop_done == NULL)) { + return NJS_ERROR; + } + + ret = njs_array_iterator_next(vm, this, &prop_value->value); + if (njs_slow_path(ret == NJS_ERROR)) { + return ret; + } + + if (njs_slow_path(ret == NJS_DECLINED)) { + njs_set_undefined(&prop_value->value); + njs_set_boolean(&prop_done->value, 1); + + return NJS_OK; + } + + njs_set_boolean(&prop_done->value, 0); + + return NJS_OK; +} + + +static const njs_object_prop_t njs_array_iterator_prototype_properties[] = +{ + { + .type = NJS_PROPERTY, + .name = njs_string("next"), + .value = njs_native_function2(njs_array_iterator_prototype_next, 0, + NJS_DATA_TAG_ARRAY_ITERATOR), + .configurable = 1, + .writable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_wellknown_symbol(NJS_SYMBOL_TO_STRING_TAG), + .value = njs_string("Array Iterator"), + .configurable = 1, + }, +}; + + +static const njs_object_init_t njs_array_iterator_prototype_init = { + njs_array_iterator_prototype_properties, + njs_nitems(njs_array_iterator_prototype_properties), +}; + + +const njs_object_type_init_t njs_array_iterator_type_init = { + .prototype_props = &njs_array_iterator_prototype_init, + .prototype_value = { .object = { .type = NJS_OBJECT } }, +}; diff --git a/src/njs_iterator.h b/src/njs_iterator.h new file mode 100644 index 00000000..97b534e8 --- /dev/null +++ b/src/njs_iterator.h @@ -0,0 +1,21 @@ + +/* + * Copyright (C) Artem S. Povalyukhin + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NJS_ITERATOR_H_INCLUDED_ +#define _NJS_ITERATOR_H_INCLUDED_ + + +njs_int_t njs_array_iterator_create(njs_vm_t *vm, const njs_value_t *src, + njs_value_t *dst, njs_object_enum_t kind); + +njs_int_t njs_array_iterator_next(njs_vm_t *vm, njs_value_t *iterator, + njs_value_t *retval); + + +extern const njs_object_type_init_t njs_iterator_type_init; +extern const njs_object_type_init_t njs_array_iterator_type_init; + +#endif /* _NJS_ITERATOR_H_INCLUDED_ */ diff --git a/src/njs_main.h b/src/njs_main.h index 3102c62b..6e007b83 100644 --- a/src/njs_main.h +++ b/src/njs_main.h @@ -71,6 +71,7 @@ #include #include #include +#include #include #include diff --git a/src/njs_string.c b/src/njs_string.c index 8cf03c89..68e198d6 100644 --- a/src/njs_string.c +++ b/src/njs_string.c @@ -3845,6 +3845,24 @@ njs_string_prototype_replace(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, } +static njs_int_t +njs_string_prototype_iterator_obj(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t kind) +{ + njs_int_t ret; + njs_value_t *this; + + this = njs_argument(args, 0); + + ret = njs_string_object_validate(vm, this); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + return njs_array_iterator_create(vm, this, &vm->retval, kind); +} + + double njs_string_to_number(const njs_value_t *value, njs_bool_t parse_float) { @@ -4327,6 +4345,15 @@ static const njs_object_prop_t njs_string_prototype_properties[] = .writable = 1, .configurable = 1, }, + + { + .type = NJS_PROPERTY, + .name = njs_wellknown_symbol(NJS_SYMBOL_ITERATOR), + .value = njs_native_function2(njs_string_prototype_iterator_obj, 0, + NJS_ENUM_VALUES), + .writable = 1, + .configurable = 1, + }, }; diff --git a/src/njs_typed_array.c b/src/njs_typed_array.c index c74fbf03..48994541 100644 --- a/src/njs_typed_array.c +++ b/src/njs_typed_array.c @@ -2183,6 +2183,29 @@ njs_typed_array_prototype_join(njs_vm_t *vm, njs_value_t *args, } +static njs_int_t +njs_typed_array_prototype_iterator_obj(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t kind) +{ + njs_value_t *this; + njs_typed_array_t *array; + + this = njs_argument(args, 0); + if (njs_slow_path(!njs_is_typed_array(this))) { + njs_type_error(vm, "this is not a typed array"); + return NJS_ERROR; + } + + array = njs_typed_array(this); + if (njs_slow_path(njs_is_detached_buffer(array->buffer))) { + njs_type_error(vm, "detached buffer"); + return NJS_ERROR; + } + + return njs_array_iterator_create(vm, this, &vm->retval, kind); +} + + static njs_int_t njs_typed_array_constructor_intrinsic(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused) @@ -2324,6 +2347,15 @@ static const njs_object_prop_t njs_typed_array_prototype_properties[] = .configurable = 1, }, + { + .type = NJS_PROPERTY, + .name = njs_string("entries"), + .value = njs_native_function2(njs_typed_array_prototype_iterator_obj, 0, + NJS_ENUM_BOTH), + .writable = 1, + .configurable = 1, + }, + { .type = NJS_PROPERTY, .name = njs_string("every"), @@ -2401,6 +2433,15 @@ static const njs_object_prop_t njs_typed_array_prototype_properties[] = .configurable = 1, }, + { + .type = NJS_PROPERTY, + .name = njs_string("keys"), + .value = njs_native_function2(njs_typed_array_prototype_iterator_obj, 0, + NJS_ENUM_KEYS), + .writable = 1, + .configurable = 1, + }, + { .type = NJS_PROPERTY, .name = njs_string("lastIndexOf"), @@ -2490,6 +2531,24 @@ static const njs_object_prop_t njs_typed_array_prototype_properties[] = .writable = 1, .configurable = 1, }, + + { + .type = NJS_PROPERTY, + .name = njs_string("values"), + .value = njs_native_function2(njs_typed_array_prototype_iterator_obj, 0, + NJS_ENUM_VALUES), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_wellknown_symbol(NJS_SYMBOL_ITERATOR), + .value = njs_native_function2(njs_typed_array_prototype_iterator_obj, 0, + NJS_ENUM_VALUES), + .writable = 1, + .configurable = 1, + }, }; diff --git a/src/njs_value.h b/src/njs_value.h index b8aa7739..bd9c6725 100644 --- a/src/njs_value.h +++ b/src/njs_value.h @@ -84,6 +84,7 @@ typedef enum { NJS_DATA_TAG_CRYPTO_HMAC, NJS_DATA_TAG_TEXT_ENCODER, NJS_DATA_TAG_TEXT_DECODER, + NJS_DATA_TAG_ARRAY_ITERATOR, NJS_DATA_TAG_MAX } njs_data_tag_t; diff --git a/src/njs_vm.h b/src/njs_vm.h index cb1af089..87276213 100644 --- a/src/njs_vm.h +++ b/src/njs_vm.h @@ -89,7 +89,9 @@ typedef enum { NJS_OBJ_TYPE_TEXT_ENCODER, NJS_OBJ_TYPE_BUFFER, -#define NJS_OBJ_TYPE_HIDDEN_MIN (NJS_OBJ_TYPE_FS_DIRENT) +#define NJS_OBJ_TYPE_HIDDEN_MIN (NJS_OBJ_TYPE_ITERATOR) + NJS_OBJ_TYPE_ITERATOR, + NJS_OBJ_TYPE_ARRAY_ITERATOR, NJS_OBJ_TYPE_FS_DIRENT, NJS_OBJ_TYPE_CRYPTO_HASH, NJS_OBJ_TYPE_CRYPTO_HMAC, diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c index 0256d4a0..4b8b5055 100644 --- a/src/test/njs_unit_test.c +++ b/src/test/njs_unit_test.c @@ -6864,6 +6864,138 @@ static njs_unit_test_t njs_test[] = { njs_str("[1,2].sort(1)"), njs_str("TypeError: comparefn must be callable or undefined") }, + /* + Array.prototype.keys() + Array.prototype.values() + Array.prototype.entries() + */ + + { njs_str("['keys', 'values', 'entries', Symbol.iterator]" + ".every((x) => typeof Array.prototype[x] == 'function')"), + njs_str("true") }, + + { njs_str("['keys', 'values', 'entries', Symbol.iterator]" + ".every((x) => Array.prototype[x].length === 0)"), + njs_str("true") }, + +#if 0 + { njs_str("Array.prototype[Symbol.iterator] === Array.prototype.values"), + njs_str("true") }, +#endif + + { njs_str("['keys', 'values', 'entries', Symbol.iterator]" + ".every((x) => typeof [][x]() == 'object')"), + njs_str("true") }, + + { njs_str("['keys', 'values', 'entries', Symbol.iterator]" + ".every((x) => typeof [][x]().next == 'function')"), + njs_str("true") }, + + { njs_str("var i = [1,2,3].keys();" + "[i.next(), i.next(), i.next(), i.next()].map((x) => x.value)"), + njs_str("0,1,2,") }, + + { njs_str("var i = [1,2,3].values();" + "[i.next(), i.next(), i.next(), i.next()].map((x) => x.value)"), + njs_str("1,2,3,") }, + + { njs_str("var a = [], i = a.values();" + "a.push(1); a.push(2); a.push(3);" + "[i.next(), i.next(), i.next(), i.next()].map((x) => x.value)"), + njs_str("1,2,3,") }, + + { njs_str("var a = [], i = a.values(); i.next();" + "a.push(1); a.push(2); a.push(3);" + "[i.next(), i.next(), i.next(), i.next()].map((x) => x.value)"), + njs_str(",,,") }, + + { njs_str("var i = [1,2,3].entries();" + "[i.next(), i.next(), i.next(), i.next()].map((x) => x.value)"), + njs_str("0,1,1,2,2,3,") }, + + { njs_str("var i = Array.prototype.keys.call('abc');" + "[i.next(), i.next(), i.next(), i.next()].map((x) => x.done)"), + njs_str("false,false,false,true") }, + + { njs_str("var i = Array.prototype.values.call('abc');" + "[i.next(), i.next(), i.next(), i.next()].map((x) => x.value)"), + njs_str("a,b,c,") }, + + { njs_str("var x = [true, 1, Symbol()];" + "x.map((x) => Array.prototype.keys.call(x).next()).every((x) => x.done)"), + njs_str("true") }, + + { njs_str("var x = [true, 1, Symbol()];" + "x.forEach((x) => Object.getPrototypeOf(Object(x)).length = 1);" + "x.map((x) => Array.prototype.keys.call(x).next()).every((x) => !x.done)"), + njs_str("true") }, + + /* + TypedArray.prototype.keys() + TypedArray.prototype.values() + TypedArray.prototype.entries() + */ + + { njs_str("['keys', 'values', 'entries', Symbol.iterator]" + ".every((x) => typeof Buffer.prototype[x] == 'function')"), + njs_str("true") }, + + { njs_str("var i = Buffer.from([1,2,3]).keys();" + "[i.next(), i.next(), i.next(), i.next()].map((x) => x.value)"), + njs_str("0,1,2,") }, + + { njs_str("var i = Buffer.from([1,2,3]).values();" + "[i.next(), i.next(), i.next(), i.next()].map((x) => x.value)"), + njs_str("1,2,3,") }, + + { njs_str("var i = Buffer.from([1,2,3]).entries();" + "[i.next(), i.next(), i.next(), i.next()].map((x) => x.value)"), + njs_str("0,1,1,2,2,3,") }, + + { njs_str("[true, 1, Symbol(), 'test', [], { length: 1 }]" + ".map((x) => { try { Buffer.prototype.keys.call(x); return x; } catch (e) { return e; } })" + ".every((x) => x instanceof TypeError)"), + njs_str("true") }, + + /* %IteratorPrototype% */ + + { njs_str("var x = Object.getPrototypeOf(Object.getPrototypeOf([].keys()));" + "typeof x[Symbol.iterator] == 'function'"), + njs_str("true") }, + + { njs_str("var x = Object.getPrototypeOf(Object.getPrototypeOf([].keys()));" + "x[Symbol.iterator]() === x"), + njs_str("true") }, + + /* %ArrayIteratorPrototype% */ + + { njs_str("var x = Object.getPrototypeOf([].keys());" + "typeof x.next == 'function'"), + njs_str("true") }, + + { njs_str("var x = Object.getPrototypeOf([].keys());" + "x[Symbol.toStringTag] == 'Array Iterator'"), + njs_str("true") }, + + /* %StringIteratorPrototype% */ + + { njs_str("typeof String.prototype[Symbol.iterator] == 'function'"), + njs_str("true") }, + + { njs_str("var x = Object.getPrototypeOf(''[Symbol.iterator]());" + "typeof x.next == 'function'"), + njs_str("true") }, + +#if 0 + { njs_str("var x = Object.getPrototypeOf(''[Symbol.iterator]());" + "x[Symbol.toStringTag] == 'String Iterator'"), + njs_str("true") }, +#else + { njs_str("var x = Object.getPrototypeOf(''[Symbol.iterator]());" + "x[Symbol.toStringTag] == 'Array Iterator'"), + njs_str("true") }, +#endif + /* Template literal. */ { njs_str("`"), -- 2.47.3