]> git.kaiwu.me - njs.git/commitdiff
Native function continuation support.
authorIgor Sysoev <igor@sysoev.ru>
Mon, 8 Feb 2016 14:41:09 +0000 (17:41 +0300)
committerIgor Sysoev <igor@sysoev.ru>
Mon, 8 Feb 2016 14:41:09 +0000 (17:41 +0300)
njs/njs_array.c
njs/njs_function.c
njs/njs_function.h
njs/njs_vm.c
njs/njs_vm.h
njs/njscript.c
njs/test/njs_unit_test.c

index 969b3776b184c010fa4c7aac8f8e7467fcb96a1c..ef53cd5741b1e30ed1d6b40d593dd244baeaeec5 100644 (file)
 
 
 typedef struct {
-    njs_value_t  retval;
-    int32_t      index;
-    uint32_t     length;
+    njs_continuation_t  continuation;
+    njs_value_t         *values;
+    uint32_t            max;
+} njs_array_join_t;
+
+
+typedef struct {
+    njs_value_t         retval;
+    int32_t             index;
+    uint32_t            length;
 } njs_array_next_t;
 
 
+static njs_ret_t njs_array_prototype_join_continuation(njs_vm_t *vm,
+    njs_param_t *param);
 static nxt_noinline njs_value_t *njs_array_copy(njs_value_t *dst,
     njs_value_t *src);
 static nxt_noinline nxt_int_t njs_array_next(njs_value_t *value, nxt_uint_t n,
@@ -508,13 +517,11 @@ njs_array_prototype_to_string(njs_vm_t *vm, njs_param_t *param)
 static njs_ret_t
 njs_array_prototype_join(njs_vm_t *vm, njs_param_t *param)
 {
-    u_char             *p;
-    size_t             size, length;
-    nxt_int_t          ret;
-    nxt_uint_t         i, n, max;
-    njs_array_t        *array;
-    njs_value_t        *this, *value, *values;
-    njs_string_prop_t  separator, string;
+    uint32_t          max;
+    nxt_uint_t        i, n;
+    njs_array_t       *array;
+    njs_value_t       *this, *value, *values;
+    njs_array_join_t  *join;
 
     this = param->this;
 
@@ -528,15 +535,6 @@ njs_array_prototype_join(njs_vm_t *vm, njs_param_t *param)
         goto empty;
     }
 
-    if (param->nargs != 0) {
-        value = &param->args[0];
-
-    } else {
-        value = (njs_value_t *) &njs_string_comma;
-    }
-
-    (void) njs_string_prop(&separator, value);
-
     max = 0;
 
     for (i = 0; i < array->length; i++) {
@@ -546,37 +544,118 @@ njs_array_prototype_join(njs_vm_t *vm, njs_param_t *param)
         }
     }
 
-    values = nxt_mem_cache_align(vm->mem_cache_pool, sizeof(njs_value_t),
-                                 sizeof(njs_value_t) * max);
-    if (nxt_slow_path(values == NULL)) {
-        return NXT_ERROR;
+    if (max != 0) {
+        join = nxt_mem_cache_alloc(vm->mem_cache_pool,
+                                   sizeof(njs_array_join_t));
+        if (nxt_slow_path(join == NULL)) {
+            return NXT_ERROR;
+        }
+
+        values = nxt_mem_cache_align(vm->mem_cache_pool, sizeof(njs_value_t),
+                                     sizeof(njs_value_t) * max);
+        if (nxt_slow_path(values == NULL)) {
+            return NXT_ERROR;
+        }
+
+        join->continuation.function = njs_array_prototype_join_continuation;
+        join->continuation.this = this;
+        join->continuation.args = param->args;
+        join->continuation.nargs = param->nargs;
+        join->values = values;
+        join->max = max;
+        vm->frame->continuation = &join->continuation;
+
+        n = 0;
+
+        for (i = 0; i < array->length; i++) {
+            value = &array->start[i];
+            if (njs_is_valid(value) && !njs_is_string(value)) {
+                values[n++] = *value;
+
+                if (n >= max) {
+                    break;
+                }
+            }
+        }
     }
 
+    return njs_array_prototype_join_continuation(vm, param);
+
+empty:
+
+    vm->retval = njs_string_empty;
+
+    return NXT_OK;
+}
+
+
+static njs_ret_t
+njs_array_prototype_join_continuation(njs_vm_t *vm, njs_param_t *param)
+{
+    u_char             *p;
+    size_t             size, length, mask;
+    uint32_t           max;
+    nxt_uint_t         i, n;
+    njs_array_t        *array;
+    njs_value_t        *value, *values;
+    njs_array_join_t   *join;
+    njs_string_prop_t  separator, string;
+
+    if (param->nargs != 0) {
+        value = &param->args[0];
+
+    } else {
+        value = (njs_value_t *) &njs_string_comma;
+    }
+
+    (void) njs_string_prop(&separator, value);
+
+    array = param->this->data.u.array;
+
     size = separator.size * (array->length - 1);
     length = separator.length * (array->length - 1);
     n = 0;
 
+    max = 0;
+    values = NULL;
+
+    join = (njs_array_join_t *) vm->frame->continuation;
+
+    if (join != NULL) {
+        values = join->values;
+        max = join->max;
+    }
+
+    mask = -1;
+
     for (i = 0; i < array->length; i++) {
         value = &array->start[i];
 
         if (njs_is_valid(value)) {
 
             if (!njs_is_string(value)) {
-                ret = njs_value_to_string(vm, &values[n], value);
-                if (nxt_slow_path(ret != NXT_OK)) {
-                    return NXT_ERROR;
-                }
-
                 value = &values[n++];
+
+                if (!njs_is_string(value)) {
+                    vm->frame->trap_scratch.data.u.value = value;
+
+                    return NJS_TRAP_STRING_ARG;
+                }
             }
 
             (void) njs_string_prop(&string, value);
 
             size += string.size;
             length += string.length;
+
+            if (string.length == 0 && string.size != 0) {
+                mask = 0;
+            }
         }
     }
 
+    length &= mask;
+
     p = njs_string_alloc(vm, &vm->retval, size, length);
     if (nxt_slow_path(p == NULL)) {
         return NXT_ERROR;
@@ -610,11 +689,7 @@ njs_array_prototype_join(njs_vm_t *vm, njs_param_t *param)
 
     nxt_mem_cache_free(vm->mem_cache_pool, values);
 
-    return NXT_OK;
-
-empty:
-
-    vm->retval = njs_string_empty;
+    vm->frame->continuation = NULL;
 
     return NXT_OK;
 }
index 2c2af49137b2b9e392fa4a61b4b9b723014f54f1..6e84a90925468275f6fc9dba685e250486267029 100644 (file)
@@ -57,7 +57,7 @@ njs_function_native_frame(njs_vm_t *vm, njs_function_t *function,
         return NULL;
     }
 
-    frame->u.function = function;
+    frame->function = function;
     frame->ctor = code->ctor;
 
     this = (njs_value_t *) ((u_char *) njs_native_data(frame)
@@ -99,8 +99,11 @@ njs_function_frame_alloc(njs_vm_t *vm, size_t size)
     frame->free_size = spare_size - size;
     frame->free = (u_char *) frame + size;
 
+    frame->continuation = NULL;
+    frame->trap_restart = NULL;
     frame->ctor = 0;
     frame->reentrant = 0;
+    frame->trap_frame = 0;
     frame->trap_tries = 0;
     frame->trap_reference = 0;
 
@@ -169,7 +172,7 @@ njs_function_frame(njs_vm_t *vm, njs_function_t *function, njs_param_t *param,
         return NXT_ERROR;
     }
 
-    native_frame->u.function = function;
+    native_frame->function = function;
     native_frame->ctor = ctor;
 
     args = (njs_value_t *) ((u_char *) native_frame + NJS_FRAME_SIZE);
@@ -217,8 +220,8 @@ njs_function_call(njs_vm_t *vm, njs_index_t retval, size_t advance)
 
     frame->retval = retval;
 
-    function = frame->native.u.function;
-    frame->native.u.return_address = vm->current + advance;
+    function = frame->native.function;
+    frame->return_address = vm->current + advance;
     vm->current = function->u.lambda->u.start;
 
     frame->prev_arguments = vm->scopes[NJS_SCOPE_ARGUMENTS];
@@ -416,6 +419,11 @@ njs_function_prototype_bind(njs_vm_t *vm, njs_param_t *param)
 {
     njs_function_t  *bound;
 
+    if (!njs_is_function(param->this)) {
+        vm->exception = &njs_exception_type_error;
+        return NXT_ERROR;
+    }
+
     bound = nxt_mem_cache_alloc(vm->mem_cache_pool, sizeof(njs_function_t));
 
     if (nxt_fast_path(bound != NULL)) {
index 74abdd028f5507239c152da1168d84022a542b8b..1ba98e2516dc2678249915bd99e58e8d06717429 100644 (file)
@@ -52,6 +52,14 @@ struct njs_function_lambda_s {
     (void *) ((u_char *) frame + NJS_NATIVE_FRAME_SIZE)
 
 
+typedef struct {
+    njs_native_t                   function;
+    njs_value_t                    *this;
+    njs_value_t                    *args;
+    nxt_uint_t                     nargs;
+} njs_continuation_t;
+
+
 typedef struct njs_exception_s     njs_exception_t;
 
 struct njs_exception_s {
@@ -71,15 +79,8 @@ struct njs_native_frame_s {
 
     u_char                         *free;
 
-    /*
-     * The return_address is required in njs_frame_t only, however, it
-     * can be stored here just after function address has been fetched.
-     */
-    union {
-        njs_function_t             *function;
-        u_char                     *return_address;
-    } u;
-
+    njs_function_t                 *function;
+    njs_continuation_t             *continuation;
     njs_native_frame_t             *previous;
     njs_value_t                    *arguments;
 
@@ -103,6 +104,9 @@ struct njs_native_frame_s {
     /* The function is reentrant. */
     uint8_t                        reentrant:1;       /* 1 bit  */
 
+    /* A frame of trap generated from continuation. */
+    uint8_t                        trap_frame:1;      /* 1 bit */
+
     /* A number of trap tries, it can be no more than three. */
     uint8_t                        trap_tries:2;      /* 2 bits */
 
@@ -117,6 +121,7 @@ struct njs_native_frame_s {
 typedef struct {
     njs_native_frame_t             native;
 
+    u_char                         *return_address;
     njs_value_t                    *prev_arguments;
     njs_value_t                    *prev_local;
     njs_value_t                    *local;
index 61ffbde5a2e9ef1ff3c39e016be5feb03b45115e..d0a27d6a6f7ffb7b6036b8e6eb71228772085951 100644 (file)
@@ -86,7 +86,7 @@ static nxt_noinline njs_ret_t njs_function_frame_free(njs_vm_t *vm,
 
 static void njs_vm_trap(njs_vm_t *vm, nxt_uint_t trap, njs_value_t *value1,
     njs_value_t *value2);
-static void njs_vm_trap_argument(njs_vm_t *vm, nxt_uint_t trap);
+static njs_ret_t njs_vm_trap_argument(njs_vm_t *vm, nxt_uint_t trap);
 static njs_ret_t njs_vmcode_number_primitive(njs_vm_t *vm, njs_value_t *invld,
     njs_value_t *narg);
 static njs_ret_t njs_vmcode_string_primitive(njs_vm_t *vm, njs_value_t *invld,
@@ -233,9 +233,12 @@ njs_vmcode_interpreter(njs_vm_t *vm)
         case NJS_TRAP_NUMBER_ARG:
         case NJS_TRAP_STRING_ARG:
 
-            njs_vm_trap_argument(vm, ret - NJS_TRAP_BASE);
+            ret = njs_vm_trap_argument(vm, ret - NJS_TRAP_BASE);
+            if (nxt_fast_path(ret == NXT_OK)) {
+                goto again;
+            }
 
-            goto again;
+            break;
 
         default:
             break;
@@ -2238,11 +2241,13 @@ njs_vmcode_function_call(njs_vm_t *vm, njs_value_t *invld, njs_value_t *retval)
     njs_ret_t                   ret;
     njs_value_t                 *args;
     njs_param_t                 param;
+    njs_native_t                native;
     njs_function_t              *function;
     njs_native_frame_t          *frame, *previous, *skip;
+    njs_continuation_t          *continuation;
     njs_vmcode_function_call_t  *call;
 
-    function = vm->frame->u.function;
+    function = vm->frame->function;
 
     if (!function->native) {
         (void) njs_function_call(vm, (njs_index_t) retval,
@@ -2253,18 +2258,32 @@ njs_vmcode_function_call(njs_vm_t *vm, njs_value_t *invld, njs_value_t *retval)
     call = (njs_vmcode_function_call_t *) vm->current;
     args = vm->scopes[NJS_SCOPE_CALLEE_ARGUMENTS];
 
-    ret = njs_normalize_args(vm, args - 1, function->args_types,
-                             call->code.nargs);
-    if (ret != NJS_OK) {
-        return ret;
-    }
+    continuation = vm->frame->continuation;
+
+    if (continuation == NULL) {
+        ret = njs_normalize_args(vm, args - 1, function->args_types,
+                                 call->code.nargs);
+        if (ret != NJS_OK) {
+            return ret;
+        }
 
-    param.retval = (njs_index_t) retval;
-    param.nargs = call->code.nargs - 1;
-    param.args = args;
-    param.this = args - 1;
+        param.args = args;
+        param.this = args - 1;
+        param.nargs = call->code.nargs - 1;
+        param.retval = (njs_index_t) retval;
 
-    ret = vm->frame->u.function->u.native(vm, &param);
+        native = function->u.native;
+
+    } else {
+        param.this = continuation->this;
+        param.args = continuation->args;
+        param.retval = (njs_index_t) retval;
+        param.nargs = continuation->nargs;
+
+        native = continuation->function;
+    }
+
+    ret = native(vm, &param);
     /*
      * A native method can return:
      *   NXT_OK on method success;
@@ -2487,7 +2506,7 @@ njs_vmcode_return(njs_vm_t *vm, njs_value_t *invld, njs_value_t *retval)
     /* GC: value external/internal++ depending on value and retval type */
     *retval = *value;
 
-    vm->current = frame->native.u.return_address;
+    vm->current = frame->return_address;
 
     /* GC: arguments and local. */
 
@@ -2707,6 +2726,10 @@ static void
 njs_vm_trap(njs_vm_t *vm, nxt_uint_t trap, njs_value_t *value1,
     njs_value_t *value2)
 {
+    njs_native_frame_t  *frame;
+
+    frame = vm->frame;
+
     /*
      * The trap_scratch value is for results of "valueOf" and "toString"
      * methods.  The trap_values[] are original operand values which will
@@ -2715,35 +2738,51 @@ njs_vm_trap(njs_vm_t *vm, nxt_uint_t trap, njs_value_t *value1,
      * original operand values for the second method call if the first
      * method call will return non-primitive value.
      */
-    njs_set_invalid(&vm->frame->trap_scratch);
-    vm->frame->trap_values[1] = *value2;
-    vm->frame->trap_reference = njs_vm_traps[trap].reference_value;
+    njs_set_invalid(&frame->trap_scratch);
+    frame->trap_values[1] = *value2;
+    frame->trap_reference = njs_vm_traps[trap].reference_value;
 
     if (njs_vm_traps[trap].reference_value) {
-        vm->frame->trap_values[0].data.u.value = value1;
+        frame->trap_values[0].data.u.value = value1;
 
     } else {
-        vm->frame->trap_values[0] = *value1;
+        frame->trap_values[0] = *value1;
     }
 
-    vm->frame->trap_restart = vm->current;
+    frame->trap_restart = vm->current;
     vm->current = (u_char *) njs_vm_traps[trap].code;
 }
 
 
-static void
+static njs_ret_t
 njs_vm_trap_argument(njs_vm_t *vm, nxt_uint_t trap)
 {
-    njs_value_t  *value;
+    njs_value_t         *value;
+    njs_native_frame_t  *frame;
 
-    value = vm->frame->trap_scratch.data.u.value;
-    vm->frame->trap_values[1].data.u.value = value;
-    vm->frame->trap_values[0] = *value;
+    frame = vm->frame;
+    value = frame->trap_scratch.data.u.value;
+    njs_set_invalid(&frame->trap_scratch);
 
-    njs_set_invalid(&vm->frame->trap_scratch);
+    if (frame->continuation != NULL) {
+        frame = njs_function_frame_alloc(vm, NJS_NATIVE_FRAME_SIZE);
+
+        if (nxt_slow_path(frame == NULL)) {
+            return NXT_ERROR;
+        }
 
-    vm->frame->trap_restart = vm->current;
+        frame->trap_frame = 1;
+    }
+
+    frame->trap_values[1].data.u.value = value;
+    frame->trap_values[0] = *value;
+
+    njs_set_invalid(&frame->trap_scratch);
+
+    frame->trap_restart = vm->current;
     vm->current = (u_char *) njs_vm_traps[trap].code;
+
+    return NXT_OK;
 }
 
 
@@ -2803,9 +2842,10 @@ static njs_ret_t
 njs_vmcode_number_argument(njs_vm_t *vm, njs_value_t *invld1,
     njs_value_t *inlvd2)
 {
-    double       num;
-    njs_ret_t    ret;
-    njs_value_t  *value;
+    double              num;
+    njs_ret_t           ret;
+    njs_value_t         *value;
+    njs_native_frame_t  *frame;
 
     value = &vm->frame->trap_values[0];
 
@@ -2823,8 +2863,19 @@ njs_vmcode_number_argument(njs_vm_t *vm, njs_value_t *invld1,
             njs_number_set(value, num);
         }
 
-        *vm->frame->trap_values[1].data.u.value = *value;
+        frame = vm->frame;
+        *frame->trap_values[1].data.u.value = *value;
+
         vm->current = vm->frame->trap_restart;
+        frame->trap_restart = NULL;
+
+        if (frame->trap_frame) {
+            vm->frame = frame->previous;
+
+            if (frame->first) {
+                nxt_mem_cache_free(vm->mem_cache_pool, frame);
+            }
+        }
 
         return 0;
     }
@@ -2837,8 +2888,9 @@ static njs_ret_t
 njs_vmcode_string_argument(njs_vm_t *vm, njs_value_t *invld1,
     njs_value_t *inlvd2)
 {
-    njs_ret_t    ret;
-    njs_value_t  *value;
+    njs_ret_t           ret;
+    njs_value_t         *value;
+    njs_native_frame_t  *frame;
 
     value = &vm->frame->trap_values[0];
 
@@ -2848,8 +2900,19 @@ njs_vmcode_string_argument(njs_vm_t *vm, njs_value_t *invld1,
         ret = njs_primitive_value_to_string(vm, value, value);
 
         if (nxt_fast_path(ret == NXT_OK)) {
-            *vm->frame->trap_values[1].data.u.value = *value;
-            vm->current = vm->frame->trap_restart;
+            frame = vm->frame;
+            *frame->trap_values[1].data.u.value = *value;
+
+            vm->current = frame->trap_restart;
+            frame->trap_restart = NULL;
+
+            if (frame->trap_frame) {
+                vm->frame = frame->previous;
+
+                if (frame->first) {
+                    nxt_mem_cache_free(vm->mem_cache_pool, frame);
+                }
+            }
         }
     }
 
@@ -2871,6 +2934,7 @@ njs_primitive_value(njs_vm_t *vm, njs_value_t *value, nxt_uint_t hint)
     njs_value_t         *retval;
     njs_object_prop_t   *prop;
     nxt_lvlhsh_query_t  lhq;
+    njs_continuation_t  *continuation;
 
     static const uint32_t  hashes[] = {
         NJS_VALUE_OF_HASH,
@@ -2882,6 +2946,29 @@ njs_primitive_value(njs_vm_t *vm, njs_value_t *value, nxt_uint_t hint)
         nxt_string("toString"),
     };
 
+    continuation = vm->frame->continuation;
+
+    if (continuation != NULL) {
+        param.this = continuation->this;
+        param.args = continuation->args;
+        param.nargs = continuation->nargs;
+        param.retval = (njs_index_t) &vm->frame->trap_scratch;
+
+        ret = continuation->function(vm, &param);
+
+        if (ret != NXT_OK) {
+            return ret;
+        }
+
+        if (njs_is_primitive(&vm->retval)) {
+            *value = vm->retval;
+            njs_set_invalid(&vm->frame->trap_scratch);
+            vm->frame->trap_tries = 0;
+
+            return 1;
+        }
+    }
+
     if (!njs_is_primitive(value)) {
         retval = &vm->frame->trap_scratch;
 
@@ -2959,19 +3046,22 @@ njs_vmcode_restart(njs_vm_t *vm, njs_value_t *invld1, njs_value_t *invld2)
     u_char                *restart;
     njs_ret_t             ret;
     njs_value_t           *retval, *value1;
+    njs_native_frame_t    *frame;
     njs_vmcode_generic_t  *vmcode;
 
-    restart = vm->frame->trap_restart;
+    frame = vm->frame;
+    restart = frame->trap_restart;
+    frame->trap_restart = NULL;
     vm->current = restart;
     vmcode = (njs_vmcode_generic_t *) restart;
 
-    value1 = &vm->frame->trap_values[0];
+    value1 = &frame->trap_values[0];
 
-    if (vm->frame->trap_reference) {
+    if (frame->trap_reference) {
         value1 = value1->data.u.value;
     }
 
-    ret = vmcode->code.operation(vm, value1, &vm->frame->trap_values[1]);
+    ret = vmcode->code.operation(vm, value1, &frame->trap_values[1]);
 
     retval = njs_vmcode_operand(vm, vmcode->operand1);
 
@@ -2979,6 +3069,14 @@ njs_vmcode_restart(njs_vm_t *vm, njs_value_t *invld1, njs_value_t *invld2)
 
     *retval = vm->retval;
 
+    if (frame->trap_frame) {
+        vm->frame = frame->previous;
+
+        if (frame->first) {
+            nxt_mem_cache_free(vm->mem_cache_pool, frame);
+        }
+    }
+
     return ret;
 }
 
index 552eaa2194a26035ee1314705148e9c6c3b56c6f..469d64151571ce6e262ad00e67cbc9a521c593ce 100644 (file)
@@ -888,6 +888,9 @@ njs_ret_t njs_vmcode_finally(njs_vm_t *vm, njs_value_t *invld,
 njs_ret_t njs_normalize_args(njs_vm_t *vm, njs_value_t *args,
     uint8_t *args_types, nxt_uint_t nargs);
 
+njs_ret_t njs_native_function_arguments(njs_vm_t *vm, njs_value_t *args,
+    uint8_t *args_types, nxt_uint_t nargs);
+
 njs_ret_t njs_value_to_ext_string(njs_vm_t *vm, nxt_str_t *dst,
     const njs_value_t *src);
 void njs_number_set(njs_value_t *value, double num);
index 540caab171613c7ccaae272d3be68c4b90f0c6c2..5a7b43572826b4ab1e3d86ee91084a349c951edb 100644 (file)
@@ -279,18 +279,21 @@ njs_vm_clone(njs_vm_t *vm, nxt_mem_cache_pool_t *mcp, void **external)
         size = NJS_GLOBAL_FRAME_SIZE + scope_size + NJS_FRAME_SPARE_SIZE;
         size = nxt_align_size(size, NJS_FRAME_SPARE_SIZE);
 
-        frame = nxt_mem_cache_align(nmcp, sizeof(njs_value_t), size);
+        frame = nxt_mem_cache_zalign(nmcp, sizeof(njs_value_t), size);
         if (nxt_slow_path(frame == NULL)) {
             goto fail;
         }
 
         nvm->frame = &frame->native;
 
+        frame->native.trap_restart = NULL;
+        frame->native.continuation = NULL;
         frame->native.previous = NULL;
         frame->native.arguments = NULL;
         frame->native.first = 1;
         frame->native.skip = 0;
         frame->native.reentrant = 0;
+        frame->native.trap_frame = 0;
         frame->native.trap_tries = 0;
 
         frame->native.exception.next = NULL;
index 4243b95194aa824e1d0a95137d87faa264bd84a8..176d30d3f4506ecf303035307b682af9daf0a6de 100644 (file)
@@ -1986,6 +1986,18 @@ static njs_unit_test_t  njs_test[] =
     { nxt_string("a = []; a.concat([])"),
       nxt_string("") },
 
+    { nxt_string("var s = { toString: function() { return 'S' } }"
+                 "var v = { toString: 8, valueOf: function() { return 'V' } }"
+                 "var o = [9]; o.join = function() { return 'O' };"
+                 "var a = [1,2,3,[4,5,6],s,v,o]; a.join('')"),
+      nxt_string("1234,5,6SVO") },
+
+    { nxt_string("var s = { toString: function() { return 'S' } }"
+                 "var v = { toString: 8, valueOf: function() { return 'V' } }"
+                 "var o = [9]; o.join = function() { return 'O' };"
+                 "var a = [1,2,3,[4,5,6],s,v,o]; a"),
+      nxt_string("1,2,3,4,5,6,S,V,O") },
+
     /* Array.toString(). */
 
     { nxt_string("a = [1,2,3]; a.join = 'NO';"