blob: 76ebb02ec3833edc7a0e9154717435b74dffe0fa [file] [log] [blame]
/*
* Copyright (C) Igor Sysoev
* Copyright (C) NGINX, Inc.
*/
#include <njs_main.h>
#define njs_fast_object(_sz) ((_sz) <= NJS_ARRAY_FAST_OBJECT_LENGTH)
#define njs_array_func(type) \
((type << 1) | NJS_ARRAY_FUNC)
#define njs_array_arg(type) \
((type << 1) | NJS_ARRAY_ARG)
#define njs_array_type(magic) (magic >> 1)
#define njs_array_arg1(magic) (magic & 0x1)
typedef enum {
NJS_ARRAY_EVERY = 0,
NJS_ARRAY_SOME,
NJS_ARRAY_INCLUDES,
NJS_ARRAY_INDEX_OF,
NJS_ARRAY_FOR_EACH,
NJS_ARRAY_FIND,
NJS_ARRAY_FIND_INDEX,
NJS_ARRAY_REDUCE,
NJS_ARRAY_FILTER,
NJS_ARRAY_MAP,
} njs_array_iterator_fun_t;
typedef enum {
NJS_ARRAY_LAST_INDEX_OF = 0,
NJS_ARRAY_REDUCE_RIGHT,
} njs_array_reverse_iterator_fun_t;
typedef enum {
NJS_ARRAY_FUNC = 0,
NJS_ARRAY_ARG
} njs_array_iterator_arg_t;
typedef struct {
njs_function_t *function;
njs_value_t *argument;
njs_value_t *value;
njs_array_t *array;
int64_t from;
int64_t to;
} njs_array_iterator_args_t;
typedef njs_int_t (*njs_array_iterator_handler_t)(njs_vm_t *vm,
njs_array_iterator_args_t *args, njs_value_t *entry, int64_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);
njs_array_t *
njs_array_alloc(njs_vm_t *vm, njs_bool_t flat, uint64_t length, uint32_t spare)
{
uint64_t size;
njs_int_t ret;
njs_array_t *array;
njs_value_t value;
if (njs_slow_path(length > UINT32_MAX)) {
goto overflow;
}
array = njs_mp_alloc(vm->mem_pool, sizeof(njs_array_t));
if (njs_slow_path(array == NULL)) {
goto memory_error;
}
size = length + spare;
if (flat || size <= NJS_ARRAY_LARGE_OBJECT_LENGTH) {
array->data = njs_mp_align(vm->mem_pool, sizeof(njs_value_t),
size * sizeof(njs_value_t));
if (njs_slow_path(array->data == NULL)) {
goto memory_error;
}
} else {
array->data = NULL;
}
array->start = array->data;
njs_lvlhsh_init(&array->object.hash);
array->object.shared_hash = vm->shared->array_instance_hash;
array->object.__proto__ = &vm->prototypes[NJS_OBJ_TYPE_ARRAY].object;
array->object.slots = NULL;
array->object.type = NJS_ARRAY;
array->object.shared = 0;
array->object.extensible = 1;
array->object.error_data = 0;
array->object.fast_array = (array->data != NULL);
if (njs_fast_path(array->object.fast_array)) {
array->size = size;
array->length = length;
} else {
array->size = 0;
array->length = 0;
njs_set_array(&value, array);
ret = njs_array_length_redefine(vm, &value, length);
if (njs_slow_path(ret != NJS_OK)) {
return NULL;
}
}
return array;
memory_error:
njs_memory_error(vm);
return NULL;
overflow:
njs_range_error(vm, "Invalid array length");
return NULL;
}
void
njs_array_destroy(njs_vm_t *vm, njs_array_t *array)
{
if (array->data != NULL) {
njs_mp_free(vm->mem_pool, array->data);
}
/* TODO: destroy keys. */
njs_mp_free(vm->mem_pool, array);
}
njs_int_t
njs_array_convert_to_slow_array(njs_vm_t *vm, njs_array_t *array)
{
uint32_t i, length;
njs_value_t index, value;
njs_object_prop_t *prop;
njs_set_array(&value, array);
array->object.fast_array = 0;
length = array->length;
for (i = 0; i < length; i++) {
if (njs_is_valid(&array->start[i])) {
njs_uint32_to_string(&index, i);
prop = njs_object_property_add(vm, &value, &index, 0);
if (njs_slow_path(prop == NULL)) {
return NJS_ERROR;
}
prop->value = array->start[i];
}
}
/* GC: release value. */
njs_mp_free(vm->mem_pool, array->start);
array->start = NULL;
return NJS_OK;
}
njs_int_t
njs_array_length_redefine(njs_vm_t *vm, njs_value_t *value, uint32_t length)
{
njs_object_prop_t *prop;
static const njs_value_t string_length = njs_string("length");
if (njs_slow_path(!njs_is_array(value))) {
njs_internal_error(vm, "njs_array_length_redefine() "
"applied to non-array");
return NJS_ERROR;
}
prop = njs_object_property_add(vm, value, njs_value_arg(&string_length), 1);
if (njs_slow_path(prop == NULL)) {
njs_internal_error(vm, "njs_array_length_redefine() "
"cannot redefine \"length\"");
return NJS_ERROR;
}
prop->enumerable = 0;
prop->configurable = 0;
njs_value_number_set(&prop->value, length);
return NJS_OK;
}
njs_int_t
njs_array_length_set(njs_vm_t *vm, njs_value_t *value,
njs_object_prop_t *prev, njs_value_t *setval)
{
double num, idx;
int64_t prev_length;
uint32_t i, length;
njs_int_t ret;
njs_array_t *array, *keys;
array = njs_object_proto_lookup(njs_object(value), NJS_ARRAY, njs_array_t);
if (njs_slow_path(array == NULL)) {
return NJS_DECLINED;
}
ret = njs_value_to_number(vm, setval, &num);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
length = (uint32_t) njs_number_to_length(num);
if ((double) length != num) {
njs_range_error(vm, "Invalid array length");
return NJS_ERROR;
}
ret = njs_value_to_length(vm, &prev->value, &prev_length);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
keys = NULL;
if (length < prev_length) {
keys = njs_array_indices(vm, value);
if (njs_slow_path(keys == NULL)) {
return NJS_ERROR;
}
if (keys->length != 0) {
i = keys->length - 1;
do {
idx = njs_string_to_index(&keys->start[i]);
if (idx >= length) {
ret = njs_value_property_delete(vm, value, &keys->start[i],
NULL);
if (njs_slow_path(ret == NJS_ERROR)) {
goto done;
}
}
} while (i-- != 0);
}
}
ret = njs_array_length_redefine(vm, value, length);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
ret = NJS_OK;
done:
if (keys != NULL) {
njs_array_destroy(vm, keys);
}
return ret;
}
static njs_int_t
njs_array_copy_within(njs_vm_t *vm, njs_value_t *array, int64_t to_pos,
int64_t from_pos, int64_t count, njs_bool_t forward)
{
int64_t i, from, to;
njs_int_t ret;
njs_array_t *arr;
njs_value_t value;
if (njs_fast_path(njs_is_fast_array(array) && count > 0)) {
arr = njs_array(array);
memmove(&arr->start[to_pos], &arr->start[from_pos],
count * sizeof(njs_value_t));
return NJS_OK;
}
if (!forward) {
from_pos += count - 1;
to_pos += count - 1;
}
for (i = 0; i < count; i++) {
if (forward) {
from = from_pos + i;
to = to_pos + i;
} else {
from = from_pos - i;
to = to_pos - i;
}
ret = njs_value_property_i64(vm, array, from, &value);
if (njs_slow_path(ret == NJS_ERROR)) {
return NJS_ERROR;
}
if (ret == NJS_OK) {
ret = njs_value_property_i64_set(vm, array, to, &value);
} else {
ret = njs_value_property_i64_delete(vm, array, to, NULL);
}
if (njs_slow_path(ret == NJS_ERROR)) {
return NJS_ERROR;
}
}
return NJS_OK;
}
njs_int_t
njs_array_add(njs_vm_t *vm, njs_array_t *array, njs_value_t *value)
{
njs_int_t ret;
ret = njs_array_expand(vm, array, 0, 1);
if (njs_fast_path(ret == NJS_OK)) {
/* GC: retain value. */
array->start[array->length++] = *value;
}
return ret;
}
njs_int_t
njs_array_string_add(njs_vm_t *vm, njs_array_t *array, const u_char *start,
size_t size, size_t length)
{
njs_int_t ret;
ret = njs_array_expand(vm, array, 0, 1);
if (njs_fast_path(ret == NJS_OK)) {
return njs_string_new(vm, &array->start[array->length++], start, size,
length);
}
return ret;
}
njs_int_t
njs_array_expand(njs_vm_t *vm, njs_array_t *array, uint32_t prepend,
uint32_t append)
{
uint32_t free_before, free_after;
uint64_t size;
njs_value_t *start, *old;
free_before = array->start - array->data;
free_after = array->size - array->length - free_before;
if (njs_fast_path(free_before >= prepend && free_after >= append)) {
return NJS_OK;
}
size = (uint64_t) prepend + array->length + append;
if (size < 16) {
size *= 2;
} else {
size += size / 2;
}
if (njs_slow_path(size > (UINT32_MAX / sizeof(njs_value_t)))) {
goto memory_error;
}
start = njs_mp_align(vm->mem_pool, sizeof(njs_value_t),
size * sizeof(njs_value_t));
if (njs_slow_path(start == NULL)) {
goto memory_error;
}
array->size = size;
old = array->data;
array->data = start;
start += prepend;
if (array->length != 0) {
memcpy(start, array->start, array->length * sizeof(njs_value_t));
}
array->start = start;
njs_mp_free(vm->mem_pool, old);
return NJS_OK;
memory_error:
njs_memory_error(vm);
return NJS_ERROR;
}
static njs_int_t
njs_array_constructor(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
njs_index_t unused)
{
double num;
uint32_t size;
njs_value_t *value;
njs_array_t *array;
args = &args[1];
size = nargs - 1;
if (size == 1 && njs_is_number(&args[0])) {
num = njs_number(&args[0]);
size = (uint32_t) njs_number_to_length(num);
if ((double) size != num) {
njs_range_error(vm, "Invalid array length");
return NJS_ERROR;
}
args = NULL;
}
array = njs_array_alloc(vm, size <= NJS_ARRAY_FLAT_MAX_LENGTH,
size, NJS_ARRAY_SPARE);
if (njs_fast_path(array != NULL)) {
if (array->object.fast_array) {
value = array->start;
if (args == NULL) {
while (size != 0) {
njs_set_invalid(value);
value++;
size--;
}
} else {
while (size != 0) {
njs_retain(args);
*value++ = *args++;
size--;
}
}
}
njs_set_array(&vm->retval, array);
return NJS_OK;
}
return NJS_ERROR;
}
static njs_int_t
njs_array_is_array(njs_vm_t *vm, njs_value_t *args,
njs_uint_t nargs, njs_index_t unused)
{
const njs_value_t *value;
if (nargs > 1 && njs_is_array(&args[1])) {
value = &njs_value_true;
} else {
value = &njs_value_false;
}
vm->retval = *value;
return NJS_OK;
}
static njs_int_t
njs_array_of(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
njs_index_t unused)
{
uint32_t length, i;
njs_array_t *array;
length = nargs > 1 ? nargs - 1 : 0;
array = njs_array_alloc(vm, 0, length, NJS_ARRAY_SPARE);
if (njs_slow_path(array == NULL)) {
return NJS_ERROR;
}
njs_set_array(&vm->retval, array);
if (array->object.fast_array) {
for (i = 0; i < length; i++) {
array->start[i] = args[i + 1];
}
}
return NJS_OK;
}
static const njs_object_prop_t njs_array_constructor_properties[] =
{
{
.type = NJS_PROPERTY,
.name = njs_string("name"),
.value = njs_string("Array"),
.configurable = 1,
},
{
.type = NJS_PROPERTY,
.name = njs_string("length"),
.value = njs_value(NJS_NUMBER, 1, 1.0),
.configurable = 1,
},
{
.type = NJS_PROPERTY_HANDLER,
.name = njs_string("prototype"),
.value = njs_prop_handler(njs_object_prototype_create),
},
{
.type = NJS_PROPERTY,
.name = njs_string("isArray"),
.value = njs_native_function(njs_array_is_array, 1),
.writable = 1,
.configurable = 1,
},
{
.type = NJS_PROPERTY,
.name = njs_string("of"),
.value = njs_native_function(njs_array_of, 0),
.writable = 1,
.configurable = 1,
},
};
const njs_object_init_t njs_array_constructor_init = {
njs_array_constructor_properties,
njs_nitems(njs_array_constructor_properties),
};
static njs_int_t
njs_array_length(njs_vm_t *vm,njs_object_prop_t *prop, njs_value_t *value,
njs_value_t *setval, njs_value_t *retval)
{
double num;
int64_t size;
uint32_t length;
njs_int_t ret;
njs_value_t *val;
njs_array_t *array;
njs_object_t *proto;
proto = njs_object(value);
if (njs_fast_path(setval == NULL)) {
array = njs_object_proto_lookup(proto, NJS_ARRAY, njs_array_t);
if (njs_slow_path(array == NULL)) {
njs_set_undefined(retval);
return NJS_DECLINED;
}
njs_set_number(retval, array->length);
return NJS_OK;
}
if (proto->type != NJS_ARRAY) {
njs_set_undefined(retval);
return NJS_DECLINED;
}
if (njs_slow_path(!njs_is_valid(setval))) {
return NJS_DECLINED;
}
ret = njs_value_to_number(vm, setval, &num);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
length = (uint32_t) njs_number_to_length(num);
if ((double) length != num) {
njs_range_error(vm, "Invalid array length");
return NJS_ERROR;
}
array = (njs_array_t *) proto;
if (njs_fast_path(array->object.fast_array)) {
if (njs_fast_path(length <= NJS_ARRAY_LARGE_OBJECT_LENGTH)) {
size = (int64_t) length - array->length;
if (size > 0) {
ret = njs_array_expand(vm, array, 0, size);
if (njs_slow_path(ret != NJS_OK)) {
return NJS_ERROR;
}
val = &array->start[array->length];
do {
njs_set_invalid(val);
val++;
size--;
} while (size != 0);
}
array->length = length;
*retval = *setval;
return NJS_OK;
}
ret = njs_array_convert_to_slow_array(vm, array);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
}
prop->type = NJS_PROPERTY;
njs_set_number(&prop->value, length);
*retval = *setval;
return NJS_OK;
}
static njs_int_t
njs_array_prototype_slice(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
njs_index_t unused)
{
int64_t start, end, length, object_length;
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;
}
ret = njs_object_length(vm, this, &object_length);
if (njs_slow_path(ret == NJS_ERROR)) {
return ret;
}
length = object_length;
ret = njs_value_to_integer(vm, njs_arg(args, nargs, 1), &start);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
if (start < 0) {
start += length;
if (start < 0) {
start = 0;
}
}
if (start >= length) {
start = 0;
length = 0;
} else {
if (njs_is_defined(njs_arg(args, nargs, 2))) {
ret = njs_value_to_integer(vm, njs_argument(args, 2), &end);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
} else {
end = length;
}
if (end < 0) {
end += length;
}
if (length >= end) {
length = end - start;
if (length < 0) {
start = 0;
length = 0;
}
} else {
length -= start;
}
}
return njs_array_prototype_slice_copy(vm, this, start, length);
}
static njs_int_t
njs_array_prototype_slice_copy(njs_vm_t *vm, njs_value_t *this,
int64_t start, int64_t length)
{
size_t size;
u_char *dst;
uint32_t n;
njs_int_t ret;
njs_array_t *array, *keys;
njs_value_t *value, retval, self;
const u_char *src, *end;
njs_slice_prop_t string_slice;
njs_string_prop_t string;
keys = NULL;
array = njs_array_alloc(vm, 0, length, NJS_ARRAY_SPARE);
if (njs_slow_path(array == NULL)) {
return NJS_ERROR;
}
if (njs_slow_path(length == 0)) {
ret = NJS_OK;
goto done;
}
n = 0;
if (njs_fast_path(array->object.fast_array)) {
if (njs_fast_path(njs_is_fast_array(this))) {
value = njs_array_start(this);
do {
if (njs_fast_path(njs_is_valid(&value[start]))) {
array->start[n++] = value[start++];
} else {
/* src value may be in Array.prototype object. */
ret = njs_value_property_i64(vm, this, start++,
&array->start[n]);
if (njs_slow_path(ret == NJS_ERROR)) {
return NJS_ERROR;
}
if (ret != NJS_OK) {
njs_set_invalid(&array->start[n]);
}
n++;
}
length--;
} while (length != 0);
} else if (njs_is_string(this) || this->type == NJS_OBJECT_STRING) {
if (this->type == NJS_OBJECT_STRING) {
this = njs_object_value(this);
}
string_slice.start = start;
string_slice.length = length;
string_slice.string_length = njs_string_prop(&string, this);
njs_string_slice_string_prop(&string, &string, &string_slice);
src = string.start;
end = src + string.size;
if (string.length == 0) {
/* Byte string. */
do {
value = &array->start[n++];
dst = njs_string_short_start(value);
*dst = *src++;
njs_string_short_set(value, 1, 0);
length--;
} while (length != 0);
} else {
/* UTF-8 or ASCII string. */
do {
value = &array->start[n++];
dst = njs_string_short_start(value);
dst = njs_utf8_copy(dst, &src, end);
size = dst - njs_string_short_start(value);
njs_string_short_set(value, size, 1);
length--;
} while (length != 0);
}
} else if (njs_is_object(this)) {
do {
value = &array->start[n++];
ret = njs_value_property_i64(vm, this, start++, value);
if (ret != NJS_OK) {
njs_set_invalid(value);
}
length--;
} while (length != 0);
} else {
/* Primitive types. */
value = array->start;
do {
njs_set_invalid(value++);
length--;
} while (length != 0);
}
ret = NJS_OK;
goto done;
}
njs_set_array(&self, array);
if (njs_fast_object(length)) {
do {
ret = njs_value_property_i64(vm, this, start++, &retval);
if (njs_slow_path(ret == NJS_ERROR)) {
return NJS_ERROR;
}
if (ret == NJS_OK) {
ret = njs_value_property_i64_set(vm, &self, start, &retval);
if (njs_slow_path(ret == NJS_ERROR)) {
return ret;
}
}
length--;
} while (length != 0);
ret = NJS_OK;
goto done;
}
keys = njs_array_indices(vm, this);
if (njs_slow_path(keys == NULL)) {
return NJS_ERROR;
}
for (n = 0; n < keys->length; n++) {
ret = njs_value_property(vm, this, &keys->start[n], &retval);
if (njs_slow_path(ret == NJS_ERROR)) {
goto done;
}
ret = njs_value_property_set(vm, &self, &keys->start[n], &retval);
if (njs_slow_path(ret == NJS_ERROR)) {
goto done;
}
}
ret = NJS_OK;
done:
if (keys != NULL) {
njs_array_destroy(vm, keys);
}
njs_set_array(&vm->retval, array);
return ret;
}
static njs_int_t
njs_array_prototype_push(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
njs_index_t unused)
{
int64_t length;
njs_int_t ret;
njs_uint_t i;
njs_array_t *array;
njs_value_t *this;
length = 0;
this = njs_argument(args, 0);
ret = njs_value_to_object(vm, this);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
if (njs_is_fast_array(this)) {
array = njs_array(this);
if (nargs != 0) {
ret = njs_array_expand(vm, array, 0, nargs);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
for (i = 1; i < nargs; i++) {
/* GC: njs_retain(&args[i]); */
array->start[array->length++] = args[i];
}
}
njs_set_number(&vm->retval, array->length);
return NJS_OK;
}
ret = njs_object_length(vm, this, &length);
if (njs_slow_path(ret == NJS_ERROR)) {
return ret;
}
if (njs_slow_path((length + nargs - 1) > NJS_MAX_LENGTH)) {
njs_type_error(vm, "Invalid length");
return NJS_ERROR;
}
for (i = 1; i < nargs; i++) {
ret = njs_value_property_i64_set(vm, this, length++, &args[i]);
if (njs_slow_path(ret == NJS_ERROR)) {
return ret;
}
}
ret = njs_object_length_set(vm, this, length);
if (njs_slow_path(ret == NJS_ERROR)) {
return ret;
}
njs_set_number(&vm->retval, length);
return NJS_OK;
}
static njs_int_t
njs_array_prototype_pop(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
njs_index_t unused)
{
int64_t length;
njs_int_t ret;
njs_array_t *array;
njs_value_t *this, *entry;
this = njs_argument(args, 0);
ret = njs_value_to_object(vm, this);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
njs_set_undefined(&vm->retval);
if (njs_is_fast_array(this)) {
array = njs_array(this);
if (array->length != 0) {
array->length--;
entry = &array->start[array->length];
if (njs_is_valid(entry)) {
vm->retval = *entry;
} else {
/* src value may be in Array.prototype object. */
ret = njs_value_property_i64(vm, this, array->length,
&vm->retval);
if (njs_slow_path(ret == NJS_ERROR)) {
return NJS_ERROR;
}
}
}
return NJS_OK;
}
ret = njs_object_length(vm, this, &length);
if (njs_slow_path(ret == NJS_ERROR)) {
return ret;
}
if (length == 0) {
njs_set_undefined(&vm->retval);
goto done;
}
ret = njs_value_property_i64(vm, this, --length, &vm->retval);
if (njs_slow_path(ret == NJS_ERROR)) {
return ret;
}
ret = njs_value_property_i64_delete(vm, this, length, NULL);
if (njs_slow_path(ret == NJS_ERROR)) {
return ret;
}
done:
ret = njs_object_length_set(vm, this, length);
if (njs_slow_path(ret == NJS_ERROR)) {
return ret;
}
return NJS_OK;
}
static njs_int_t
njs_array_prototype_unshift(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
njs_index_t unused)
{
double idx;
int64_t from, to, length;
njs_int_t ret;
njs_uint_t n;
njs_array_t *array, *keys;
njs_value_t *this, entry;
this = njs_argument(args, 0);
length = 0;
n = nargs - 1;
ret = njs_value_to_object(vm, this);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
if (njs_fast_path(njs_is_fast_array(this))) {
array = njs_array(this);
if (n != 0) {
ret = njs_array_expand(vm, array, n, 0);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
array->length += n;
n = nargs;
do {
n--;
/* GC: njs_retain(&args[n]); */
array->start--;
array->start[0] = args[n];
} while (n > 1);
}
njs_set_number(&vm->retval, array->length);
return NJS_OK;
}
ret = njs_object_length(vm, this, &length);
if (njs_slow_path(ret == NJS_ERROR)) {
return ret;
}
if (n == 0) {
goto done;
}
if (njs_slow_path((length + n) > NJS_MAX_LENGTH)) {
njs_type_error(vm, "Invalid length");
return NJS_ERROR;
}
if (!njs_fast_object(length)) {
keys = njs_array_indices(vm, this);
if (njs_slow_path(keys == NULL)) {
return NJS_ERROR;
}
from = keys->length;
while (from > 0) {
ret = njs_value_property_delete(vm, this, &keys->start[--from],
&entry);
if (njs_slow_path(ret == NJS_ERROR)) {
njs_array_destroy(vm, keys);
return ret;
}
if (ret == NJS_OK) {
idx = njs_string_to_index(&keys->start[from]) + n;
ret = njs_value_property_i64_set(vm, this, idx, &entry);
if (njs_slow_path(ret == NJS_ERROR)) {
njs_array_destroy(vm, keys);
return ret;
}
}
}
njs_array_destroy(vm, keys);
length += n;
goto copy;
}
from = length;
length += n;
to = length;
while (from > 0) {
ret = njs_value_property_i64_delete(vm, this, --from, &entry);
if (njs_slow_path(ret == NJS_ERROR)) {
return ret;
}
to--;
if (ret == NJS_OK) {
ret = njs_value_property_i64_set(vm, this, to, &entry);
if (njs_slow_path(ret == NJS_ERROR)) {
return ret;
}
}
}
copy:
for (n = 1; n < nargs; n++) {
ret = njs_value_property_i64_set(vm, this, n - 1, &args[n]);
if (njs_slow_path(ret == NJS_ERROR)) {
return ret;
}
}
done:
ret = njs_object_length_set(vm, this, length);
if (njs_slow_path(ret == NJS_ERROR)) {
return ret;
}
njs_set_number(&vm->retval, length);
return NJS_OK;
}
static njs_int_t
njs_array_prototype_shift(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
njs_index_t unused)
{
int64_t i, length;
njs_int_t ret;
njs_array_t *array;
njs_value_t *this, *item, entry;
this = njs_argument(args, 0);
length = 0;
ret = njs_value_to_object(vm, this);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
njs_set_undefined(&vm->retval);
if (njs_is_fast_array(this)) {
array = njs_array(this);
if (array->length != 0) {
array->length--;
item = &array->start[0];
if (njs_is_valid(item)) {
vm->retval = *item;
} else {
/* src value may be in Array.prototype object. */
ret = njs_value_property_i64(vm, this, 0, &vm->retval);
if (njs_slow_path(ret == NJS_ERROR)) {
return NJS_ERROR;
}
}
array->start++;
}
return NJS_OK;
}
ret = njs_object_length(vm, this, &length);
if (njs_slow_path(ret == NJS_ERROR)) {
return ret;
}
if (length == 0) {
goto done;
}
ret = njs_value_property_i64_delete(vm, this, 0, &vm->retval);
if (njs_slow_path(ret == NJS_ERROR)) {
return ret;
}
for (i = 1; i < length; i++) {
ret = njs_value_property_i64_delete(vm, this, i, &entry);
if (njs_slow_path(ret == NJS_ERROR)) {
return ret;
}
if (ret == NJS_OK) {
ret = njs_value_property_i64_set(vm, this, i - 1, &entry);
if (njs_slow_path(ret == NJS_ERROR)) {
return ret;
}
}
}
length--;
done:
ret = njs_object_length_set(vm, this, length);
if (njs_slow_path(ret == NJS_ERROR)) {
return ret;
}
return NJS_OK;
}
static njs_int_t
njs_array_prototype_splice(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
njs_index_t unused)
{
int64_t i, n, start, length, items, delta, delete;
njs_int_t ret;
njs_value_t *this, value, del_object;
njs_array_t *array, *deleted;
this = njs_argument(args, 0);
ret = njs_value_to_object(vm, this);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
ret = njs_object_length(vm, this, &length);
if (njs_slow_path(ret == NJS_ERROR)) {
return ret;
}
ret = njs_value_to_integer(vm, njs_arg(args, nargs, 1), &start);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
start = (start < 0) ? njs_max(length + start, 0) : njs_min(start, length);
items = 0;
delete = 0;
if (nargs == 2) {
delete = length - start;
} else if (nargs > 2) {
items = nargs - 3;
ret = njs_value_to_integer(vm, njs_arg(args, nargs, 2), &delete);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
delete = njs_min(njs_max(delete, 0), length - start);
}
delta = items - delete;
if (njs_slow_path((length + delta) > NJS_MAX_LENGTH)) {
njs_type_error(vm, "Invalid length");
return NJS_ERROR;
}
/* TODO: ArraySpeciesCreate(). */
deleted = njs_array_alloc(vm, 0, delete, 0);
if (njs_slow_path(deleted == NULL)) {
return NJS_ERROR;
}
if (njs_fast_path(njs_is_fast_array(this) && deleted->object.fast_array)) {
array = njs_array(this);
for (i = 0, n = start; i < delete; i++, n++) {
deleted->start[i] = array->start[n];
}
} else {
njs_set_array(&del_object, deleted);
for (i = 0, n = start; i < delete; i++, n++) {
ret = njs_value_property_i64(vm, this, n, &value);
if (njs_slow_path(ret == NJS_ERROR)) {
return NJS_ERROR;
}
if (ret == NJS_OK) {
/* TODO: CreateDataPropertyOrThrow(). */
ret = njs_value_property_i64_set(vm, &del_object, i, &value);
if (njs_slow_path(ret == NJS_ERROR)) {
return ret;
}
}
}
ret = njs_object_length_set(vm, &del_object, delete);
if (njs_slow_path(ret != NJS_OK)) {
return NJS_ERROR;
}
}
if (njs_fast_path(njs_is_fast_array(this))) {
array = njs_array(this);
if (delta != 0) {
/*
* Relocate the rest of items.
* Index of the first item is in "n".
*/
if (delta > 0) {
ret = njs_array_expand(vm, array, 0, delta);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
}
ret = njs_array_copy_within(vm, this, start + items, start + delete,
array->length - (start + delete), 0);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
array->length += delta;
}
/* Copy new items. */
if (items > 0) {
memcpy(&array->start[start], &args[3],
items * sizeof(njs_value_t));
}
} else {
if (delta != 0) {
ret = njs_array_copy_within(vm, this, start + items, start + delete,
length - (start + delete), delta < 0);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
for (i = length - 1; i >= length + delta; i--) {
ret = njs_value_property_i64_delete(vm, this, i, NULL);
if (njs_slow_path(ret == NJS_ERROR)) {
return NJS_ERROR;
}
}
}
/* Copy new items. */
for (i = 3, n = start; items-- > 0; i++, n++) {
ret = njs_value_property_i64_set(vm, this, n, &args[i]);
if (njs_slow_path(ret == NJS_ERROR)) {
return NJS_ERROR;
}
}
ret = njs_object_length_set(vm, this, length + delta);
if (njs_slow_path(ret != NJS_OK)) {
return NJS_ERROR;
}
}
njs_set_array(&vm->retval, deleted);
return NJS_OK;
}
static njs_int_t
njs_array_prototype_reverse(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
njs_index_t unused)
{
int64_t length, l, h;
njs_int_t ret, lret, hret;
njs_value_t value, lvalue, hvalue, *this;
njs_array_t *array;
this = njs_argument(args, 0);
ret = njs_value_to_object(vm, this);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
ret = njs_object_length(vm, this, &length);
if (njs_slow_path(ret == NJS_ERROR)) {
return ret;
}
if (njs_slow_path(length < 2)) {
vm->retval = *this;
return NJS_OK;
}
if (njs_is_fast_array(this)) {
array = njs_array(this);
for (l = 0, h = length - 1; l < h; l++, h--) {
if (njs_fast_path(njs_is_valid(&array->start[l]))) {
lvalue = array->start[l];
lret = NJS_OK;
} else {
lret = njs_value_property_i64(vm, this, l, &lvalue);
if (njs_slow_path(lret == NJS_ERROR)) {
return NJS_ERROR;
}
}
if (njs_fast_path(njs_is_valid(&array->start[h]))) {
hvalue = array->start[h];
hret = NJS_OK;
} else {
hret = njs_value_property_i64(vm, this, h, &hvalue);
if (njs_slow_path(hret == NJS_ERROR)) {
return NJS_ERROR;
}
}
if (lret == NJS_OK) {
array->start[h] = lvalue;
if (hret == NJS_OK) {
array->start[l] = hvalue;
} else {
array->start[l] = njs_value_invalid;
}
} else if (hret == NJS_OK) {
array->start[l] = hvalue;
array->start[h] = njs_value_invalid;
}
}
njs_set_array(&vm->retval, array);
return NJS_OK;
}
for (l = 0, h = length - 1; l < h; l++, h--) {
lret = njs_value_property_i64(vm, this, l, &lvalue);
if (njs_slow_path(lret == NJS_ERROR)) {
return NJS_ERROR;
}
hret = njs_value_property_i64(vm, this, h, &hvalue);
if (njs_slow_path(hret == NJS_ERROR)) {
return NJS_ERROR;
}
if (lret == NJS_OK) {
ret = njs_value_property_i64_set(vm, this, h, &lvalue);
if (njs_slow_path(ret == NJS_ERROR)) {
return NJS_ERROR;
}
if (hret == NJS_OK) {
ret = njs_value_property_i64_set(vm, this, l, &hvalue);
if (njs_slow_path(ret == NJS_ERROR)) {
return NJS_ERROR;
}
} else {
ret = njs_value_property_i64_delete(vm, this, l, &value);
if (njs_slow_path(ret == NJS_ERROR)) {
return NJS_ERROR;
}
}
} else if (hret == NJS_OK) {
ret = njs_value_property_i64_set(vm, this, l, &hvalue);
if (njs_slow_path(ret == NJS_ERROR)) {
return NJS_ERROR;
}
ret = njs_value_property_i64_delete(vm, this, h, &value);
if (njs_slow_path(ret == NJS_ERROR)) {
return NJS_ERROR;
}
}
}
vm->retval = *this;
return NJS_OK;
}
njs_int_t
njs_array_prototype_to_string(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
njs_index_t unused)
{
njs_int_t ret;
njs_value_t value;
njs_lvlhsh_query_t lhq;
static const njs_value_t join_string = njs_string("join");
if (njs_is_object(&args[0])) {
njs_object_property_init(&lhq, &join_string, NJS_JOIN_HASH);
ret = njs_object_property(vm, &args[0], &lhq, &value);
if (njs_slow_path(ret == NJS_ERROR)) {
return ret;
}
if (njs_is_function(&value)) {
return njs_function_apply(vm, njs_function(&value), args, nargs,
&vm->retval);
}
}
return njs_object_prototype_to_string(vm, args, nargs, unused);
}
static njs_int_t
njs_array_prototype_join(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
njs_index_t unused)
{
u_char *p, *last;
int64_t i, size, len, length;
njs_int_t ret;
njs_chb_t chain;
njs_utf8_t utf8;
njs_array_t *array;
njs_value_t *value, *this, entry;
njs_string_prop_t separator, string;
this = njs_argument(args, 0);
ret = njs_value_to_object(vm, this);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
value = njs_arg(args, nargs, 1);
if (njs_slow_path(!njs_is_string(value))) {
if (njs_is_undefined(value)) {
value = njs_value_arg(&njs_string_comma);
} else {
ret = njs_value_to_string(vm, value, value);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
}
}
(void) njs_string_prop(&separator, value);
if (njs_slow_path(!njs_is_object(this))) {
vm->retval = njs_string_empty;
return NJS_OK;
}
length = 0;
array = NULL;
utf8 = njs_is_byte_string(&separator) ? NJS_STRING_BYTE : NJS_STRING_UTF8;
if (njs_is_fast_array(this)) {
array = njs_array(this);
len = array->length;
} else {
ret = njs_object_length(vm, this, &len);
if (njs_slow_path(ret == NJS_ERROR)) {
return ret;
}
}
if (njs_slow_path(len == 0)) {
vm->retval = njs_string_empty;
return NJS_OK;
}
njs_chb_init(&chain, vm->mem_pool);
for (i = 0; i < len; i++) {
if (njs_fast_path(array != NULL
&& array->object.fast_array
&& njs_is_valid(&array->start[i])))
{
value = &array->start[i];
} else {
ret = njs_value_property_i64(vm, this, i, &entry);
if (njs_slow_path(ret == NJS_ERROR)) {
return ret;
}
value = &entry;
}
if (njs_is_valid(value) && !njs_is_null_or_undefined(value)) {
if (!njs_is_string(value)) {
last = njs_chb_current(&chain);
ret = njs_value_to_chain(vm, &chain, value);
if (njs_slow_path(ret < NJS_OK)) {
return ret;
}
if (last != njs_chb_current(&chain) && ret == 0) {
/*
* Appended values was a byte string.
*/
utf8 = NJS_STRING_BYTE;
}
length += ret;
} else {
(void) njs_string_prop(&string, value);
if (njs_is_byte_string(&string)) {
utf8 = NJS_STRING_BYTE;
}
length += string.length;
njs_chb_append(&chain, string.start, string.size);
}
}
length += separator.length;
njs_chb_append(&chain, separator.start, separator.size);
if (njs_slow_path(length > NJS_STRING_MAX_LENGTH)) {
njs_range_error(vm, "invalid string length");
return NJS_ERROR;
}
}
njs_chb_drop(&chain, separator.size);
size = njs_chb_size(&chain);
if (njs_slow_path(size < 0)) {
njs_memory_error(vm);
return NJS_ERROR;
}
length -= separator.length;
p = njs_string_alloc(vm, &vm->retval, size, utf8 ? length : 0);
if (njs_slow_path(p == NULL)) {
return NJS_ERROR;
}
njs_chb_join_to(&chain, p);
njs_chb_destroy(&chain);
return NJS_OK;
}
static int
njs_array_indices_handler(const void *first, const void *second, void *ctx)
{
double num1, num2;
int64_t diff;
njs_str_t str1, str2;
const njs_value_t *val1, *val2;
val1 = first;
val2 = second;
num1 = njs_string_to_index(val1);
num2 = njs_string_to_index(val2);
if (!isnan(num1) || !isnan(num2)) {
if (isnan(num1)) {
return 1;
}
if (isnan(num2)) {
return -1;
}
diff = (int64_t) (num1 - num2);
if (diff < 0) {
return -1;
}
return diff != 0;
}
njs_string_get(val1, &str1);
njs_string_get(val2, &str2);
return strncmp((const char *) str1.start, (const char *) str2.start,
njs_min(str1.length, str2.length));
}
njs_array_t *
njs_array_keys(njs_vm_t *vm, njs_value_t *object, njs_bool_t all)
{
njs_array_t *keys;
keys = njs_value_own_enumerate(vm, object, NJS_ENUM_KEYS, NJS_ENUM_STRING,
all);
if (njs_slow_path(keys == NULL)) {
return NULL;
}
njs_qsort(keys->start, keys->length, sizeof(njs_value_t),
njs_array_indices_handler, NULL);
return keys;
}
njs_array_t *
njs_array_indices(njs_vm_t *vm, njs_value_t *object)
{
double idx;
uint32_t i;
njs_array_t *keys;
keys = njs_array_keys(vm, object, 1);
if (njs_slow_path(keys == NULL)) {
return NULL;
}
for (i = 0; i < keys->length; i++) {
idx = njs_string_to_index(&keys->start[i]);
if (isnan(idx)) {
keys->length = i;
break;
}
}
return keys;
}
njs_inline njs_int_t
njs_is_concat_spreadable(njs_vm_t *vm, njs_value_t *value)
{
njs_int_t ret;
njs_value_t retval;
static const njs_value_t key =
njs_wellknown_symbol(NJS_SYMBOL_IS_CONCAT_SPREADABLE);
if (njs_slow_path(!njs_is_object(value))) {
return NJS_DECLINED;
}
ret = njs_value_property(vm, value, njs_value_arg(&key), &retval);
if (njs_slow_path(ret == NJS_ERROR)) {
return NJS_ERROR;
}
if (njs_is_defined(&retval)) {
return njs_bool(&retval) ? NJS_OK : NJS_DECLINED;
}
return njs_is_array(value) ? NJS_OK : NJS_DECLINED;
}
static njs_int_t
njs_array_prototype_concat(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
njs_index_t unused)
{
double idx;
int64_t k, len, length;
njs_int_t ret;
njs_uint_t i;
njs_value_t this, retval, *value, *e;
njs_array_t *array, *keys;
ret = njs_value_to_object(vm, &args[0]);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
/* TODO: ArraySpeciesCreate(). */
array = njs_array_alloc(vm, 0, 0, NJS_ARRAY_SPARE);
if (njs_slow_path(array == NULL)) {
return NJS_ERROR;
}
njs_set_array(&this, array);
len = 0;
length = 0;
for (i = 0; i < nargs; i++) {
e = njs_argument(args, i);
ret = njs_is_concat_spreadable(vm, e);
if (njs_slow_path(ret == NJS_ERROR)) {
return NJS_ERROR;
}
if (ret == NJS_OK) {
ret = njs_object_length(vm, e, &len);
if (njs_slow_path(ret == NJS_ERROR)) {
return ret;
}
if (njs_slow_path((length + len) > NJS_MAX_LENGTH)) {
njs_type_error(vm, "Invalid length");
return NJS_ERROR;
}
if (njs_is_fast_array(&this) && njs_is_fast_array(e)
&& (length + len) <= NJS_ARRAY_LARGE_OBJECT_LENGTH)
{
for (k = 0; k < len; k++, length++) {
value = &njs_array_start(e)[k];
if (njs_slow_path(!njs_is_valid(value))) {
ret = njs_value_property_i64(vm, e, k, &retval);
if (njs_slow_path(ret == NJS_ERROR)) {
return ret;
}
if (ret == NJS_DECLINED) {
njs_set_invalid(&retval);
}
value = &retval;
}
ret = njs_array_add(vm, array, value);
if (njs_slow_path(ret != NJS_OK)) {
return NJS_ERROR;
}
}
continue;
}
if (njs_fast_object(len)) {
for (k = 0; k < len; k++, length++) {
ret = njs_value_property_i64(vm, e, k, &retval);
if (njs_slow_path(ret == NJS_ERROR)) {
return ret;
}
if (ret != NJS_OK) {
continue;
}
ret = njs_value_property_i64_set(vm, &this, length,
&retval);
if (njs_slow_path(ret == NJS_ERROR)) {
return ret;
}
}
continue;
}
keys = njs_array_indices(vm, e);
if (njs_slow_path(keys == NULL)) {
return NJS_ERROR;
}
for (k = 0; k < keys->length; k++) {
ret = njs_value_property(vm, e, &keys->start[k], &retval);
if (njs_slow_path(ret == NJS_ERROR)) {
return ret;
}
idx = njs_string_to_index(&keys->start[k]) + length;
if (ret == NJS_OK) {
ret = njs_value_property_i64_set(vm, &this, idx, &retval);
if (njs_slow_path(ret == NJS_ERROR)) {
njs_array_destroy(vm, keys);
return ret;
}
}
}
njs_array_destroy(vm, keys);
length += len;
continue;
}
if (njs_slow_path((length + len) >= NJS_MAX_LENGTH)) {
njs_type_error(vm, "Invalid length");
return NJS_ERROR;
}
if (njs_is_fast_array(&this)) {
ret = njs_array_add(vm, array, e);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
} else {
ret = njs_value_property_i64_set(vm, &this, length, e);
if (njs_slow_path(ret == NJS_ERROR)) {
return ret;
}
}
length++;
}
ret = njs_object_length_set(vm, &this, length);
if (njs_slow_path(ret != NJS_OK)) {
return NJS_ERROR;
}
vm->retval = this;
return NJS_OK;
}
static njs_int_t
njs_array_prototype_fill(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
njs_index_t unused)
{
int64_t i, length, start, end;
njs_int_t ret;
njs_array_t *array;
njs_value_t *this, *value;
this = njs_argument(args, 0);
ret = njs_value_to_object(vm, this);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
array = NULL;
if (njs_is_fast_array(this)) {
array = njs_array(this);
length = array->length;
} else {
ret = njs_object_length(vm, this, &length);
if (njs_slow_path(ret == NJS_ERROR)) {
return ret;
}
}
ret = njs_value_to_integer(vm, njs_arg(args, nargs, 2), &start);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
start = (start < 0) ? njs_max(length + start, 0) : njs_min(start, length);
if (njs_is_undefined(njs_arg(args, nargs, 3))) {
end = length;
} else {
ret = njs_value_to_integer(vm, njs_arg(args, nargs, 3), &end);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
}
end = (end < 0) ? njs_max(length + end, 0) : njs_min(end, length);
value = njs_arg(args, nargs, 1);
if (array != NULL) {
for (i = start; i < end; i++) {
array->start[i] = *value;
}
vm->retval = *this;
return NJS_OK;
}
value = njs_arg(args, nargs, 1);
while (start < end) {
ret = njs_value_property_i64_set(vm, this, start++, value);
if (njs_slow_path(ret == NJS_ERROR)) {
return ret;
}
}
vm->retval = *this;
return NJS_OK;
}
njs_inline njs_int_t
njs_array_iterator_call(njs_vm_t *vm, njs_array_iterator_args_t *args,
const njs_value_t *entry, uint32_t n)
{
njs_value_t arguments[3];
/* GC: array elt, array */
arguments[0] = *entry;
njs_set_number(&arguments[1], n);
arguments[2] = *args->value;
return njs_function_call(vm, args->function, args->argument, arguments, 3,
&vm->retval);
}
static njs_int_t
njs_array_object_handler(njs_vm_t *vm, njs_array_iterator_handler_t handler,
njs_array_iterator_args_t *args, njs_value_t *key, int64_t i)
{
njs_int_t ret;
njs_value_t prop, *entry;
if (key != NULL) {
ret = njs_value_property(vm, args->value, key, &prop);
if (njs_slow_path(ret == NJS_ERROR)) {
return ret;
}
} else {
ret = njs_value_property_i64(vm, args->value, i, &prop);
if (njs_slow_path(ret == NJS_ERROR)) {
return ret;
}
}
entry = (ret == NJS_OK) ? &prop : njs_value_arg(&njs_value_invalid);
ret = handler(vm, args, entry, i);
if (njs_slow_path(ret != NJS_OK)) {
if (ret == NJS_DONE) {
return NJS_DONE;
}
return NJS_ERROR;
}
return ret;
}
njs_inline njs_int_t
njs_array_iterator(njs_vm_t *vm, njs_array_iterator_args_t *args,
njs_array_iterator_handler_t handler)
{
double idx;
int64_t length, i, from, to;
njs_int_t ret;
njs_array_t *array, *keys;
njs_value_t *value, *entry, prop, character, string_obj;
njs_object_t *object;
const u_char *p, *end, *pos;
njs_string_prop_t string_prop;
value = args->value;
from = args->from;
to = args->to;
if (njs_is_array(value)) {
array = njs_array(value);
for (; from < to; from++) {
if (njs_slow_path(!array->object.fast_array)) {
goto process_object;
}
if (njs_fast_path(from < array->length
&& njs_is_valid(&array->start[from])))
{
ret = handler(vm, args, &array->start[from], from);
} else {
entry = njs_value_arg(&njs_value_invalid);
ret = njs_value_property_i64(vm, value, from, &prop);
if (njs_slow_path(ret != NJS_DECLINED)) {
if (ret == NJS_ERROR) {
return NJS_ERROR;
}
entry = &prop;
}
ret = handler(vm, args, entry, from);
}
if (njs_slow_path(ret != NJS_OK)) {
if (ret == NJS_DONE) {
return NJS_DONE;
}
return NJS_ERROR;
}
}
return NJS_OK;
}
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 = njs_string_prop(&string_prop, value);
p = string_prop.start;
end = p + string_prop.size;
if ((size_t) length == string_prop.size) {
/* Byte or ASCII string. */
for (i = from; i < to; i++) {
/* This cannot fail. */
(void) njs_string_new(vm, &character, p + i, 1, 1);
ret = handler(vm, args, &character, i);
if (njs_slow_path(ret != NJS_OK)) {
if (ret == NJS_DONE) {
return NJS_DONE;
}
return NJS_ERROR;
}
}
} else {
/* UTF-8 string. */
for (i = from; i < to; 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 == NJS_DONE) {
return NJS_DONE;
}
return NJS_ERROR;
}
p = pos;
}
}
return NJS_OK;
}
if (!njs_is_object(value)) {
return NJS_OK;
}
process_object:
if (!njs_fast_object(to - from)) {
keys = njs_array_indices(vm, value);
if (njs_slow_path(keys == NULL)) {
return NJS_ERROR;
}
for (i = 0; i < keys->length; i++) {
idx = njs_string_to_index(&keys->start[i]);
if (idx < from || idx >= to) {
continue;
}
ret = njs_array_object_handler(vm, handler, args, &keys->start[i],
idx);
if (njs_slow_path(ret != NJS_OK)) {
njs_array_destroy(vm, keys);
return ret;
}
}
njs_array_destroy(vm, keys);
return NJS_OK;
}
for (i = from; i < to; i++) {
ret = njs_array_object_handler(vm, handler, args, NULL, i);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
}
return NJS_OK;
}
static njs_int_t
njs_array_handler_every(njs_vm_t *vm, njs_array_iterator_args_t *args,
njs_value_t *entry, int64_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_false;
return NJS_DONE;
}
}
return NJS_OK;
}
static njs_int_t
njs_array_handler_some(njs_vm_t *vm, njs_array_iterator_args_t *args,
njs_value_t *entry, int64_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 NJS_DONE;
}
}
return NJS_OK;
}
static njs_int_t
njs_array_handler_includes(njs_vm_t *vm, njs_array_iterator_args_t *args,
njs_value_t *entry, int64_t n)
{
if (!njs_is_valid(entry)) {
entry = njs_value_arg(&njs_value_undefined);
}
if (njs_values_same_zero(args->argument, entry)) {
njs_set_true(&vm->retval);
return NJS_DONE;
}
return NJS_OK;
}
static njs_int_t
njs_array_handler_index_of(njs_vm_t *vm, njs_array_iterator_args_t *args,
njs_value_t *entry, int64_t n)
{
if (njs_values_strict_equal(args->argument, entry)) {
njs_set_number(&vm->retval, n);
return NJS_DONE;
}
return NJS_OK;
}
static njs_int_t
njs_array_handler_for_each(njs_vm_t *vm, njs_array_iterator_args_t *args,
njs_value_t *entry, int64_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_handler_find(njs_vm_t *vm, njs_array_iterator_args_t *args,
njs_value_t *entry, int64_t n)
{
njs_int_t ret;
njs_value_t copy;
if (njs_is_valid(entry)) {
copy = *entry;
} else {
njs_set_undefined(&copy);
}
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 NJS_DONE;
}
return NJS_OK;
}
static njs_int_t
njs_array_handler_find_index(njs_vm_t *vm, njs_array_iterator_args_t *args,
njs_value_t *entry, int64_t n)
{
njs_int_t ret;
njs_value_t copy;
if (njs_is_valid(entry)) {
copy = *entry;
} else {
njs_set_undefined(&copy);
}
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 NJS_DONE;
}
return NJS_OK;
}
static njs_int_t
njs_array_handler_reduce(njs_vm_t *vm, njs_array_iterator_args_t *args,
njs_value_t *entry, int64_t n)
{
njs_int_t ret;
njs_value_t arguments[5];
if (njs_is_valid(entry)) {
if (!njs_is_valid(args->argument)) {
*(args->argument) = *entry;
return NJS_OK;
}
/* GC: array elt, array */
njs_set_undefined(&arguments[0]);
arguments[1] = *args->argument;
arguments[2] = *entry;
njs_set_number(&arguments[3], n);
arguments[4] = *args->value;
ret = njs_function_apply(vm, args->function, arguments, 5,
args->argument);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
}
return NJS_OK;
}
static njs_int_t
njs_array_handler_filter(njs_vm_t *vm, njs_array_iterator_args_t *args,
njs_value_t *entry, int64_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;
}
}
}
return NJS_OK;
}
static njs_int_t
njs_array_handler_map(njs_vm_t *vm, njs_array_iterator_args_t *args,
njs_value_t *entry, int64_t n)
{
njs_int_t ret;
njs_array_t *retval;
njs_value_t this;
retval = args->array;
if (retval->object.fast_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_valid(&vm->retval)) {
if (retval->object.fast_array) {
retval->start[n] = vm->retval;
} else {
njs_set_array(&this, retval);
ret = njs_value_property_i64_set(vm, &this, n, &vm->retval);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
}
}
}
return NJS_OK;
}
static njs_int_t
njs_array_prototype_iterator(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
njs_index_t magic)
{
int64_t i, length;
njs_int_t ret;
njs_array_t *array;
njs_value_t accumulator;
njs_array_iterator_args_t iargs;
njs_array_iterator_handler_t handler;
iargs.value = njs_argument(args, 0);
ret = njs_value_to_object(vm, iargs.value);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
ret = njs_value_length(vm, iargs.value, &iargs.to);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
iargs.from = 0;
if (njs_array_arg1(magic) == NJS_ARRAY_FUNC) {
if (njs_slow_path(!njs_is_function(njs_arg(args, nargs, 1)))) {
njs_type_error(vm, "callback argument is not callable");
return NJS_ERROR;
}
iargs.function = njs_function(njs_argument(args, 1));
iargs.argument = njs_arg(args, nargs, 2);
} else {
iargs.argument = njs_arg(args, nargs, 1);
}
switch (njs_array_type(magic)) {
case NJS_ARRAY_EVERY:
handler = njs_array_handler_every;
break;
case NJS_ARRAY_SOME:
handler = njs_array_handler_some;
break;
case NJS_ARRAY_INCLUDES:
case NJS_ARRAY_INDEX_OF:
switch (njs_array_type(magic)) {
case NJS_ARRAY_INCLUDES:
handler = njs_array_handler_includes;
if (iargs.to == 0) {
goto done;
}
break;
default:
handler = njs_array_handler_index_of;
}
ret = njs_value_to_integer(vm, njs_arg(args, nargs, 2), &iargs.from);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
if (iargs.from < 0) {
iargs.from += iargs.to;
if (iargs.from < 0) {
iargs.from = 0;
}
}
break;
case NJS_ARRAY_FOR_EACH:
handler = njs_array_handler_for_each;
break;
case NJS_ARRAY_FIND:
handler = njs_array_handler_find;
break;
case NJS_ARRAY_FIND_INDEX:
handler = njs_array_handler_find_index;
break;
case NJS_ARRAY_REDUCE:
handler = njs_array_handler_reduce;
njs_set_invalid(&accumulator);
if (nargs > 2) {
accumulator = *iargs.argument;
}
iargs.argument = &accumulator;
break;
case NJS_ARRAY_FILTER:
case NJS_ARRAY_MAP:
default:
if (njs_array_type(magic) == NJS_ARRAY_FILTER) {
length = 0;
handler = njs_array_handler_filter;
} else {
length = iargs.to;
handler = njs_array_handler_map;
}
array = njs_array_alloc(vm, 0, length, NJS_ARRAY_SPARE);
if (njs_slow_path(array == NULL)) {
return NJS_ERROR;
}
if (array->object.fast_array) {
for (i = 0; i < length; i++) {
njs_set_invalid(&array->start[i]);
}
}
iargs.array = array;
break;
}
ret = njs_array_iterator(vm, &iargs, handler);
if (njs_slow_path(ret == NJS_ERROR)) {
return ret;
}
if (ret == NJS_DONE) {
return NJS_OK;
}
done:
/* Default values. */
switch (njs_array_type(magic)) {
case NJS_ARRAY_EVERY:
vm->retval = njs_value_true;
break;
case NJS_ARRAY_SOME:
case NJS_ARRAY_INCLUDES:
vm->retval = njs_value_false;
break;
case NJS_ARRAY_INDEX_OF:
case NJS_ARRAY_FIND_INDEX:
njs_set_number(&vm->retval, -1);
break;
case NJS_ARRAY_FOR_EACH:
case NJS_ARRAY_FIND:
njs_set_undefined(&vm->retval);
break;
case NJS_ARRAY_REDUCE:
if (!njs_is_valid(&accumulator)) {
njs_type_error(vm, "Reduce of empty object with no initial value");
return NJS_ERROR;
}
vm->retval = accumulator;
break;
case NJS_ARRAY_FILTER:
case NJS_ARRAY_MAP:
default:
njs_set_array(&vm->retval, iargs.array);
}
return NJS_OK;
}
njs_inline njs_int_t
njs_array_reverse_iterator(njs_vm_t *vm, njs_array_iterator_args_t *args,
njs_array_iterator_handler_t handler)
{
double idx;
int64_t i, from, to, length;
njs_int_t ret;
njs_array_t *array, *keys;
njs_value_t *entry, *value, prop, character, string_obj;
njs_object_t *object;
const u_char *p, *end, *pos;
njs_string_prop_t string_prop;
value = args->value;
from = args->from;
to = args->to;
if (njs_is_array(value)) {
array = njs_array(value);
from += 1;
while (from-- > to) {
if (njs_slow_path(!array->object.fast_array)) {
goto process_object;
}
if (njs_fast_path(from < array->length
&& njs_is_valid(&array->start[from])))
{
ret = handler(vm, args, &array->start[from], from);
} else {
entry = njs_value_arg(&njs_value_invalid);
ret = njs_value_property_i64(vm, value, from, &prop);
if (njs_slow_path(ret != NJS_DECLINED)) {
if (ret == NJS_ERROR) {
return NJS_ERROR;
}
entry = &prop;
}
ret = handler(vm, args, entry, from);
}
if (njs_slow_path(ret != NJS_OK)) {
if (ret == NJS_DONE) {
return NJS_DONE;
}
return NJS_ERROR;
}
}
return NJS_OK;
}
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 = njs_string_prop(&string_prop, value);
end = string_prop.start + string_prop.size;
if ((size_t) length == string_prop.size) {
/* Byte or ASCII string. */
p = string_prop.start + from;
i = from + 1;
while (i-- > to) {
/* 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 == NJS_DONE) {
return NJS_DONE;
}
return NJS_ERROR;
}
p--;
}
} else {
/* UTF-8 string. */
p = njs_string_offset(string_prop.start, end, from);
p = njs_utf8_next(p, end);
i = from + 1;
while (i-- > to) {
pos = njs_utf8_prev(p);
/* This cannot fail. */
(void) njs_string_new(vm, &character, pos, p - pos , 1);
ret = handler(vm, args, &character, i);
if (njs_slow_path(ret != NJS_OK)) {
if (ret == NJS_DONE) {
return NJS_DONE;
}
return NJS_ERROR;
}
p = pos;
}
}
return NJS_OK;
}
if (!njs_is_object(value)) {
return NJS_OK;
}
process_object:
if (!njs_fast_object(from - to)) {
keys = njs_array_indices(vm, value);
if (njs_slow_path(keys == NULL)) {
return NJS_ERROR;
}
i = keys->length;
while (i > 0) {
idx = njs_string_to_index(&keys->start[--i]);
if (idx < to || idx > from) {
continue;
}
ret = njs_array_object_handler(vm, handler, args, &keys->start[i],
idx);
if (njs_slow_path(ret != NJS_OK)) {
njs_array_destroy(vm, keys);
return ret;
}
}
njs_array_destroy(vm, keys);
return NJS_OK;
}
i = from + 1;
while (i-- > to) {
ret = njs_array_object_handler(vm, handler, args, NULL, i);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
}
return NJS_OK;
}
static njs_int_t
njs_array_prototype_reverse_iterator(njs_vm_t *vm, njs_value_t *args,
njs_uint_t nargs, njs_index_t type)
{
int64_t from, length;
njs_int_t ret;
njs_value_t accumulator;
njs_array_iterator_args_t iargs;
njs_array_iterator_handler_t handler;
iargs.value = njs_argument(args, 0);
ret = njs_value_to_object(vm, iargs.value);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
iargs.argument = njs_arg(args, nargs, 1);
ret = njs_value_length(vm, iargs.value, &length);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
switch (type) {
case NJS_ARRAY_LAST_INDEX_OF:
handler = njs_array_handler_index_of;
if (length == 0) {
goto done;
}
if (nargs > 2) {
ret = njs_value_to_integer(vm, njs_arg(args, nargs, 2), &from);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
} else {
from = length - 1;
}
if (from >= 0) {
from = njs_min(from, length - 1);
} else if (from < 0) {
from += length;
}
break;
case NJS_ARRAY_REDUCE_RIGHT:
default:
handler = njs_array_handler_reduce;
if (njs_slow_path(!njs_is_function(njs_arg(args, nargs, 1)))) {
njs_type_error(vm, "callback argument is not callable");
return NJS_ERROR;
}
njs_set_invalid(&accumulator);
iargs.function = njs_function(njs_argument(args, 1));
iargs.argument = &accumulator;
if (nargs > 2) {
accumulator = *njs_argument(args, 2);
} else if (length == 0) {
goto done;
}
from = length - 1;
break;
}
iargs.from = from;
iargs.to = 0;
ret = njs_array_reverse_iterator(vm, &iargs, handler);
if (njs_fast_path(ret == NJS_ERROR)) {
return NJS_ERROR;
}
if (ret == NJS_DONE) {
return NJS_OK;
}
done:
switch (type) {
case NJS_ARRAY_LAST_INDEX_OF:
njs_set_number(&vm->retval, -1);
break;
case NJS_ARRAY_REDUCE_RIGHT:
default:
if (!njs_is_valid(&accumulator)) {
njs_type_error(vm, "Reduce of empty object with no initial value");
return NJS_ERROR;
}
vm->retval = accumulator;
break;
}
return NJS_OK;
}
typedef struct {
njs_value_t value;
njs_value_t *str;
int64_t pos;
} njs_array_sort_slot_t;
typedef struct {
njs_vm_t *vm;
njs_function_t *function;
njs_bool_t exception;
njs_arr_t strings;
} njs_array_sort_ctx_t;
static int
njs_array_compare(const void *a, const void *b, void *c)
{
double num;
njs_int_t ret;
njs_value_t arguments[3], retval;
njs_array_sort_ctx_t *ctx;
njs_array_sort_slot_t *aslot, *bslot;
ctx = c;
if (ctx->exception) {
return 0;
}
aslot = (njs_array_sort_slot_t *) a;
bslot = (njs_array_sort_slot_t *) b;
if (ctx->function != NULL) {
njs_set_undefined(&arguments[0]);
arguments[1] = aslot->value;
arguments[2] = bslot->value;
ret = njs_function_apply(ctx->vm, ctx->function, arguments, 3, &retval);
if (njs_slow_path(ret != NJS_OK)) {
goto exception;
}
ret = njs_value_to_number(ctx->vm, &retval, &num);
if (njs_slow_path(ret != NJS_OK)) {
goto exception;
}
if (njs_slow_path(isnan(num))) {
return 0;
}
if (num != 0) {
return (num > 0) - (num < 0);
}
goto compare_same;
}
if (aslot->str == NULL) {
aslot->str = njs_arr_add(&ctx->strings);
ret = njs_value_to_string(ctx->vm, aslot->str, &aslot->value);
if (njs_slow_path(ret != NJS_OK)) {
goto exception;
}
}
if (bslot->str == NULL) {
bslot->str = njs_arr_add(&ctx->strings);
ret = njs_value_to_string(ctx->vm, bslot->str, &bslot->value);
if (njs_slow_path(ret != NJS_OK)) {