]> git.kaiwu.me - njs.git/commitdiff
Fixed Array prototype functions according to the specification.
authorAlexander Borisov <alexander.borisov@nginx.com>
Wed, 28 Aug 2019 11:59:28 +0000 (14:59 +0300)
committerAlexander Borisov <alexander.borisov@nginx.com>
Wed, 28 Aug 2019 11:59:28 +0000 (14:59 +0300)
The following fuctions were fixed:
every,  filter, find, findIndex, forEach, map, reduce, some.

src/njs_array.c
src/njs_object.c
src/njs_object.h
src/njs_value.c
src/njs_value.h
src/test/njs_unit_test.c

index 3da31f033293eb92aadb8ac43b3e37bced4855f4..949cc28ae4120ef6b04ed9fbd972c5ac3a2d0619 100644 (file)
@@ -8,6 +8,19 @@
 #include <njs_main.h>
 
 
+typedef struct {
+    njs_function_t   *function;
+    njs_value_t      *this_arg;
+    njs_value_t      *value;
+
+    njs_array_t      *array;
+} njs_array_interator_args_t;
+
+
+typedef njs_int_t (*njs_array_iterator_handler_t)(njs_vm_t *vm,
+    njs_array_interator_args_t *args, njs_value_t *entry, uint32_t n);
+
+
 static njs_int_t njs_array_prototype_slice_copy(njs_vm_t *vm,
     njs_value_t *this, int64_t start, int64_t length);
 static njs_value_t *njs_array_copy(njs_value_t *dst, njs_value_t *src);
@@ -1349,54 +1362,194 @@ njs_array_prototype_fill(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
 
 
 njs_inline njs_int_t
-njs_array_iterator_call(njs_vm_t *vm, njs_function_t *function,
-    const njs_value_t *this_arg, const njs_value_t *value, uint32_t n,
-    const njs_value_t *array)
+njs_array_iterator_call(njs_vm_t *vm, njs_array_interator_args_t *args,
+    const njs_value_t *entry, uint32_t n)
 {
     njs_value_t  arguments[3];
 
     /* GC: array elt, array */
 
-    arguments[0] = *value;
+    arguments[0] = *entry;
     njs_set_number(&arguments[1], n);
-    arguments[2] = *array;
+    arguments[2] = *args->value;
 
-    return njs_function_call(vm, function, this_arg, arguments, 3,
+    return njs_function_call(vm, args->function, args->this_arg, arguments, 3,
                              &vm->retval);
 }
 
 
-static njs_int_t
-njs_array_prototype_for_each(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
-    njs_index_t unused)
+njs_inline njs_int_t
+njs_array_iterator(njs_vm_t *vm, njs_array_interator_args_t *args,
+    njs_array_iterator_handler_t handler, uint32_t length)
 {
-    uint32_t           i, length;
+    uint32_t           i;
     njs_int_t          ret;
-    njs_value_t        *array, *value, *this_arg;
-    njs_function_t     *function;
+    njs_value_t        *entry, *value, character, index, string_obj, prop;
+    njs_object_t       *object;
+    const u_char       *p, *end, *pos;
+    njs_string_prop_t  string_prop;
 
-    if (nargs < 2 || !njs_is_array(&args[0]) || !njs_is_function(&args[1])) {
-        njs_type_error(vm, "unexpected iterator arguments");
-        return NJS_ERROR;
+    value = args->value;
+
+    if (njs_is_array(value)) {
+        if (njs_slow_path(!njs_object_hash_is_empty(value))) {
+            goto process_object;
+        }
+
+        length = njs_array_len(value);
+
+        for (i = 0; i < length; i++) {
+            entry = &njs_array_start(value)[i];
+
+            ret = handler(vm, args, entry, i);
+            if (njs_slow_path(ret != NJS_OK)) {
+                if (ret > 0) {
+                    return NJS_DECLINED;
+                }
+
+                return NJS_ERROR;
+            }
+
+            length = njs_min(length, njs_array_len(value));
+        }
+
+        return NJS_OK;
     }
 
-    array = &args[0];
-    length = njs_array_len(array);
-    function = njs_function(&args[1]);
-    this_arg = njs_arg(args, nargs, 2);
+    if (njs_is_string(value) || njs_is_object_string(value)) {
+
+        if (njs_is_string(value)) {
+            object = njs_object_value_alloc(vm, value, NJS_STRING);
+            if (njs_slow_path(object == NULL)) {
+                return NJS_ERROR;
+            }
+
+            njs_set_type_object(&string_obj, object, NJS_OBJECT_STRING);
+
+            args->value = &string_obj;
+        }
+        else {
+            value = njs_object_value(value);
+        }
+
+        length = (uint32_t) njs_string_prop(&string_prop, value);
+
+        p = string_prop.start;
+        end = p + string_prop.size;
+
+        if (length == string_prop.size) {
+            /* Byte or ASCII string. */
+
+            for (i = 0; i < length; i++) {
+                /* This cannot fail. */
+                (void) njs_string_new(vm, &character, p++, 1, 1);
+
+                ret = handler(vm, args, &character, i);
+                if (njs_slow_path(ret != NJS_OK)) {
+                    if (ret > 0) {
+                        return NJS_DECLINED;
+                    }
+
+                    return NJS_ERROR;
+                }
+            }
+
+        } else {
+            /* UTF-8 string. */
+
+            for (i = 0; i < length; i++) {
+                pos = njs_utf8_next(p, end);
+
+                /* This cannot fail. */
+                (void) njs_string_new(vm, &character, p, pos - p, 1);
+
+                ret = handler(vm, args, &character, i);
+                if (njs_slow_path(ret != NJS_OK)) {
+                    if (ret > 0) {
+                        return NJS_DECLINED;
+                    }
+
+                    return NJS_ERROR;
+                }
+
+                p = pos;
+            }
+        }
+
+        return NJS_OK;
+    }
+
+    if (!njs_is_object(value)) {
+        return NJS_OK;
+    }
+
+process_object:
+
+    if (length > NJS_ARRAY_MAX_LENGTH) {
+        ret = njs_object_length(vm, value, &length);
+        if (njs_slow_path(ret != NJS_OK)) {
+            return NJS_ERROR;
+        }
+    }
 
     for (i = 0; i < length; i++) {
-        value = &njs_array_start(array)[i];
+        njs_uint32_to_string(&index, i);
+
+        ret = njs_value_property(vm, value, &index, &prop);
+        if (njs_slow_path(ret == NJS_ERROR)) {
+            return ret;
+        }
 
-        if (njs_is_valid(value)) {
-            ret = njs_array_iterator_call(vm, function, this_arg, value, i,
-                                          array);
+        if (ret != NJS_DECLINED) {
+            ret = handler(vm, args, &prop, i);
             if (njs_slow_path(ret != NJS_OK)) {
-                return ret;
+                if (ret > 0) {
+                    return NJS_DECLINED;
+                }
+
+                return NJS_ERROR;
             }
         }
+    }
 
-        length = njs_min(length, njs_array_len(array));
+    return NJS_OK;
+}
+
+
+static njs_int_t
+njs_array_handler_for_each(njs_vm_t *vm, njs_array_interator_args_t *args,
+    njs_value_t *entry, uint32_t n)
+{
+    if (njs_is_valid(entry)) {
+        return njs_array_iterator_call(vm, args, entry, n);
+    }
+
+    return NJS_OK;
+}
+
+
+static njs_int_t
+njs_array_prototype_for_each(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
+    njs_index_t unused)
+{
+    njs_int_t                   ret;
+    njs_array_interator_args_t  iargs;
+
+    if (njs_is_null_or_undefined(njs_arg(args, nargs, 0))
+        || !njs_is_function(njs_arg(args, nargs, 1)))
+    {
+        njs_type_error(vm, "unexpected iterator arguments");
+        return NJS_ERROR;
+    }
+
+    iargs.value = njs_argument(args, 0);
+    iargs.function = njs_function(&args[1]);
+    iargs.this_arg = njs_arg(args, nargs, 2);
+
+    ret = njs_array_iterator(vm, &iargs, njs_array_handler_for_each,
+                             NJS_ARRAY_MAX_LENGTH + 1);
+    if (njs_slow_path(ret != NJS_OK)) {
+        return ret;
     }
 
     njs_set_undefined(&vm->retval);
@@ -1405,48 +1558,79 @@ njs_array_prototype_for_each(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
 }
 
 
+static njs_int_t
+njs_array_handler_some(njs_vm_t *vm, njs_array_interator_args_t *args,
+    njs_value_t *entry, uint32_t n)
+{
+    njs_int_t  ret;
+
+    if (njs_is_valid(entry)) {
+        ret = njs_array_iterator_call(vm, args, entry, n);
+        if (njs_slow_path(ret != NJS_OK)) {
+            return ret;
+        }
+
+        if (njs_is_true(&vm->retval)) {
+            vm->retval = njs_value_true;
+
+            return 1;
+        }
+    }
+
+    return NJS_OK;
+}
+
+
 static njs_int_t
 njs_array_prototype_some(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
     njs_index_t unused)
 {
-    uint32_t           i, length;
-    njs_int_t          ret;
-    njs_value_t        *array, *value, *this_arg;
-    njs_function_t     *function;
-    const njs_value_t  *retval;
+    njs_int_t                   ret;
+    njs_array_interator_args_t  iargs;
 
-    if (nargs < 2 || !njs_is_array(&args[0]) || !njs_is_function(&args[1])) {
+    if (njs_is_null_or_undefined(njs_arg(args, nargs, 0))
+        || !njs_is_function(njs_arg(args, nargs, 1)))
+    {
         njs_type_error(vm, "unexpected iterator arguments");
         return NJS_ERROR;
     }
 
-    array = &args[0];
-    length = njs_array_len(array);
-    function = njs_function(&args[1]);
-    this_arg = njs_arg(args, nargs, 2);
+    iargs.value = njs_argument(args, 0);
+    iargs.function = njs_function(&args[1]);
+    iargs.this_arg = njs_arg(args, nargs, 2);
 
-    retval = &njs_value_false;
+    ret = njs_array_iterator(vm, &iargs, njs_array_handler_some,
+                             NJS_ARRAY_MAX_LENGTH + 1);
+    if (njs_slow_path(ret == NJS_ERROR)) {
+        return ret;
+    }
 
-    for (i = 0; i < length; i++) {
-        value = &njs_array_start(array)[i];
+    if (ret != NJS_DECLINED) {
+        vm->retval = njs_value_false;
+    }
 
-        if (njs_is_valid(value)) {
-            ret = njs_array_iterator_call(vm, function, this_arg, value, i,
-                                          array);
-            if (njs_slow_path(ret != NJS_OK)) {
-                return ret;
-            }
+    return NJS_OK;
+}
 
-            if (njs_is_true(&vm->retval)) {
-                retval = &njs_value_true;
-                break;
-            }
+
+static njs_int_t
+njs_array_handler_every(njs_vm_t *vm, njs_array_interator_args_t *args,
+    njs_value_t *entry, uint32_t n)
+{
+    njs_int_t  ret;
+
+    if (njs_is_valid(entry)) {
+        ret = njs_array_iterator_call(vm, args, entry, n);
+        if (njs_slow_path(ret != NJS_OK)) {
+            return ret;
         }
 
-        length = njs_min(length, njs_array_len(array));
-    }
+        if (!njs_is_true(&vm->retval)) {
+            vm->retval = njs_value_false;
 
-    vm->retval = *retval;
+            return 1;
+        }
+    }
 
     return NJS_OK;
 }
@@ -1456,45 +1640,58 @@ static njs_int_t
 njs_array_prototype_every(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
     njs_index_t unused)
 {
-    uint32_t           i, length;
-    njs_int_t          ret;
-    njs_value_t        *array, *value, *this_arg;
-    njs_function_t     *function;
-    const njs_value_t  *retval;
+    njs_int_t                   ret;
+    njs_array_interator_args_t  iargs;
 
-    if (nargs < 2 || !njs_is_array(&args[0]) || !njs_is_function(&args[1])) {
+    if (njs_is_null_or_undefined(njs_arg(args, nargs, 0))
+        || !njs_is_function(njs_arg(args, nargs, 1)))
+    {
         njs_type_error(vm, "unexpected iterator arguments");
         return NJS_ERROR;
     }
 
-    array = &args[0];
-    length = njs_array_len(array);
-    function = njs_function(&args[1]);
-    this_arg = njs_arg(args, nargs, 2);
+    iargs.value = njs_argument(args, 0);
+    iargs.function = njs_function(&args[1]);
+    iargs.this_arg = njs_arg(args, nargs, 2);
 
-    retval = &njs_value_true;
+    ret = njs_array_iterator(vm, &iargs, njs_array_handler_every,
+                             NJS_ARRAY_MAX_LENGTH + 1);
+    if (njs_slow_path(ret == NJS_ERROR)) {
+        return ret;
+    }
+
+    if (ret != NJS_DECLINED) {
+        vm->retval = njs_value_true;
+    }
+
+    return NJS_OK;
+}
 
-    for (i = 0; i < length; i++) {
-        value = &njs_array_start(array)[i];
 
-        if (njs_is_valid(value)) {
-            ret = njs_array_iterator_call(vm, function, this_arg, value, i,
-                                          array);
+static njs_int_t
+njs_array_handler_filter(njs_vm_t *vm, njs_array_interator_args_t *args,
+    njs_value_t *entry, uint32_t n)
+{
+    njs_int_t    ret;
+    njs_value_t  copy;
+
+    if (njs_is_valid(entry)) {
+        copy = *entry;
+
+        ret = njs_array_iterator_call(vm, args, &copy, n);
+        if (njs_slow_path(ret != NJS_OK)) {
+            return ret;
+        }
+
+        if (njs_is_true(&vm->retval)) {
+
+            ret = njs_array_add(vm, args->array, &copy);
             if (njs_slow_path(ret != NJS_OK)) {
                 return ret;
             }
-
-            if (!njs_is_true(&vm->retval)) {
-                retval = &njs_value_false;
-                break;
-            }
         }
-
-        length = njs_min(length, njs_array_len(array));
     }
 
-    vm->retval = *retval;
-
     return NJS_OK;
 }
 
@@ -1503,49 +1700,61 @@ static njs_int_t
 njs_array_prototype_filter(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
     njs_index_t unused)
 {
-    uint32_t        i, length;
-    njs_int_t       ret;
-    njs_array_t     *retval;
-    njs_value_t     *array, value, *this_arg;
-    njs_function_t  *function;
+    njs_int_t                   ret;
+    njs_array_interator_args_t  iargs;
 
-    if (nargs < 2 || !njs_is_array(&args[0]) || !njs_is_function(&args[1])) {
+    if (njs_is_null_or_undefined(njs_arg(args, nargs, 0))
+        || !njs_is_function(njs_arg(args, nargs, 1)))
+    {
         njs_type_error(vm, "unexpected iterator arguments");
         return NJS_ERROR;
     }
 
-    array = &args[0];
-    length = njs_array_len(array);
-    function = njs_function(&args[1]);
-    this_arg = njs_arg(args, nargs, 2);
+    iargs.value = njs_argument(args, 0);
+    iargs.function = njs_function(&args[1]);
+    iargs.this_arg = njs_arg(args, nargs, 2);
 
-    retval = njs_array_alloc(vm, 0, NJS_ARRAY_SPARE);
-    if (njs_slow_path(retval == NULL)) {
+    iargs.array = njs_array_alloc(vm, 0, NJS_ARRAY_SPARE);
+    if (njs_slow_path(iargs.array == NULL)) {
         return NJS_ERROR;
     }
 
-    for (i = 0; i < length; i++) {
-        value = njs_array_start(array)[i];
+    ret = njs_array_iterator(vm, &iargs, njs_array_handler_filter,
+                             NJS_ARRAY_MAX_LENGTH + 1);
+    if (njs_slow_path(ret != NJS_OK)) {
+        return ret;
+    }
 
-        if (njs_is_valid(&value)) {
-            ret = njs_array_iterator_call(vm, function, this_arg, &value, i,
-                                          array);
-            if (njs_slow_path(ret != NJS_OK)) {
-                return ret;
-            }
+    njs_set_array(&vm->retval, iargs.array);
 
-            if (njs_is_true(&vm->retval)) {
-                ret = njs_array_add(vm, retval, &value);
-                if (njs_slow_path(ret != NJS_OK)) {
-                    return ret;
-                }
-            }
-        }
+    return NJS_OK;
+}
+
+
+static njs_int_t
+njs_array_handler_find(njs_vm_t *vm, njs_array_interator_args_t *args,
+    njs_value_t *entry, uint32_t n)
+{
+    njs_int_t    ret;
+    njs_value_t  copy;
+
+    if (njs_is_valid(entry)) {
+        copy = *entry;
 
-        length = njs_min(length, njs_array_len(array));
+    } else {
+        njs_set_undefined(&copy);
     }
 
-    njs_set_array(&vm->retval, retval);
+    ret = njs_array_iterator_call(vm, args, &copy, n);
+    if (njs_slow_path(ret != NJS_OK)) {
+        return ret;
+    }
+
+    if (njs_is_true(&vm->retval)) {
+        vm->retval = copy;
+
+        return 1;
+    }
 
     return NJS_OK;
 }
@@ -1555,45 +1764,58 @@ static njs_int_t
 njs_array_prototype_find(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
     njs_index_t unused)
 {
-    uint32_t           i, length;
-    njs_int_t          ret;
-    njs_value_t        *array, value, *this_arg;
-    njs_function_t     *function;
-    const njs_value_t  *retval;
+    njs_int_t                   ret;
+    njs_array_interator_args_t  iargs;
 
-    if (nargs < 2 || !njs_is_array(&args[0]) || !njs_is_function(&args[1])) {
+    if (njs_is_null_or_undefined(njs_arg(args, nargs, 0))
+        || !njs_is_function(njs_arg(args, nargs, 1)))
+    {
         njs_type_error(vm, "unexpected iterator arguments");
         return NJS_ERROR;
     }
 
-    array = &args[0];
-    length = njs_array_len(array);
-    function = njs_function(&args[1]);
-    this_arg = njs_arg(args, nargs, 2);
+    iargs.value = njs_argument(args, 0);
+    iargs.function = njs_function(&args[1]);
+    iargs.this_arg = njs_arg(args, nargs, 2);
 
-    retval = &njs_value_undefined;
+    ret = njs_array_iterator(vm, &iargs, njs_array_handler_find,
+                             NJS_ARRAY_MAX_LENGTH + 1);
+    if (njs_slow_path(ret == NJS_ERROR)) {
+        return ret;
+    }
 
-    for (i = 0; i < length; i++) {
-        value = njs_array_start(array)[i];
+    if (ret != NJS_DECLINED) {
+        vm->retval = njs_value_undefined;
+    }
 
-        if (!njs_is_valid(&value)) {
-            njs_set_undefined(&value);
-        }
+    return NJS_OK;
+}
 
-        ret = njs_array_iterator_call(vm, function, this_arg, &value, i, array);
-        if (njs_slow_path(ret != NJS_OK)) {
-            return ret;
-        }
 
-        if (njs_is_true(&vm->retval)) {
-            retval = &value;
-            break;
-        }
+static njs_int_t
+njs_array_handler_find_index(njs_vm_t *vm, njs_array_interator_args_t *args,
+    njs_value_t *entry, uint32_t n)
+{
+    njs_int_t    ret;
+    njs_value_t  copy;
 
-        length = njs_min(length, njs_array_len(array));
+    if (njs_is_valid(entry)) {
+        copy = *entry;
+
+    } else {
+        njs_set_undefined(&copy);
     }
 
-    vm->retval = *retval;
+    ret = njs_array_iterator_call(vm, args, &copy, n);
+    if (njs_slow_path(ret != NJS_OK)) {
+        return ret;
+    }
+
+    if (njs_is_true(&vm->retval)) {
+        njs_set_number(&vm->retval, n);
+
+        return 1;
+    }
 
     return NJS_OK;
 }
@@ -1603,46 +1825,56 @@ static njs_int_t
 njs_array_prototype_find_index(njs_vm_t *vm, njs_value_t *args,
     njs_uint_t nargs, njs_index_t unused)
 {
-    double          index;
-    uint32_t        i, length;
-    njs_int_t       ret;
-    njs_value_t     *array, value, *this_arg;
-    njs_function_t  *function;
+    njs_int_t                   ret;
+    njs_array_interator_args_t  iargs;
 
-    if (nargs < 2 || !njs_is_array(&args[0]) || !njs_is_function(&args[1])) {
+    if (njs_is_null_or_undefined(njs_arg(args, nargs, 0))
+        || !njs_is_function(njs_arg(args, nargs, 1)))
+    {
         njs_type_error(vm, "unexpected iterator arguments");
         return NJS_ERROR;
     }
 
-    array = &args[0];
-    length = njs_array_len(array);
-    function = njs_function(&args[1]);
-    this_arg = njs_arg(args, nargs, 2);
+    iargs.value = njs_argument(args, 0);
+    iargs.function = njs_function(&args[1]);
+    iargs.this_arg = njs_arg(args, nargs, 2);
 
-    index = -1;
+    ret = njs_array_iterator(vm, &iargs, njs_array_handler_find_index,
+                             NJS_ARRAY_MAX_LENGTH + 1);
+    if (njs_slow_path(ret == NJS_ERROR)) {
+        return ret;
+    }
 
-    for (i = 0; i < length; i++) {
-        value = njs_array_start(array)[i];
+    if (ret != NJS_DECLINED) {
+        njs_set_number(&vm->retval, -1);
+    }
 
-        if (!njs_is_valid(&value)) {
-            njs_set_undefined(&value);
-        }
+    return NJS_OK;
+}
 
-        ret = njs_array_iterator_call(vm, function, this_arg, &value, i, array);
+
+static njs_int_t
+njs_array_handler_map(njs_vm_t *vm, njs_array_interator_args_t *args,
+    njs_value_t *entry, uint32_t n)
+{
+    njs_int_t    ret;
+    njs_array_t  *retval;
+
+    retval = args->array;
+
+    njs_set_invalid(&retval->start[n]);
+
+    if (njs_is_valid(entry)) {
+        ret = njs_array_iterator_call(vm, args, entry, n);
         if (njs_slow_path(ret != NJS_OK)) {
             return ret;
         }
 
-        if (njs_is_true(&vm->retval)) {
-            index = i;
-            break;
+        if (njs_is_valid(&vm->retval)) {
+            retval->start[n] = vm->retval;
         }
-
-        length = njs_min(length, njs_array_len(array));
     }
 
-    njs_set_number(&vm->retval, index);
-
     return NJS_OK;
 }
 
@@ -1651,75 +1883,93 @@ static njs_int_t
 njs_array_prototype_map(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
     njs_index_t unused)
 {
-    uint32_t        i, length, size;
-    njs_int_t       ret;
-    njs_array_t     *retval;
-    njs_value_t     *array, *value, *this_arg;
-    njs_function_t  *function;
+    uint32_t                    length, i;
+    njs_int_t                   ret;
+    njs_array_t                 *array;
+    njs_array_interator_args_t  iargs;
 
-    if (nargs < 2 || !njs_is_array(&args[0]) || !njs_is_function(&args[1])) {
+    if (njs_is_null_or_undefined(njs_arg(args, nargs, 0))
+        || !njs_is_function(njs_arg(args, nargs, 1)))
+    {
         njs_type_error(vm, "unexpected iterator arguments");
         return NJS_ERROR;
     }
 
-    array = &args[0];
-    length = njs_array_len(array);
-    function = njs_function(&args[1]);
-    this_arg = njs_arg(args, nargs, 2);
+    iargs.value = njs_argument(args, 0);
+    iargs.function = njs_function(&args[1]);
+    iargs.this_arg = njs_arg(args, nargs, 2);
 
-    size = length;
+    ret = njs_value_length(vm, iargs.value, &length);
+    if (njs_slow_path(ret != NJS_OK)) {
+        return ret;
+    }
 
-    retval = njs_array_alloc(vm, length, 0);
-    if (njs_slow_path(retval == NULL)) {
+    iargs.array = njs_array_alloc(vm, length, 0);
+    if (njs_slow_path(iargs.array == NULL)) {
         return NJS_ERROR;
     }
 
-    for (i = 0; i < length; i++) {
-        njs_set_invalid(&retval->start[i]);
-
-        value = &njs_array_start(array)[i];
+    if (length > 0) {
+        ret = njs_array_iterator(vm, &iargs, njs_array_handler_map, length);
+        if (njs_slow_path(ret != NJS_OK)) {
+            return ret;
+        }
 
-        if (njs_is_valid(value)) {
-            ret = njs_array_iterator_call(vm, function, this_arg, value, i,
-                                          array);
-            if (njs_slow_path(ret != NJS_OK)) {
-                return ret;
-            }
+        if (njs_is_array(iargs.value)
+            && njs_object_hash_is_empty(iargs.value))
+        {
+            array = iargs.array;
 
-            if (njs_is_valid(&vm->retval)) {
-                retval->start[i] = vm->retval;
+            for (i = njs_array_len(iargs.value); i < length; i++) {
+                njs_set_invalid(&array->start[i]);
             }
         }
-
-        length = njs_min(length, njs_array_len(array));
     }
 
-    for ( ; i < size; i++) {
-        njs_set_invalid(&retval->start[i]);
-    }
-
-    njs_set_array(&vm->retval, retval);
+    njs_set_array(&vm->retval, iargs.array);
 
     return NJS_OK;
 }
 
 
 njs_inline njs_int_t
-njs_array_iterator_reduce(njs_vm_t *vm, njs_function_t *function,
-    njs_value_t *accumulator, njs_value_t *value, uint32_t n,
-    njs_value_t *array)
+njs_array_iterator_reduce(njs_vm_t *vm, njs_array_interator_args_t *args,
+    njs_value_t *entry, uint32_t n)
 {
     njs_value_t  arguments[5];
 
     /* GC: array elt, array */
 
     njs_set_undefined(&arguments[0]);
-    arguments[1] = *accumulator;
-    arguments[2] = *value;
+    arguments[1] = *args->this_arg;
+    arguments[2] = *entry;
     njs_set_number(&arguments[3], n);
-    arguments[4] = *array;
+    arguments[4] = *args->value;
+
+    return njs_function_apply(vm, args->function, arguments, 5, args->this_arg);
+}
+
+
+static njs_int_t
+njs_array_handler_reduce(njs_vm_t *vm, njs_array_interator_args_t *args,
+    njs_value_t *entry, uint32_t n)
+{
+    njs_int_t  ret;
+
+    if (njs_is_valid(entry)) {
 
-    return njs_function_apply(vm, function, arguments, 5, accumulator);
+        if (!njs_is_valid(args->this_arg)) {
+            *(args->this_arg) = *entry;
+            return NJS_OK;
+        }
+
+        ret = njs_array_iterator_reduce(vm, args, entry, n);
+        if (njs_slow_path(ret != NJS_OK)) {
+            return ret;
+        }
+    }
+
+    return NJS_OK;
 }
 
 
@@ -1727,44 +1977,31 @@ static njs_int_t
 njs_array_prototype_reduce(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
     njs_index_t unused)
 {
-    uint32_t        i, length;
-    njs_int_t       ret;
-    njs_value_t     accumulator, *array, *value;
-    njs_function_t  *function;
+    njs_int_t                   ret;
+    njs_value_t                 accumulator;
+    njs_array_interator_args_t  iargs;
 
-    if (nargs < 2 || !njs_is_array(&args[0]) || !njs_is_function(&args[1])) {
+    if (njs_is_null_or_undefined(njs_arg(args, nargs, 0))
+        || !njs_is_function(njs_arg(args, nargs, 1)))
+    {
         njs_type_error(vm, "unexpected iterator arguments");
         return NJS_ERROR;
     }
 
-    array = &args[0];
-    length = njs_array_len(array);
-    function =  njs_function(&args[1]);
-
     njs_set_invalid(&accumulator);
 
     if (nargs > 2) {
-        accumulator = args[2];
+        accumulator = *njs_argument(args, 2);
     }
 
-    for (i = 0; i < length; i++) {
-        value = &njs_array_start(array)[i];
-
-        if (njs_is_valid(value)) {
-
-            if (!njs_is_valid(&accumulator)) {
-                accumulator = njs_array_start(array)[i];
-                continue;
-            }
+    iargs.value = njs_argument(args, 0);
+    iargs.function = njs_function(&args[1]);
+    iargs.this_arg = &accumulator;
 
-            ret = njs_array_iterator_reduce(vm, function, &accumulator, value,
-                                            i, array);
-            if (njs_slow_path(ret != NJS_OK)) {
-                return ret;
-            }
-        }
-
-        length = njs_min(length, njs_array_len(array));
+    ret = njs_array_iterator(vm, &iargs, njs_array_handler_reduce,
+                             NJS_ARRAY_MAX_LENGTH + 1);
+    if (njs_slow_path(ret != NJS_OK)) {
+        return ret;
     }
 
     if (!njs_is_valid(&accumulator)) {
@@ -1782,20 +2019,19 @@ static njs_int_t
 njs_array_prototype_reduce_right(njs_vm_t *vm, njs_value_t *args,
     njs_uint_t nargs, njs_index_t unused)
 {
-    int32_t         i;
-    uint32_t        length;
-    njs_int_t       ret;
-    njs_value_t     accumulator, *array, *value;
-    njs_function_t  *function;
+    int32_t                     i;
+    uint32_t                    length;
+    njs_int_t                   ret;
+    njs_value_t                 accumulator, *value, *entry;
+    njs_array_interator_args_t  iter;
 
     if (nargs < 2 || !njs_is_array(&args[0]) || !njs_is_function(&args[1])) {
         njs_type_error(vm, "unexpected iterator arguments");
         return NJS_ERROR;
     }
 
-    array = &args[0];
-    length = njs_array_len(array);
-    function =  njs_function(&args[1]);
+    value = &args[0];
+    length = njs_array_len(value);
 
     njs_set_invalid(&accumulator);
 
@@ -1803,24 +2039,27 @@ njs_array_prototype_reduce_right(njs_vm_t *vm, njs_value_t *args,
         accumulator = args[2];
     }
 
+    iter.value = value;
+    iter.function =  njs_function(&args[1]);
+    iter.this_arg = &accumulator;
+
     for (i = length - 1; i >= 0; i--) {
-        value = &njs_array_start(array)[i];
+        entry = &njs_array_start(value)[i];
 
-        if (njs_is_valid(value)) {
+        if (njs_is_valid(entry)) {
 
             if (!njs_is_valid(&accumulator)) {
-                accumulator = njs_array_start(array)[i];
+                accumulator = njs_array_start(value)[i];
                 continue;
             }
 
-            ret = njs_array_iterator_reduce(vm, function, &accumulator, value,
-                                            i, array);
+            ret = njs_array_iterator_reduce(vm, &iter, entry, i);
             if (njs_slow_path(ret != NJS_OK)) {
                 return ret;
             }
         }
 
-        length = njs_min(length, njs_array_len(array));
+        length = njs_min(length, njs_array_len(value));
     }
 
     if (!njs_is_valid(&accumulator)) {
index 9c734115cd5e4615b14396e70064d02f3657ba9e..e09d1e13fa07579f96efce8a893b77f21409e950 100644 (file)
@@ -2297,3 +2297,30 @@ const njs_object_init_t  njs_object_prototype_init = {
     njs_object_prototype_properties,
     njs_nitems(njs_object_prototype_properties),
 };
+
+
+njs_int_t
+njs_object_length(njs_vm_t *vm, njs_value_t *value, uint32_t *length)
+{
+    njs_int_t    ret;
+    njs_value_t  value_length;
+
+    const njs_value_t  string_length = njs_string("length");
+
+    ret = njs_value_property(vm, value, njs_value_arg(&string_length),
+                             &value_length);
+    if (njs_slow_path(ret == NJS_ERROR)) {
+        return ret;
+    }
+
+    if (!njs_is_primitive(&value_length)) {
+        ret = njs_value_to_numeric(vm, &value_length, &value_length);
+        if (njs_slow_path(ret != NJS_OK)) {
+            return ret;
+        }
+    }
+
+    *length = njs_primitive_value_to_length(&value_length);
+
+    return NJS_OK;
+}
index d6bc79b0f3154232d348d54f4b2b2dda7964e9f0..564cced192fd04f0077d4e0f0b4c8f9a5702e6cf 100644 (file)
@@ -69,6 +69,7 @@ njs_value_t *njs_property_constructor_create(njs_vm_t *vm, njs_lvlhsh_t *hash,
     njs_value_t *constructor);
 njs_int_t njs_object_prototype_to_string(njs_vm_t *vm, njs_value_t *args,
     njs_uint_t nargs, njs_index_t unused);
+njs_int_t njs_object_length(njs_vm_t *vm, njs_value_t *value, uint32_t *dest);
 
 njs_object_prop_t *njs_object_prop_alloc(njs_vm_t *vm, const njs_value_t *name,
     const njs_value_t *value, uint8_t attributes);
index 9a034d197fe00ed082fad3e37c2aa0ba3b1ca6fc..667a648a7e5366dee464cce97ac5dd10cc0906a8 100644 (file)
@@ -230,6 +230,28 @@ njs_value_own_enumerate(njs_vm_t *vm, const njs_value_t *value,
 }
 
 
+njs_int_t
+njs_value_length(njs_vm_t *vm, njs_value_t *value, uint32_t *length)
+{
+    njs_string_prop_t  string_prop;
+
+    if (njs_is_string(value)) {
+        *length = (uint32_t) njs_string_prop(&string_prop, value);
+
+    } else if (njs_is_primitive(value)) {
+        *length = 0;
+
+    } else if (njs_is_array(value)) {
+        *length = njs_array_len(value);
+
+    } else {
+        return njs_object_length(vm, value, length);
+    }
+
+    return NJS_OK;
+}
+
+
 const char *
 njs_type_string(njs_value_type_t type)
 {
index 79bc90d6c4d1ee4ca2ce9603f771345ebaeff866..0c4c425ba8d8ea5c9a42345b0dec8a14e677d430 100644 (file)
@@ -526,6 +526,10 @@ typedef struct {
     ((value)->type == NJS_OBJECT_VALUE)
 
 
+#define njs_is_object_string(value)                                           \
+    ((value)->type == NJS_OBJECT_STRING)
+
+
 #define njs_object_value_type(type)                                           \
     (type + NJS_OBJECT)
 
@@ -586,6 +590,10 @@ typedef struct {
     (&(value)->data.u.object->hash)
 
 
+#define njs_object_hash_is_empty(value)                                       \
+    (njs_lvlhsh_is_empty(njs_object_hash(value)))
+
+
 #define njs_array(value)                                                      \
     ((value)->data.u.array)
 
@@ -814,6 +822,7 @@ njs_array_t *njs_value_enumerate(njs_vm_t *vm, const njs_value_t *value,
     njs_object_enum_t kind, njs_bool_t all);
 njs_array_t *njs_value_own_enumerate(njs_vm_t *vm, const njs_value_t *value,
     njs_object_enum_t kind, njs_bool_t all);
+njs_int_t njs_value_length(njs_vm_t *vm, njs_value_t *value, uint32_t *dest);
 const char *njs_type_string(njs_value_type_t type);
 const char *njs_arg_type_string(uint8_t arg);
 
index cc8566c68de45022271e9b7106131e002b406150..eafc7a8697ee079ba15e937ef1a99821a6b4f3be 100644 (file)
@@ -4047,6 +4047,16 @@ static njs_unit_test_t  njs_test[] =
                  "a.forEach(function(v, i, a) { c++ }); c"),
       njs_str("0") },
 
+    { njs_str("var o = {0: 'a', 1: 'b', 2: 'c'};"
+              "Object.defineProperty(o, 'length', {get: () => 4});"
+              "Object.defineProperty(o, '3', {get: () => 'd'});"
+              "var r = ''; Array.prototype.forEach.call(o, function(v, i, a) { r += v }); r"),
+      njs_str("abcd") },
+
+    { njs_str("var s = 't'; var t = '';"
+              "Array.prototype.forEach.call(s, function (a, b, c) {t = typeof c;}); [t, typeof s];"),
+      njs_str("object,string") },
+
     { njs_str("var a = [];"
                  "a.some(function(v, i, a) { return v > 1 })"),
       njs_str("false") },
@@ -4063,6 +4073,20 @@ static njs_unit_test_t  njs_test[] =
                  "a.some(function(v, i, a) { return v > 3 })"),
       njs_str("false") },
 
+    { njs_str("var o = {0: 'a', 1: 'b', 2: 'c', 'length': { valueOf: function() { return 3 }}};"
+              "var r = Array.prototype.some.call(o, function(el, i, arr) {return el == 'c'}); r"),
+      njs_str("true") },
+
+    { njs_str("var o = {0: 'a', 1: 'b', 2: 'd', 'length': { valueOf: function() { return 3 }}};"
+              "var r = Array.prototype.some.call(o, function(el, i, arr) {return el == 'c'}); r"),
+      njs_str("false") },
+
+    { njs_str("var o = {0: 'a', 1: 'b', 2: 'c'};"
+              "Object.defineProperty(o, 'length', {get: () => 4});"
+              "Object.defineProperty(o, '3', {get: () => 'd'});"
+              "var r = Array.prototype.some.call(o, function(v, i, a) { return v === 'd' }); r"),
+      njs_str("true") },
+
     { njs_str("var a = [];"
                  "a.every(function(v, i, a) { return v > 1 })"),
       njs_str("true") },
@@ -4079,6 +4103,26 @@ static njs_unit_test_t  njs_test[] =
                  "a.every(function(v, i, a) { return v > 0 })"),
       njs_str("true") },
 
+    { njs_str("var o = {0: 'c', 1: 'b', 2: 'c', 'length': { valueOf() { return 3 }}};"
+              "var r = Array.prototype.every.call(o, function(el, i, arr) {return el == 'c'}); r"),
+      njs_str("false") },
+
+    { njs_str("var o = {0: 'c', 1: 'c', 2: 'c', 'length': { valueOf() { return 3 }}};"
+              "var r = Array.prototype.every.call(o, function(el, i, arr) {return el == 'c'}); r"),
+      njs_str("true") },
+
+    { njs_str("var o = {0: 'x', 1: 'y', 2: 'z'};"
+              "Object.defineProperty(o, 'length', {get: () => 4});"
+              "Object.defineProperty(o, '3', {get: () => 'a'});"
+              "var r = Array.prototype.some.call(o, function(v, i, a) { return v === 'a' }); r"),
+      njs_str("true") },
+
+    { njs_str("var o = {0: 'a', 1: 'b', 2: 'c'};"
+              "Object.defineProperty(o, 'length', {get: () => 4});"
+              "Object.defineProperty(o, '3', {get: () => 'b'});"
+              "var r = Array.prototype.some.call(o, function(v, i, a) { return v === 'y' }); r"),
+      njs_str("false") },
+
     { njs_str("[].fill(1);"),
       njs_str("") },
 
@@ -4212,6 +4256,16 @@ static njs_unit_test_t  njs_test[] =
                  "a.filter(function(v, i, a) { a[i+1] = v+10; return true })"),
       njs_str("1,11,21,31,41,51,61") },
 
+    { njs_str("var o = {0: 'c', 1: 'b', 2: 'c', 'length': { valueOf() { return 3 }}};"
+              "var r = Array.prototype.filter.call(o, function(el, i, arr) {return el == 'c'}); r"),
+      njs_str("c,c") },
+
+    { njs_str("var o = {0: 'c', 1: 'a', 2: 'b'};"
+              "Object.defineProperty(o, 'length', {get: () => 4});"
+              "Object.defineProperty(o, '3', {get: () => 'c'});"
+              "var r = Array.prototype.filter.call(o, function(el, i, arr) { return el == 'c' }); r"),
+      njs_str("c,c") },
+
     { njs_str("var a = [];"
                  "a.find(function(v, i, a) { return v > 1 })"),
       njs_str("undefined") },
@@ -4252,9 +4306,23 @@ static njs_unit_test_t  njs_test[] =
       njs_str("3") },
 
     { njs_str("var a = [1,2,3,4,5,6];"
-                 "a.find(function(v, i, a) { a.shift(); return v == 4 })"),
+              "a.find(function(v, i, a) { a.shift(); return v == 4 })"),
       njs_str("undefined") },
 
+    { njs_str("var o = {0: 'a', 1: 'b', 2: 'c', 'length': { valueOf() { return 3 }}};"
+              "var r = Array.prototype.find.call(o, function(el, i, arr) {return el == 'b'}); r"),
+      njs_str("b") },
+
+    { njs_str("var o = {0: 'a', 1: 'b', 2: 'c', 'length': { valueOf() { return 3 }}};"
+              "var r = Array.prototype.find.call(o, function(el, i, arr) {delete o['1']; return el == 'c'}); r"),
+      njs_str("c") },
+
+    { njs_str("var o = {0: 'a', 1: 'b', 2: 'c'};"
+              "Object.defineProperty(o, 'length', {get: () => 4});"
+              "Object.defineProperty(o, '3', {get: () => 'd'});"
+              "var r = Array.prototype.find.call(o, function(el, i, arr) { return el == 'd' }); r"),
+      njs_str("d") },
+
     { njs_str("var a = [];"
                  "a.findIndex(function(v, i, a) { return v > 1 })"),
       njs_str("-1") },
@@ -4299,6 +4367,16 @@ static njs_unit_test_t  njs_test[] =
                  "a.findIndex(function(v, i, a) { a.shift(); return v == 4 })"),
       njs_str("-1") },
 
+    { njs_str("var o = {0: 'a', 1: 'b', 2: 'c', 'length': { valueOf() { return 3 }}};"
+              "var r = Array.prototype.findIndex.call(o, function(el, i, arr) {return el == 'b'}); r"),
+      njs_str("1") },
+
+    { njs_str("var o = {0: 'a', 1: 'b', 2: 'c'};"
+              "Object.defineProperty(o, 'length', {get: () => 4});"
+              "Object.defineProperty(o, '3', {get: () => 'd'});"
+              "var r = Array.prototype.findIndex.call(o, function(el, i, arr) { return el == 'd' }); r"),
+      njs_str("3") },
+
     { njs_str("var a = [];"
                  "a.map(function(v, i, a) { return v + 1 })"),
       njs_str("") },
@@ -4323,6 +4401,21 @@ static njs_unit_test_t  njs_test[] =
                  "a.map(function(v, i, a) { a.shift(); return v + 1 })"),
       njs_str("2,4,6,,,") },
 
+    { njs_str("var o = {0: 'a', 1: 'b', 2: 'c', 'length': { valueOf() { return 3 }}};"
+              "var r = Array.prototype.map.call(o, num => num + '1'); r"),
+      njs_str("a1,b1,c1") },
+
+    { njs_str("var o = {0: 'a', 1: 'b', 2: 'c'};"
+              "Object.defineProperty(o, 'length', {get: () => 4});"
+              "Object.defineProperty(o, '3', {get: () => 'd'});"
+              "var r = Array.prototype.map.call(o, function(el, i, arr) { return el + '1' }); r"),
+      njs_str("a1,b1,c1,d1") },
+
+    { njs_str("Array.prototype.map.call(new String('abc'),"
+             "                          (val, idx, obj) => {return obj instanceof String})"
+              ".every(x => x === true)"),
+      njs_str("true") },
+
     { njs_str("var a = [];"
                  "a.reduce(function(p, v, i, a) { return p + v })"),
       njs_str("TypeError: invalid index") },
@@ -4359,6 +4452,23 @@ static njs_unit_test_t  njs_test[] =
                  "                         { return a.concat(b) }, [])"),
       njs_str("0,1,2,3,4,5") },
 
+    { njs_str("var o = {0: 'a', 1: 'b', 2: 'c', 'length': { valueOf() { return 3 }}};"
+                 "var reducer = (a, b) => a + b;"
+                 "var a = Array.prototype.reduce.call(o, reducer); a"),
+      njs_str("abc") },
+
+    { njs_str("var o = {0: 'a', 1: 'b', 2: 'c'};"
+              "Object.defineProperty(o, 'length', {get: () => 4});"
+              "Object.defineProperty(o, '3', {get: () => 'd'});"
+              "var r = Array.prototype.reduce.call(o, (a, b) => a + b); r"),
+      njs_str("abcd") },
+
+    { njs_str("var o = {1: 'b', 2: 'c', 3: 'd'};"
+              "Object.defineProperty(o, 'length', {get: () => 4});"
+              "Object.defineProperty(o, '0', {get: () => 'a'});"
+              "var r = Array.prototype.reduce.call(o, (a, b) => a + b); r"),
+      njs_str("abcd") },
+
     { njs_str("var a = [];"
                  "a.reduceRight(function(p, v, i, a) { return p + v })"),
       njs_str("TypeError: invalid index") },