]> git.kaiwu.me - njs.git/commitdiff
JSON object.
authorDmitry Volyntsev <xeioex@nginx.com>
Tue, 3 Oct 2017 18:24:58 +0000 (21:24 +0300)
committerDmitry Volyntsev <xeioex@nginx.com>
Tue, 3 Oct 2017 18:24:58 +0000 (21:24 +0300)
18 files changed:
Makefile
njs/njs_array.c
njs/njs_array.h
njs/njs_builtin.c
njs/njs_generator.c
njs/njs_json.c [new file with mode: 0644]
njs/njs_json.h [new file with mode: 0644]
njs/njs_lexer_keyword.c
njs/njs_number.c
njs/njs_number.h
njs/njs_object.c
njs/njs_object.h
njs/njs_object_hash.h
njs/njs_parser.c
njs/njs_parser.h
njs/njs_vm.h
njs/test/njs_unit_test.c
nxt/nxt_utf8.h

index 18a8e67c9fb291fe3b24f149af5b64b42da00072..c33f262d366367f19e8672251ceeb6b5f915032f 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -16,6 +16,7 @@ $(NXT_BUILDDIR)/libnjs.a: \
        $(NXT_BUILDDIR)/njs_string.o \
        $(NXT_BUILDDIR)/njs_object.o \
        $(NXT_BUILDDIR)/njs_array.o \
+       $(NXT_BUILDDIR)/njs_json.o \
        $(NXT_BUILDDIR)/njs_function.o \
        $(NXT_BUILDDIR)/njs_regexp.o \
        $(NXT_BUILDDIR)/njs_date.o \
@@ -48,6 +49,7 @@ $(NXT_BUILDDIR)/libnjs.a: \
                $(NXT_BUILDDIR)/njs_string.o \
                $(NXT_BUILDDIR)/njs_object.o \
                $(NXT_BUILDDIR)/njs_array.o \
+               $(NXT_BUILDDIR)/njs_json.o \
                $(NXT_BUILDDIR)/njs_function.o \
                $(NXT_BUILDDIR)/njs_regexp.o \
                $(NXT_BUILDDIR)/njs_date.o \
@@ -211,6 +213,18 @@ $(NXT_BUILDDIR)/njs_array.o: \
                -I$(NXT_LIB) -Injs \
                njs/njs_array.c
 
+$(NXT_BUILDDIR)/njs_json.o: \
+       $(NXT_BUILDDIR)/libnxt.a \
+       njs/njscript.h \
+       njs/njs_vm.h \
+       njs/njs_object.h \
+       njs/njs_json.c \
+
+       $(NXT_CC) -c -o $(NXT_BUILDDIR)/njs_json.o $(NXT_CFLAGS) \
+               -I$(NXT_LIB) -Injs \
+               njs/njs_json.c
+
+
 $(NXT_BUILDDIR)/njs_function.o: \
        $(NXT_BUILDDIR)/libnxt.a \
        njs/njscript.h \
index ef9ac75997cd4a2adbba1e715fa8e7d7f123e01a..8465e2d480188e0fb4b926bd9695314d6e81bf34 100644 (file)
@@ -157,7 +157,7 @@ njs_array_alloc(njs_vm_t *vm, uint32_t length, uint32_t spare)
 }
 
 
-static njs_ret_t
+njs_ret_t
 njs_array_add(njs_vm_t *vm, njs_array_t *array, njs_value_t *value)
 {
     njs_ret_t  ret;
index 320c2d958643b7797d92af6d34d6b945faa512bf..7b6e7c109bebea938269d37e0cab27b392d1e718 100644 (file)
@@ -16,6 +16,7 @@
 
 
 njs_array_t *njs_array_alloc(njs_vm_t *vm, uint32_t length, uint32_t spare);
+njs_ret_t njs_array_add(njs_vm_t *vm, njs_array_t *array, njs_value_t *value);
 njs_ret_t njs_array_string_add(njs_vm_t *vm, njs_array_t *array, u_char *start,
     size_t size, size_t length);
 njs_ret_t njs_array_expand(njs_vm_t *vm, njs_array_t *array, uint32_t prepend,
index 53d699a71fd94044a3a58d2eb0d37f4da10917ac..6aae9e99993ec5bcfe1c6ba4834c65ade78d3f62 100644 (file)
@@ -20,6 +20,7 @@
 #include <njs_string.h>
 #include <njs_object.h>
 #include <njs_array.h>
+#include <njs_json.h>
 #include <njs_function.h>
 #include <njs_variable.h>
 #include <njs_extern.h>
@@ -44,6 +45,7 @@ static nxt_int_t njs_builtin_completions(njs_vm_t *vm, size_t *size,
 const njs_object_init_t    *njs_object_init[] = {
     NULL,                         /* global this        */
     &njs_math_object_init,        /* Math               */
+    &njs_json_object_init,        /* JSON               */
 };
 
 
index db8b5c3508782d47ba369f090b6f93790fcbe470..7219f5674b17baa54bd547d2440d4ce6fa4892e2 100644 (file)
@@ -298,6 +298,7 @@ njs_generator(njs_vm_t *vm, njs_parser_t *parser, njs_parser_node_t *node)
 
     case NJS_TOKEN_GLOBAL_THIS:
     case NJS_TOKEN_MATH:
+    case NJS_TOKEN_JSON:
     case NJS_TOKEN_EVAL:
     case NJS_TOKEN_TO_STRING:
     case NJS_TOKEN_IS_NAN:
diff --git a/njs/njs_json.c b/njs/njs_json.c
new file mode 100644 (file)
index 0000000..9bcc903
--- /dev/null
@@ -0,0 +1,2088 @@
+
+/*
+ * Copyright (C) Dmitry Volyntsev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#include <nxt_auto_config.h>
+#include <nxt_types.h>
+#include <nxt_clang.h>
+#include <nxt_alignment.h>
+#include <nxt_string.h>
+#include <nxt_stub.h>
+#include <nxt_djb_hash.h>
+#include <nxt_array.h>
+#include <nxt_lvlhsh.h>
+#include <nxt_random.h>
+#include <nxt_mem_cache_pool.h>
+#include <njscript.h>
+#include <njs_vm.h>
+#include <njs_string.h>
+#include <njs_number.h>
+#include <njs_object.h>
+#include <njs_object_hash.h>
+#include <njs_array.h>
+#include <njs_function.h>
+#include <stdio.h>
+#include <string.h>
+
+
+typedef struct {
+    njs_vm_t                   *vm;
+    nxt_mem_cache_pool_t       *pool;
+    nxt_uint_t                 depth;
+    u_char                     *start;
+    u_char                     *end;
+} njs_json_parse_ctx_t;
+
+
+typedef struct {
+    njs_value_t                value;
+
+    uint8_t                    written;       /* 1 bit */
+
+    enum {
+       NJS_JSON_OBJECT_START,
+       NJS_JSON_OBJECT_CONTINUE,
+       NJS_JSON_OBJECT_TO_JSON_REPLACED,
+       NJS_JSON_OBJECT_REPLACED,
+       NJS_JSON_ARRAY_START,
+       NJS_JSON_ARRAY_CONTINUE,
+       NJS_JSON_ARRAY_TO_JSON_REPLACED,
+       NJS_JSON_ARRAY_REPLACED
+    }                          type:8;
+
+    uint32_t                   index;
+    njs_array_t                *keys;
+    njs_value_t                *prop_value;
+} njs_json_state_t;
+
+
+typedef struct {
+    union {
+        njs_continuation_t     cont;
+        u_char                 padding[NJS_CONTINUATION_SIZE];
+    } u;
+    /*
+     * This retval value must be aligned so the continuation is padded
+     * to aligned size.
+     */
+    njs_value_t                retval;
+
+    nxt_array_t                stack;
+    njs_json_state_t           *state;
+    njs_function_t             *function;
+} njs_json_parse_t;
+
+
+typedef struct njs_chb_node_s njs_chb_node_t;
+
+struct njs_chb_node_s {
+    njs_chb_node_t             *next;
+    u_char                     *start;
+    u_char                     *pos;
+    u_char                     *end;
+};
+
+
+typedef struct {
+    union {
+        njs_continuation_t     cont;
+        u_char                 padding[NJS_CONTINUATION_SIZE];
+    } u;
+    /*
+     * This retval value must be aligned so the continuation is padded
+     * to aligned size.
+     */
+    njs_value_t                retval;
+    njs_value_t                key;
+
+    njs_vm_t                   *vm;
+    nxt_mem_cache_pool_t       *pool;
+    njs_chb_node_t             *nodes;
+    njs_chb_node_t             *last;
+    nxt_array_t                stack;
+    njs_json_state_t           *state;
+
+    njs_value_t                replacer;
+    nxt_str_t                  space;
+} njs_json_stringify_t;
+
+
+static u_char *njs_json_parse_value(njs_json_parse_ctx_t *ctx,
+    njs_value_t *value, u_char *p);
+static u_char *njs_json_parse_object(njs_json_parse_ctx_t *ctx,
+    njs_value_t *value, u_char *p);
+static u_char *njs_json_parse_array(njs_json_parse_ctx_t *ctx,
+    njs_value_t *value, u_char *p);
+static u_char *njs_json_parse_string(njs_json_parse_ctx_t *ctx,
+    njs_value_t *value, u_char *p);
+static u_char *njs_json_parse_number(njs_json_parse_ctx_t *ctx,
+    njs_value_t *value, u_char *p);
+nxt_inline uint32_t njs_json_unicode(const u_char *p);
+static u_char *njs_json_skip_space(u_char *start, u_char *end);
+
+static njs_ret_t njs_json_parse_continuation(njs_vm_t *vm,
+    njs_value_t *args, nxt_uint_t nargs, njs_index_t unused);
+static njs_ret_t njs_json_parse_continuation_apply(njs_vm_t *vm,
+    njs_json_parse_t *parse);
+static njs_json_state_t *njs_json_push_parse_state(njs_vm_t *vm,
+    njs_json_parse_t *parse, njs_value_t *value);
+static njs_json_state_t *njs_json_pop_parse_state(njs_json_parse_t *parse);
+static void njs_json_parse_exception(njs_json_parse_ctx_t *ctx,
+    const char* msg, u_char *pos);
+
+static njs_ret_t njs_json_stringify_continuation(njs_vm_t *vm,
+    njs_value_t *args, nxt_uint_t nargs, njs_index_t unused);
+static njs_function_t *njs_object_to_json_function(njs_vm_t *vm,
+    njs_value_t *value);
+static njs_ret_t njs_json_stringify_to_json(njs_vm_t *vm,
+    njs_json_stringify_t* stringify, njs_function_t *function,
+    njs_value_t *key, njs_value_t *value);
+static njs_ret_t njs_json_stringify_replacer(njs_vm_t *vm,
+    njs_json_stringify_t* stringify, njs_value_t *key, njs_value_t *value);
+static njs_ret_t njs_json_stringify_array(njs_vm_t *vm,
+    njs_json_stringify_t *stringify);
+static njs_json_state_t *njs_json_push_stringify_state(njs_vm_t *vm,
+    njs_json_stringify_t *stringify, njs_value_t *value);
+static njs_json_state_t *njs_json_pop_stringify_state(
+    njs_json_stringify_t *stringify);
+static void njs_json_stringify_exception(njs_json_stringify_t *stringify,
+    const char* msg);
+
+static nxt_int_t njs_json_append_value(njs_json_stringify_t *stringify,
+    njs_value_t *value);
+static nxt_int_t njs_json_append_string(njs_json_stringify_t *stringify,
+    njs_value_t *value);
+static nxt_int_t njs_json_append_number(njs_json_stringify_t *stringify,
+    njs_value_t *value);
+
+static njs_value_t *njs_json_wrap_value(njs_vm_t *vm, njs_value_t *value);
+
+
+#define NJS_JSON_BUF_MIN_SIZE       128
+
+#define njs_json_buf_written(stringify, bytes)                              \
+    (stringify)->last->pos += (bytes);
+
+#define njs_json_buf_node_size(n) (size_t) ((n)->pos - (n)->start)
+#define njs_json_buf_node_room(n) (size_t) ((n)->end - (n)->pos)
+
+static nxt_int_t njs_json_buf_append(njs_json_stringify_t *stringify,
+    const char* msg, size_t len);
+static u_char *njs_json_buf_reserve(njs_json_stringify_t *stringify,
+    size_t size);
+static nxt_int_t njs_json_buf_pullup(njs_json_stringify_t *stringify,
+    nxt_str_t *str);
+
+
+static njs_ret_t
+njs_json_parse(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs,
+    njs_index_t unused)
+{
+    u_char                *p, *end;
+    njs_value_t           arg, *value, *wrapper;
+    njs_json_parse_t      *parse;
+    njs_string_prop_t     string;
+    njs_json_parse_ctx_t  ctx;
+
+    value = nxt_mem_cache_alloc(vm->mem_cache_pool, sizeof(njs_value_t));
+    if (nxt_slow_path(value == NULL)) {
+        vm->exception = &njs_exception_memory_error;
+        return NXT_ERROR;
+    }
+
+    if (nargs < 2) {
+        arg = njs_string_void;
+    } else {
+        arg = args[1];
+    }
+
+    (void) njs_string_prop(&string, &arg);
+
+    p = string.start;
+    end = p + string.size;
+
+    ctx.vm = vm;
+    ctx.pool = vm->mem_cache_pool;
+    ctx.depth = 32;
+    ctx.start = string.start;
+    ctx.end = end;
+
+    p = njs_json_skip_space(p, end);
+    if (nxt_slow_path(p == end)) {
+        njs_json_parse_exception(&ctx, "Unexpected end of input", p);
+        return NXT_ERROR;
+    }
+
+    p = njs_json_parse_value(&ctx, value, p);
+    if (nxt_slow_path(p == NULL)) {
+        return NXT_ERROR;
+    }
+
+    p = njs_json_skip_space(p, end);
+    if (nxt_slow_path(p != end)) {
+        njs_json_parse_exception(&ctx, "Unexpected token", p);
+        return NXT_ERROR;
+    }
+
+    if (nargs >= 3 && njs_is_function(&args[2]) && njs_is_object(value)) {
+        wrapper = njs_json_wrap_value(vm, value);
+        if (nxt_slow_path(wrapper == NULL)) {
+            goto memory_error;
+        }
+
+        parse = njs_vm_continuation(vm);
+        parse->u.cont.function = njs_json_parse_continuation;
+        parse->function = args[2].data.u.function;
+
+        if (nxt_array_init(&parse->stack, NULL, 4, sizeof(njs_json_state_t),
+                           &njs_array_mem_proto, vm->mem_cache_pool)
+            == NULL)
+        {
+            goto memory_error;
+        }
+
+        if (njs_json_push_parse_state(vm, parse, wrapper) == NULL) {
+            goto memory_error;
+        }
+
+        return njs_json_parse_continuation(vm, args, nargs, unused);
+    }
+
+    vm->retval = *value;
+
+    return NXT_OK;
+
+memory_error:
+
+    vm->exception = &njs_exception_memory_error;
+    return NXT_ERROR;
+}
+
+
+static njs_ret_t
+njs_json_stringify(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs,
+    njs_index_t unused)
+{
+    double                num;
+    nxt_int_t             i;
+    njs_ret_t             ret;
+    njs_value_t           *wrapper;
+    njs_json_stringify_t  *stringify;
+
+    if (nargs < 2) {
+        vm->retval = njs_value_void;
+        return NXT_OK;
+    }
+
+    stringify = njs_vm_continuation(vm);
+    stringify->vm = vm;
+    stringify->pool = vm->mem_cache_pool;
+    stringify->u.cont.function = njs_json_stringify_continuation;
+    stringify->nodes = NULL;
+    stringify->last = NULL;
+
+    if (nargs >= 3 && (njs_is_function(&args[2]) || njs_is_array(&args[2]))) {
+        stringify->replacer = args[2];
+        if (njs_is_array(&args[2])) {
+            ret = njs_json_stringify_array(vm, stringify);
+            if (nxt_slow_path(ret != NXT_OK)) {
+                goto memory_error;
+            }
+        }
+
+    } else {
+        stringify->replacer = njs_value_void;
+    }
+
+    stringify->space.length = 0;
+
+    if (nargs >= 4 && (njs_is_string(&args[3]) || njs_is_number(&args[3]))) {
+        if (njs_is_string(&args[3])) {
+            njs_string_get(&args[3], &stringify->space);
+            stringify->space.length = nxt_min(stringify->space.length, 10);
+
+        } else {
+            num = args[3].data.u.number;
+            if (!isnan(num) && !isinf(num) && num > 0) {
+                num = nxt_min(num, 10);
+
+                stringify->space.length = (size_t) num;
+                stringify->space.start = nxt_mem_cache_alloc(vm->mem_cache_pool,
+                                                             (size_t) num + 1);
+                if (nxt_slow_path(stringify->space.start == NULL)) {
+                    goto memory_error;
+                }
+
+                for (i = 0; i < (int) num; i++) {
+                    stringify->space.start[i] = ' ';
+                }
+            }
+        }
+    }
+
+    if (nxt_array_init(&stringify->stack, NULL, 4, sizeof(njs_json_state_t),
+                       &njs_array_mem_proto, vm->mem_cache_pool)
+        == NULL)
+    {
+        goto memory_error;
+    }
+
+    wrapper = njs_json_wrap_value(vm, &args[1]);
+    if (nxt_slow_path(wrapper == NULL)) {
+        goto memory_error;
+    }
+
+    if (njs_json_push_stringify_state(vm, stringify, wrapper) == NULL) {
+        goto memory_error;
+    }
+
+    return njs_json_stringify_continuation(vm, args, nargs, unused);
+
+memory_error:
+
+    vm->exception = &njs_exception_memory_error;
+    return NXT_ERROR;
+}
+
+
+static u_char *
+njs_json_parse_value(njs_json_parse_ctx_t *ctx, njs_value_t *value, u_char *p)
+{
+    switch (*p) {
+    case '{':
+        return njs_json_parse_object(ctx, value, p);
+
+    case '[':
+        return njs_json_parse_array(ctx, value, p);
+
+    case '"':
+        return njs_json_parse_string(ctx, value, p);
+
+    case 't':
+        if (nxt_fast_path(ctx->end - p >= 4 && memcmp(p, "true", 4) == 0)) {
+            *value = njs_value_true;
+
+            return p + 4;
+        }
+
+        goto error;
+
+    case 'f':
+        if (nxt_fast_path(ctx->end - p >= 5 && memcmp(p, "false", 5) == 0)) {
+            *value = njs_value_false;
+
+            return p + 5;
+        }
+
+        goto error;
+
+    case 'n':
+        if (nxt_fast_path(ctx->end - p >= 4 && memcmp(p, "null", 4) == 0)) {
+            *value = njs_value_null;
+
+            return p + 4;
+        }
+
+        goto error;
+    }
+
+    if (nxt_fast_path(*p == '-' || (*p - '0') <= 9)) {
+        return njs_json_parse_number(ctx, value, p);
+    }
+
+error:
+
+    njs_json_parse_exception(ctx, "Unexpected token", p);
+
+    return NULL;
+}
+
+
+static u_char *
+njs_json_parse_object(njs_json_parse_ctx_t *ctx, njs_value_t *value, u_char *p)
+{
+    nxt_int_t           ret;
+    njs_object_t        *object;
+    njs_value_t         *prop_name, *prop_value;
+    njs_object_prop_t   *prop;
+    nxt_lvlhsh_query_t  lhq;
+
+    if (nxt_slow_path(--ctx->depth == 0)) {
+        njs_json_parse_exception(ctx, "Nested too deep", p);
+        return NULL;
+    }
+
+    object = njs_object_alloc(ctx->vm);
+    if (nxt_slow_path(object == NULL)) {
+        goto memory_error;
+    }
+
+    prop = NULL;
+
+    for ( ;; ) {
+        p = njs_json_skip_space(p + 1, ctx->end);
+        if (nxt_slow_path(p == ctx->end)) {
+            goto error_end;
+        }
+
+        if (*p != '"') {
+            if (nxt_fast_path(*p == '}')) {
+                if (nxt_slow_path(prop != NULL)) {
+                    njs_json_parse_exception(ctx, "Trailing comma", p - 1);
+                    return NULL;
+                }
+
+                break;
+            }
+
+            goto error_token;
+        }
+
+        prop_name = nxt_mem_cache_alloc(ctx->pool, sizeof(njs_value_t));
+        if (nxt_slow_path(prop_name == NULL)) {
+            goto memory_error;
+        }
+
+        p = njs_json_parse_string(ctx, prop_name, p);
+        if (nxt_slow_path(p == NULL)) {
+            /* The exception is set by the called function. */
+            return NULL;
+        }
+
+        p = njs_json_skip_space(p, ctx->end);
+        if (nxt_slow_path(p == ctx->end || *p != ':')) {
+            goto error_token;
+        }
+
+        p = njs_json_skip_space(p + 1, ctx->end);
+        if (nxt_slow_path(p == ctx->end)) {
+            goto error_end;
+        }
+
+        prop_value = nxt_mem_cache_alloc(ctx->pool, sizeof(njs_value_t));
+        if (nxt_slow_path(prop_value == NULL)) {
+            goto memory_error;
+        }
+
+        p = njs_json_parse_value(ctx, prop_value, p);
+        if (nxt_slow_path(p == NULL)) {
+            /* The exception is set by the called function. */
+            return NULL;
+        }
+
+        prop = njs_object_prop_alloc(ctx->vm, prop_name, prop_value, 1);
+        if (nxt_slow_path(prop == NULL)) {
+            goto memory_error;
+        }
+
+        njs_string_get(prop_name, &lhq.key);
+        lhq.key_hash = nxt_djb_hash(lhq.key.start, lhq.key.length);
+        lhq.value = prop;
+        lhq.replace = 1;
+        lhq.pool = ctx->pool;
+        lhq.proto = &njs_object_hash_proto;
+
+        ret = nxt_lvlhsh_insert(&object->hash, &lhq);
+        if (nxt_slow_path(ret != NXT_OK)) {
+            ctx->vm->exception = &njs_exception_internal_error;
+            return NULL;
+        }
+
+        p = njs_json_skip_space(p, ctx->end);
+        if (nxt_slow_path(p == ctx->end)) {
+            goto error_end;
+        }
+
+        if (*p != ',') {
+            if (nxt_fast_path(*p == '}')) {
+                break;
+            }
+
+            goto error_token;
+        }
+    }
+
+    value->data.u.object = object;
+    value->type = NJS_OBJECT;
+    value->data.truth = 1;
+
+    ctx->depth++;
+
+    return p + 1;
+
+error_token:
+
+    njs_json_parse_exception(ctx, "Unexpected token", p);
+
+    return NULL;
+
+error_end:
+
+    njs_json_parse_exception(ctx, "Unexpected end of input", p);
+
+    return NULL;
+
+memory_error:
+
+    ctx->vm->exception = &njs_exception_memory_error;
+
+    return NULL;
+}
+
+
+static u_char *
+njs_json_parse_array(njs_json_parse_ctx_t *ctx, njs_value_t *value, u_char *p)
+{
+    nxt_int_t    ret;
+    njs_array_t  *array;
+    njs_value_t  *element;
+
+    if (nxt_slow_path(--ctx->depth == 0)) {
+        njs_json_parse_exception(ctx, "Nested too deep", p);
+        return NULL;
+    }
+
+    array = njs_array_alloc(ctx->vm, 0, 0);
+    if (nxt_slow_path(array == NULL)) {
+        ctx->vm->exception = &njs_exception_memory_error;
+        return NULL;
+    }
+
+    element = NULL;
+
+    for ( ;; ) {
+        p = njs_json_skip_space(p + 1, ctx->end);
+        if (nxt_slow_path(p == ctx->end)) {
+            goto error_end;
+        }
+
+        if (*p == ']') {
+            if (nxt_slow_path(element != NULL)) {
+                njs_json_parse_exception(ctx, "Trailing comma", p - 1);
+                return NULL;
+            }
+
+            break;
+        }
+
+        element = nxt_mem_cache_alloc(ctx->pool, sizeof(njs_value_t));
+        if (nxt_slow_path(element == NULL)) {
+            ctx->vm->exception = &njs_exception_memory_error;
+            return NULL;
+        }
+
+        p = njs_json_parse_value(ctx, element, p);
+        if (nxt_slow_path(p == NULL)) {
+            return NULL;
+        }
+
+        ret = njs_array_add(ctx->vm, array, element);
+        if (nxt_slow_path(ret != NXT_OK)) {
+            ctx->vm->exception = &njs_exception_internal_error;
+            return NULL;
+        }
+
+        p = njs_json_skip_space(p, ctx->end);
+        if (nxt_slow_path(p == ctx->end)) {
+            goto error_end;
+        }
+
+        if (*p != ',') {
+            if (nxt_fast_path(*p == ']')) {
+                break;
+            }
+
+            goto error_token;
+        }
+    }
+
+    value->data.u.array = array;
+    value->type = NJS_ARRAY;
+    value->data.truth = 1;
+
+    ctx->depth++;
+
+    return p + 1;
+
+error_token:
+
+    njs_json_parse_exception(ctx, "Unexpected token", p);
+
+    return NULL;
+
+error_end:
+
+    njs_json_parse_exception(ctx, "Unexpected end of input", p);
+
+    return NULL;
+}
+
+
+static u_char *
+njs_json_parse_string(njs_json_parse_ctx_t *ctx, njs_value_t *value, u_char *p)
+{
+    u_char      *s, ch, *last, *start;
+    size_t      size, surplus;
+    ssize_t     length;
+    uint32_t    utf, utf_low;
+    njs_ret_t   ret;
+
+    enum {
+        sw_usual = 0,
+        sw_escape,
+        sw_encoded1,
+        sw_encoded2,
+        sw_encoded3,
+        sw_encoded4,
+    } state;
+
+    start = p + 1;
+
+    state = 0;
+    surplus = 0;
+
+    for (p = start; p < ctx->end; p++) {
+        ch = *p;
+
+        switch (state) {
+
+        case sw_usual:
+
+            if (ch == '"') {
+                break;
+            }
+
+            if (ch == '\\') {
+                state = sw_escape;
+                continue;
+            }
+
+            if (nxt_fast_path(ch >= ' ')) {
+                continue;
+            }
+
+            njs_json_parse_exception(ctx, "Forbidden source char", p);
+
+            return NULL;
+
+        case sw_escape:
+
+            switch (ch) {
+            case '"':
+            case '\\':
+            case '/':
+            case 'n':
+            case 'r':
+            case 't':
+            case 'b':
+            case 'f':
+                surplus++;
+                state = sw_usual;
+                continue;
+
+            case 'u':
+                /*
+                 * Basic unicode 6 bytes "\uXXXX" in JSON
+                 * and up to 3 bytes in UTF-8.
+                 *
+                 * Surrogate pair: 12 bytes "\uXXXX\uXXXX" in JSON
+                 * and 3 or 4 bytes in UTF-8.
+                 */
+                surplus += 3;
+                state = sw_encoded1;
+                continue;
+            }
+
+            njs_json_parse_exception(ctx, "Unknown escape char", p);
+
+            return NULL;
+
+        case sw_encoded1:
+        case sw_encoded2:
+        case sw_encoded3:
+        case sw_encoded4:
+
+            if (nxt_fast_path((ch >= '0' && ch <= '9')
+                              || (ch >= 'A' && ch <= 'F')
+                              || (ch >= 'a' && ch <= 'f')))
+            {
+                state = (state == sw_encoded4) ? sw_usual : state + 1;
+                continue;
+            }
+
+            njs_json_parse_exception(ctx, "Invalid Unicode escape sequence", p);
+
+            return NULL;
+        }
+
+        break;
+    }
+
+    if (nxt_slow_path(p == ctx->end)) {
+        njs_json_parse_exception(ctx, "Unexpected end of input", p);
+        return NULL;
+    }
+
+    /* Points to the ending quote mark. */
+    last = p;
+
+    size = last - start - surplus;
+
+    if (surplus != 0) {
+        p = start;
+
+        start = nxt_mem_cache_alloc(ctx->pool, size);
+        if (nxt_slow_path(start == NULL)) {
+            ctx->vm->exception = &njs_exception_memory_error;
+            return NULL;
+        }
+
+        s = start;
+
+        do {
+            ch = *p++;
+
+            if (ch != '\\') {
+                *s++ = ch;
+                continue;
+            }
+
+            ch = *p++;
+
+            switch (ch) {
+            case '"':
+            case '\\':
+            case '/':
+                *s++ = ch;
+                continue;
+
+            case 'n':
+                *s++ = '\n';
+                continue;
+
+            case 'r':
+                *s++ = '\r';
+                continue;
+
+            case 't':
+                *s++ = '\t';
+                continue;
+
+            case 'b':
+                *s++ = '\b';
+                continue;
+
+            case 'f':
+                *s++ = '\f';
+                continue;
+            }
+
+            /* "\uXXXX": Unicode escape sequence. */
+
+            utf = njs_json_unicode(p);
+            p += 4;
+
+            if (utf >= 0xd800 && utf <= 0xdfff) {
+
+                /* Surrogate pair. */
+
+                if (utf > 0xdbff || p[0] != '\\' || p[1] != 'u') {
+                    njs_json_parse_exception(ctx, "Invalid Unicode char", p);
+                    return NULL;
+                }
+
+                p += 2;
+
+                utf_low = njs_json_unicode(p);
+                p += 4;
+
+                if (nxt_slow_path(utf_low < 0xdc00 || utf_low > 0xdfff)) {
+                    njs_json_parse_exception(ctx, "Invalid surrogate pair", p);
+                    return NULL;
+                }
+
+                utf = 0x10000 + ((utf - 0xd800) << 10) + (utf_low - 0xdc00);
+            }
+
+            s = nxt_utf8_encode(s, utf);
+
+        } while (p != last);
+
+        size = s - start;
+    }
+
+    length = nxt_utf8_length(start, size);
+    if (nxt_slow_path(length < 0)) {
+        length = 0;
+    }
+
+    ret = njs_string_create(ctx->vm, value, start, size, length);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        ctx->vm->exception = &njs_exception_memory_error;
+        return NULL;
+    }
+
+    return last + 1;
+}
+
+
+static u_char *
+njs_json_parse_number(njs_json_parse_ctx_t *ctx, njs_value_t *value, u_char *p)
+{
+    u_char     *start;
+    double     num;
+    nxt_int_t  sign;
+
+    sign = 1;
+
+    if (*p == '-') {
+        if (p + 1 == ctx->end) {
+            goto error;
+        }
+
+        p++;
+        sign = -1;
+    }
+
+    start = p;
+    num = njs_number_dec_parse(&p, ctx->end);
+    if (p != start) {
+        *value = njs_value_zero;
+        value->data.u.number = sign * num;
+
+        return p;
+    }
+
+error:
+
+    njs_json_parse_exception(ctx, "Unexpected number", p);
+
+    return NULL;
+}
+
+
+nxt_inline uint32_t
+njs_json_unicode(const u_char *p)
+{
+    u_char      c;
+    uint32_t    utf;
+    nxt_uint_t  i;
+
+    utf = 0;
+
+    for (i = 0; i < 4; i++) {
+        utf <<= 4;
+        c = p[i] | 0x20;
+        c -= '0';
+        if (c > 9) {
+            c += '0' - 'a' + 10;
+        }
+
+        utf |= c;
+    }
+
+    return utf;
+}
+
+
+static u_char *
+njs_json_skip_space(u_char *start, u_char *end)
+{
+    u_char  *p;
+
+    for (p = start; nxt_fast_path(p != end); p++) {
+
+        switch (*p) {
+        case ' ':
+        case '\t':
+        case '\r':
+        case '\n':
+            continue;
+        }
+
+        break;
+    }
+
+    return p;
+}
+
+
+#define njs_json_is_non_empty(_value)                                         \
+    (((_value)->type == NJS_OBJECT)                                           \
+      && !nxt_lvlhsh_is_empty(&(_value)->data.u.object->hash))                \
+     || (((_value)->type == NJS_ARRAY) && (_value)->data.u.array->length != 0)
+
+
+static njs_ret_t
+njs_json_parse_continuation(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs,
+    njs_index_t unused)
+{
+    nxt_int_t           ret;
+    njs_value_t         *key, *value;
+    njs_json_state_t    *state;
+    njs_json_parse_t    *parse;
+    njs_object_prop_t   *prop;
+    nxt_lvlhsh_query_t  lhq;
+
+    parse = njs_vm_continuation(vm);
+    state = parse->state;
+
+    for ( ;; ) {
+        switch (state->type) {
+        case NJS_JSON_OBJECT_START:
+            if (state->index < state->keys->length) {
+                key = &state->keys->start[state->index];
+                njs_string_get(key, &lhq.key);
+                lhq.key_hash = nxt_djb_hash(lhq.key.start, lhq.key.length);
+                lhq.proto = &njs_object_hash_proto;
+
+                ret = nxt_lvlhsh_find(&state->value.data.u.object->hash, &lhq);
+                if (nxt_slow_path(ret == NXT_DECLINED)) {
+                    state->index++;
+                    break;
+                }
+
+                prop = lhq.value;
+                state->prop_value = &prop->value;
+
+                if (njs_json_is_non_empty(&prop->value)) {
+                    state = njs_json_push_parse_state(vm, parse, &prop->value);
+                    if (state == NULL) {
+                        goto memory_error;
+                    }
+
+                    break;
+                }
+
+            } else {
+                state = njs_json_pop_parse_state(parse);
+                if (state == NULL) {
+                    vm->retval = parse->retval;
+
+                    return NXT_OK;
+                }
+            }
+
+            return njs_json_parse_continuation_apply(vm, parse);
+
+        case NJS_JSON_OBJECT_REPLACED:
+            key = &state->keys->start[state->index];
+            njs_string_get(key, &lhq.key);
+            lhq.key_hash = nxt_djb_hash(lhq.key.start, lhq.key.length);
+            lhq.replace = 1;
+            lhq.proto = &njs_object_hash_proto;
+            lhq.pool = vm->mem_cache_pool;
+
+            if (njs_is_void(&parse->retval)) {
+                ret = nxt_lvlhsh_delete(&state->value.data.u.object->hash,
+                                        &lhq);
+
+            } else {
+                prop = njs_object_prop_alloc(vm, key, &parse->retval, 1);
+                if (nxt_slow_path(prop == NULL)) {
+                    goto memory_error;
+                }
+
+                lhq.value = prop;
+                ret = nxt_lvlhsh_insert(&state->value.data.u.object->hash,
+                                        &lhq);
+            }
+
+            if (nxt_slow_path(ret != NXT_OK)) {
+                vm->exception = &njs_exception_internal_error;
+                return NXT_ERROR;
+            }
+
+            state->index++;
+            state->type = NJS_JSON_OBJECT_START;
+
+            break;
+
+        case NJS_JSON_ARRAY_START:
+            if (state->index < state->value.data.u.array->length) {
+                value = &state->value.data.u.array->start[state->index];
+
+                if (njs_json_is_non_empty(value)) {
+                    state = njs_json_push_parse_state(vm, parse, value);
+                    if (state == NULL) {
+                        goto memory_error;
+                    }
+
+                    break;
+                }
+
+            } else {
+                state = njs_json_pop_parse_state(parse);
+            }
+
+            return njs_json_parse_continuation_apply(vm, parse);
+
+        case NJS_JSON_ARRAY_REPLACED:
+            value = &state->value.data.u.array->start[state->index];
+            *value = parse->retval;
+
+            state->index++;
+            state->type = NJS_JSON_ARRAY_START;
+
+            break;
+
+        default:
+            vm->exception = &njs_exception_internal_error;
+            return NXT_ERROR;
+        }
+    }
+
+memory_error:
+
+    vm->exception = &njs_exception_memory_error;
+
+    return NXT_ERROR;
+}
+
+
+static njs_ret_t
+njs_json_parse_continuation_apply(njs_vm_t *vm, njs_json_parse_t *parse)
+{
+    size_t            size;
+    njs_value_t       arguments[3];
+    njs_json_state_t  *state;
+
+    state = parse->state;
+
+    arguments[0] = state->value;
+
+    switch (state->type) {
+    case NJS_JSON_OBJECT_START:
+        arguments[1] = state->keys->start[state->index];
+        arguments[2] = *state->prop_value;
+
+        state->type = NJS_JSON_OBJECT_REPLACED;
+        break;
+
+    case NJS_JSON_ARRAY_START:
+        size = snprintf((char *) njs_string_short_start(&arguments[1]),
+                        NJS_STRING_SHORT, "%u", state->index);
+        njs_string_short_set(&arguments[1], size, size);
+        arguments[2] = state->value.data.u.array->start[state->index];
+
+        state->type = NJS_JSON_ARRAY_REPLACED;
+        break;
+
+    default:
+        vm->exception = &njs_exception_internal_error;
+        return NXT_ERROR;
+    }
+
+    njs_set_invalid(&parse->retval);
+
+    return njs_function_apply(vm, parse->function, arguments, 3,
+                              (njs_index_t) &parse->retval);
+}
+
+
+static njs_json_state_t *
+njs_json_push_parse_state(njs_vm_t *vm, njs_json_parse_t *parse,
+    njs_value_t *value)
+{
+    njs_json_state_t  *state;
+
+    state = nxt_array_add(&parse->stack, &njs_array_mem_proto,
+                           vm->mem_cache_pool);
+    if (state != NULL) {
+        state = nxt_array_last(&parse->stack);
+        state->value = *value;
+        state->index = 0;
+
+        if (njs_is_array(value)) {
+            state->type = NJS_JSON_ARRAY_START;
+
+        } else {
+            state->type = NJS_JSON_OBJECT_START;
+            state->prop_value = NULL;
+            state->keys = njs_object_keys_array(vm, value);
+            if (state->keys == NULL) {
+                return NULL;
+            }
+        }
+    }
+
+    parse->state = state;
+
+    return state;
+}
+
+
+static njs_json_state_t *
+njs_json_pop_parse_state(njs_json_parse_t *parse)
+{
+    if (parse->stack.items > 1) {
+        parse->stack.items--;
+        parse->state = nxt_array_last(&parse->stack);
+        return parse->state;
+    }
+
+    return NULL;
+}
+
+
+static void
+njs_json_parse_exception(njs_json_parse_ctx_t *ctx, const char* msg,
+    u_char *pos)
+{
+    size_t       size;
+    ssize_t      length;
+    njs_ret_t    ret;
+    njs_value_t  *exception;
+
+    static u_char  buf[256];
+
+    exception = nxt_mem_cache_alloc(ctx->pool, sizeof(njs_value_t));
+    if (nxt_slow_path(exception == NULL)) {
+        ctx->vm->exception = &njs_exception_memory_error;
+        return;
+    }
+
+    length = nxt_utf8_length(ctx->start, pos - ctx->start);
+    if (nxt_slow_path(length < 0)) {
+        length = 0;
+    }
+
+    size = snprintf((char *) buf, sizeof(buf),
+                    "SyntaxError: %s at position %zu", msg, length);
+
+    ret = njs_string_new(ctx->vm, exception, buf, size, size);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        ctx->vm->exception = &njs_exception_memory_error;
+        return;
+    }
+
+    ctx->vm->exception = exception;
+}
+
+
+#define njs_is_object_or_array(value)                                         \
+    (((value)->type == NJS_OBJECT) || ((value)->type == NJS_ARRAY))
+
+
+#define njs_json_stringify_append(str, len)                                   \
+    ret = njs_json_buf_append(stringify, str, len);                           \
+    if (ret != NXT_OK) {                                                      \
+        goto memory_error;                                                    \
+    }
+
+
+#define njs_json_stringify_indent(times)                                      \
+    if (stringify->space.length != 0) {                                       \
+        njs_json_stringify_append("\n", 1);                                   \
+        for (i = 0; i < (nxt_int_t) (times) - 1; i++) {                       \
+            njs_json_stringify_append((char *) stringify->space.start,        \
+                                      stringify->space.length);               \
+        }                                                                     \
+    }
+
+
+#define njs_json_stringify_append_key(key)                                    \
+    if (state->written) {                                                     \
+        njs_json_stringify_append(",", 1);                                    \
+        njs_json_stringify_indent(stringify->stack.items);                    \
+    }                                                                         \
+                                                                              \
+    state->written = 1;                                                       \
+    njs_json_append_string(stringify, key);                                   \
+    njs_json_stringify_append(":", 1);                                        \
+    if (stringify->space.length != 0) {                                       \
+        njs_json_stringify_append(" ", 1);                                    \
+    }
+
+
+#define njs_json_stringify_append_value(value)                                \
+    state->written = 1;                                                       \
+    ret = njs_json_append_value(stringify, value);                            \
+    if (nxt_slow_path(ret != NXT_OK)) {                                       \
+        goto memory_error;                                                    \
+    }
+
+
+static njs_ret_t
+njs_json_stringify_continuation(njs_vm_t *vm, njs_value_t *args,
+    nxt_uint_t nargs, njs_index_t unused)
+{
+    ssize_t               length;
+    nxt_int_t             i;
+    njs_ret_t             ret;
+    nxt_str_t             str;
+    njs_value_t           *key, *value;
+    njs_function_t        *to_json;
+    njs_json_state_t      *state;
+    njs_object_prop_t     *prop;
+    nxt_lvlhsh_query_t    lhq;
+    njs_json_stringify_t  *stringify;
+
+    stringify = njs_vm_continuation(vm);
+    state = stringify->state;
+
+    for ( ;; ) {
+        switch (state->type) {
+        case NJS_JSON_OBJECT_START:
+            njs_json_stringify_append("{", 1);
+            njs_json_stringify_indent(stringify->stack.items);
+            state->type = NJS_JSON_OBJECT_CONTINUE;
+
+            /* Fall through. */
+
+        case NJS_JSON_OBJECT_CONTINUE:
+            if (state->index >= state->keys->length) {
+                njs_json_stringify_indent(stringify->stack.items - 1);
+                njs_json_stringify_append("}", 1);
+
+                state = njs_json_pop_stringify_state(stringify);
+                if (state == NULL) {
+                    goto done;
+                }
+
+                break;
+            }
+
+            key = &state->keys->start[state->index++];
+            njs_string_get(key, &lhq.key);
+            lhq.key_hash = nxt_djb_hash(lhq.key.start, lhq.key.length);
+            lhq.proto = &njs_object_hash_proto;
+
+            ret = nxt_lvlhsh_find(&state->value.data.u.object->hash, &lhq);
+            if (nxt_slow_path(ret == NXT_DECLINED)) {
+                break;
+            }
+
+            prop = lhq.value;
+
+            if (!prop->enumerable
+                || njs_is_void(&prop->value)
+                || njs_is_function(&prop->value))
+            {
+                break;
+            }
+
+            if (njs_is_object(&prop->value)) {
+                to_json = njs_object_to_json_function(vm, &prop->value);
+                if (to_json != NULL) {
+                    return njs_json_stringify_to_json(vm, stringify, to_json,
+                                                      &prop->name,
+                                                      &prop->value);
+                }
+            }
+
+            if (njs_is_function(&stringify->replacer)) {
+                return njs_json_stringify_replacer(vm, stringify, &prop->name,
+                                                   &prop->value);
+            }
+
+            njs_json_stringify_append_key(&prop->name);
+
+            if (njs_is_object_or_array(&prop->value)) {
+                state = njs_json_push_stringify_state(vm, stringify,
+                                                      &prop->value);
+                if (state == NULL) {
+                    return NXT_ERROR;
+                }
+
+                break;
+            }
+
+            njs_json_stringify_append_value(&prop->value);
+
+            break;
+
+        case NJS_JSON_OBJECT_TO_JSON_REPLACED:
+            if (njs_is_void(&stringify->retval)) {
+                state->type = NJS_JSON_OBJECT_CONTINUE;
+                break;
+            }
+
+            if (njs_is_function(&stringify->replacer)) {
+                return njs_json_stringify_replacer(vm, stringify,
+                                                   &stringify->key,
+                                                   &stringify->retval);
+            }
+
+            /* Fall through. */
+
+        case NJS_JSON_OBJECT_REPLACED:
+            state->type = NJS_JSON_OBJECT_CONTINUE;
+
+            if (njs_is_void(&stringify->retval)) {
+                break;
+            }
+
+            njs_json_stringify_append_key(&stringify->key);
+
+            value = &stringify->retval;
+            if (njs_is_object(value)) {
+                state = njs_json_push_stringify_state(vm, stringify, value);
+                if (state == NULL) {
+                    return NXT_ERROR;
+                }
+
+                break;
+            }
+
+            njs_json_stringify_append_value(value);
+
+            break;
+
+        case NJS_JSON_ARRAY_START:
+            njs_json_stringify_append("[", 1);
+            njs_json_stringify_indent(stringify->stack.items);
+            state->type = NJS_JSON_ARRAY_CONTINUE;
+
+            /* Fall through. */
+
+        case NJS_JSON_ARRAY_CONTINUE:
+            if (state->index >= state->value.data.u.array->length) {
+                njs_json_stringify_indent(stringify->stack.items - 1);
+                njs_json_stringify_append("]", 1);
+
+                state = njs_json_pop_stringify_state(stringify);
+                if (state == NULL) {
+                    goto done;
+                }
+
+                break;
+            }
+
+            if (state->written) {
+                njs_json_stringify_append(",", 1);
+                njs_json_stringify_indent(stringify->stack.items);
+            }
+
+            value = &state->value.data.u.array->start[state->index++];
+
+            if (njs_is_object(value)) {
+                to_json = njs_object_to_json_function(vm, value);
+                if (to_json != NULL) {
+                    return njs_json_stringify_to_json(vm, stringify, to_json,
+                                                      NULL, value);
+                }
+
+            }
+
+            if (njs_is_function(&stringify->replacer)) {
+                return njs_json_stringify_replacer(vm, stringify, NULL, value);
+            }
+
+            if (njs_is_object_or_array(value)) {
+                state = njs_json_push_stringify_state(vm, stringify, value);
+                if (state == NULL) {
+                    return NXT_ERROR;
+                }
+
+                break;
+            }
+
+            njs_json_stringify_append_value(value);
+
+            break;
+
+        case NJS_JSON_ARRAY_TO_JSON_REPLACED:
+            if (!njs_is_void(&stringify->retval)
+                && njs_is_function(&stringify->replacer))
+            {
+                return njs_json_stringify_replacer(vm, stringify, NULL,
+                                                   &stringify->retval);
+            }
+
+            /* Fall through. */
+
+        case NJS_JSON_ARRAY_REPLACED:
+            state->type = NJS_JSON_ARRAY_CONTINUE;
+
+            if (njs_is_object_or_array(&stringify->retval)) {
+                state = njs_json_push_stringify_state(vm, stringify,
+                                                      &stringify->retval);
+                if (state == NULL) {
+                    return NXT_ERROR;
+                }
+
+                break;
+            }
+
+            njs_json_stringify_append_value(&stringify->retval);
+
+            break;
+        }
+    }
+
+done:
+
+    ret = njs_json_buf_pullup(stringify, &str);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        goto memory_error;
+    }
+
+    /*
+     * The value to stringify is wrapped as '{"": value}'.
+     * An empty object means empty result.
+     */
+    if (str.length <= sizeof("{\n\n}") - 1) {
+        vm->retval = njs_value_void;
+        return NXT_OK;
+    }
+
+    /* Stripping the wrapper's data. */
+
+    str.start += sizeof("{\"\":") - 1;
+    str.length -= sizeof("{\"\":}") - 1;
+
+    if (stringify->space.length != 0) {
+        str.start += sizeof("\n ") - 1;
+        str.length -= sizeof("\n \n") - 1;
+    }
+
+    length = nxt_utf8_length(str.start, str.length);
+    if (nxt_slow_path(length < 0)) {
+        length = 0;
+    }
+
+    return njs_string_create(vm, &vm->retval, str.start, str.length, length);
+
+memory_error:
+
+    vm->exception = &njs_exception_memory_error;
+    return NXT_ERROR;
+}
+
+
+static njs_function_t *
+njs_object_to_json_function(njs_vm_t *vm, njs_value_t *value)
+{
+    njs_object_prop_t   *prop;
+    nxt_lvlhsh_query_t  lhq;
+
+    lhq.key_hash = NJS_TO_JSON_HASH;
+    lhq.key = nxt_string_value("toJSON");
+
+    prop = njs_object_property(vm, value->data.u.object, &lhq);
+
+    if (prop != NULL && njs_is_function(&prop->value)) {
+        return prop->value.data.u.function;
+    }
+
+    return NULL;
+}
+
+
+static njs_ret_t
+njs_json_stringify_to_json(njs_vm_t *vm, njs_json_stringify_t* stringify,
+    njs_function_t *function, njs_value_t *key, njs_value_t *value)
+{
+    size_t            size;
+    njs_value_t       arguments[2];
+    njs_json_state_t  *state;
+
+    njs_set_invalid(&stringify->retval);
+
+    state = stringify->state;
+
+    arguments[0] = *value;
+
+    switch (state->type) {
+    case NJS_JSON_OBJECT_START:
+    case NJS_JSON_OBJECT_CONTINUE:
+        if (key != NULL) {
+            arguments[1] = *key;
+            stringify->key = *key;
+
+        } else {
+            njs_string_short_set(&arguments[1], 0, 0);
+            njs_string_short_set(&stringify->key, 0, 0);
+        }
+
+        state->type = NJS_JSON_OBJECT_TO_JSON_REPLACED;
+        break;
+
+    case NJS_JSON_ARRAY_START:
+    case NJS_JSON_ARRAY_CONTINUE:
+        size = snprintf((char *) njs_string_short_start(&arguments[1]),
+                        NJS_STRING_SHORT, "%u", state->index - 1);
+        njs_string_short_set(&arguments[1], size, size);
+
+        state->type = NJS_JSON_ARRAY_TO_JSON_REPLACED;
+        break;
+
+    default:
+        vm->exception = &njs_exception_internal_error;
+        return NXT_ERROR;
+    }
+
+    return njs_function_apply(vm, function, arguments, 2,
+                              (njs_index_t) &stringify->retval);
+}
+
+
+static njs_ret_t
+njs_json_stringify_replacer(njs_vm_t *vm, njs_json_stringify_t* stringify,
+    njs_value_t *key, njs_value_t *value)
+{
+    size_t            size;
+    njs_value_t       arguments[3];
+    njs_json_state_t  *state;
+
+    state = stringify->state;
+
+    arguments[0] = state->value;
+
+    switch (state->type) {
+    case NJS_JSON_OBJECT_START:
+    case NJS_JSON_OBJECT_CONTINUE:
+    case NJS_JSON_OBJECT_TO_JSON_REPLACED:
+        arguments[1] = *key;
+        stringify->key = *key;
+        arguments[2] = *value;
+
+        state->type = NJS_JSON_OBJECT_REPLACED;
+        break;
+
+    case NJS_JSON_ARRAY_START:
+    case NJS_JSON_ARRAY_CONTINUE:
+    case NJS_JSON_ARRAY_TO_JSON_REPLACED:
+        size = snprintf((char *) njs_string_short_start(&arguments[1]),
+                        NJS_STRING_SHORT, "%u", state->index - 1);
+        njs_string_short_set(&arguments[1], size, size);
+        arguments[2] = *value;
+
+        state->type = NJS_JSON_ARRAY_REPLACED;
+        break;
+
+    default:
+        vm->exception = &njs_exception_internal_error;
+        return NXT_ERROR;
+    }
+
+    njs_set_invalid(&stringify->retval);
+
+    return njs_function_apply(vm, stringify->replacer.data.u.function,
+                              arguments, 3, (njs_index_t) &stringify->retval);
+}
+
+
+static njs_ret_t
+njs_json_stringify_array(njs_vm_t *vm, njs_json_stringify_t  *stringify)
+{
+    njs_ret_t    ret;
+    uint32_t     i, n, k, properties_length, array_length;
+    njs_value_t  *value, num_value;
+    njs_array_t  *properties, *array;
+
+    properties_length = 1;
+    array = stringify->replacer.data.u.array;
+    array_length = array->length;
+
+    for (i = 0; i < array_length; i++) {
+        if (njs_is_valid(&array->start[i])) {
+            properties_length++;
+        }
+    }
+
+    properties = njs_array_alloc(vm, properties_length, NJS_ARRAY_SPARE);
+    if (nxt_slow_path(properties == NULL)) {
+        return NXT_ERROR;
+    }
+
+    n = 0;
+    properties->start[n++] = njs_string_empty;
+
+    for (i = 0; i < array_length; i++) {
+        value = &array->start[i];
+
+        if (!njs_is_valid(&array->start[i])) {
+            continue;
+        }
+
+        switch (value->type) {
+        case NJS_OBJECT_NUMBER:
+            value = &value->data.u.object_value->value;
+            /* Fall through. */
+
+        case NJS_NUMBER:
+            ret = njs_number_to_string(vm, &num_value, value);
+            if (nxt_slow_path(ret != NXT_OK)) {
+                return NXT_ERROR;
+            }
+
+            value = &num_value;
+            break;
+
+        case NJS_OBJECT_STRING:
+            value = &value->data.u.object_value->value;
+            break;
+
+        case NJS_STRING:
+            break;
+
+        default:
+            continue;
+        }
+
+        for (k = 0; k < n; k ++) {
+            if (njs_values_strict_equal(value, &properties->start[k]) == 1) {
+                break;
+            }
+        }
+
+        if (k == n) {
+            properties->start[n++] = *value;
+        }
+    }
+
+    properties->length = n;
+    stringify->replacer.data.u.array = properties;
+
+    return NXT_OK;
+}
+
+
+static njs_json_state_t *
+njs_json_push_stringify_state(njs_vm_t *vm, njs_json_stringify_t *stringify,
+    njs_value_t *value)
+{
+    njs_json_state_t  *state;
+
+    if (stringify->stack.items >= 32) {
+        njs_json_stringify_exception(stringify,
+                                     "Nested too deep or a cyclic structure");
+        return NULL;
+    }
+
+    state = nxt_array_add(&stringify->stack, &njs_array_mem_proto,
+                           vm->mem_cache_pool);
+    if (nxt_slow_path(state == NULL)) {
+        vm->exception = &njs_exception_memory_error;
+        return NULL;
+    }
+
+    state = nxt_array_last(&stringify->stack);
+    state->value = *value;
+    state->index = 0;
+    state->written = 0;
+
+    if (njs_is_array(value)) {
+        state->type = NJS_JSON_ARRAY_START;
+
+    } else {
+        state->type = NJS_JSON_OBJECT_START;
+        state->prop_value = NULL;
+
+        if (njs_is_array(&stringify->replacer)) {
+            state->keys = stringify->replacer.data.u.array;
+
+        } else {
+            state->keys = njs_object_keys_array(vm, value);
+            if (state->keys == NULL) {
+                return NULL;
+            }
+        }
+    }
+
+    stringify->state = state;
+    return state;
+}
+
+
+static njs_json_state_t *
+njs_json_pop_stringify_state(njs_json_stringify_t *stringify)
+{
+    if (stringify->stack.items > 1) {
+        stringify->stack.items--;
+        stringify->state = nxt_array_last(&stringify->stack);
+        stringify->state->written = 1;
+        return stringify->state;
+    }
+
+    return NULL;
+}
+
+
+static void
+njs_json_stringify_exception(njs_json_stringify_t *stringify, const char* msg)
+{
+    size_t       size;
+    njs_ret_t    ret;
+    njs_value_t  *exception;
+
+    static u_char  buf[256];
+
+    exception = nxt_mem_cache_alloc(stringify->pool, sizeof(njs_value_t));
+    if (nxt_slow_path(exception == NULL)) {
+        stringify->vm->exception = &njs_exception_memory_error;
+        return;
+    }
+
+    size = snprintf((char *) buf, sizeof(buf), "TypeError: %s", msg);
+
+    ret = njs_string_new(stringify->vm, exception, buf, size, size);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        stringify->vm->exception = &njs_exception_memory_error;
+        return;
+    }
+
+    stringify->vm->exception = exception;
+}
+
+
+static nxt_int_t
+njs_json_append_value(njs_json_stringify_t *stringify, njs_value_t *value)
+{
+    switch (value->type) {
+    case NJS_OBJECT_STRING:
+        value = &value->data.u.object_value->value;
+        /* Fall through. */
+
+    case NJS_STRING:
+        return njs_json_append_string(stringify, value);
+
+    case NJS_OBJECT_NUMBER:
+        value = &value->data.u.object_value->value;
+        /* Fall through. */
+
+    case NJS_NUMBER:
+        return njs_json_append_number(stringify, value);
+
+    case NJS_OBJECT_BOOLEAN:
+        value = &value->data.u.object_value->value;
+        /* Fall through. */
+
+    case NJS_BOOLEAN:
+        if (njs_is_true(value)) {
+            return njs_json_buf_append(stringify, "true", 4);
+
+        } else {
+            return njs_json_buf_append(stringify, "false", 5);
+        }
+
+    case NJS_VOID:
+    case NJS_NULL:
+    case NJS_FUNCTION:
+        return njs_json_buf_append(stringify, "null", 4);
+
+    default:
+        njs_json_stringify_exception(stringify, "Non-serializable object");
+        return NXT_DECLINED;
+    }
+}
+
+
+static nxt_int_t
+njs_json_append_string(njs_json_stringify_t *stringify, njs_value_t *value)
+{
+    u_char             c, *dst, *dst_end;
+    size_t             length;
+    const u_char       *p, *end;
+    njs_string_prop_t  str;
+
+    static char   hex2char[16] = { '0', '1', '2', '3', '4', '5', '6', '7',
+                                   '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
+
+    (void) njs_string_prop(&str, value);
+
+    p = str.start;
+    end = p + str.size;
+    length = str.length;
+
+    dst = njs_json_buf_reserve(stringify, 64);
+    if (nxt_slow_path(dst == NULL)) {
+        return NXT_ERROR;
+    }
+
+    dst_end = dst + 64;
+
+    *dst++ = '\"';
+
+    while (p < end) {
+
+        if (*p < ' ' || *p == '\"' || *p == '\\') {
+            c = (u_char) *p++;
+            *dst++ = '\\';
+
+            switch (c) {
+            case '\\':
+                *dst++ = '\\';
+                break;
+            case '"':
+                *dst++ = '\"';
+                break;
+            case '\r':
+                *dst++ = 'r';
+                break;
+            case '\n':
+                *dst++ = 'n';
+                break;
+            case '\t':
+                *dst++ = 't';
+                break;
+            case '\b':
+                *dst++ = 'b';
+                break;
+            case '\f':
+                *dst++ = 'f';
+                break;
+            default:
+                *dst++ = 'u';
+                *dst++ = '0';
+                *dst++ = '0';
+                *dst++ = hex2char[(c & 0xf0) >> 4];
+                *dst++ = hex2char[c & 0x0f];
+            }
+        }
+
+        /*
+         * Control characters less than space are encoded using 6 bytes
+         * "\uXXXX".  Checking there is at least 6 bytes of destination storage
+         * space.
+         */
+
+        while (p < end && (dst_end - dst) > 6) {
+            if (*p < ' ' || *p == '\"' || *p == '\\') {
+                break;
+            }
+
+            if (length != 0) {
+                /* UTF-8 or ASCII string. */
+                dst = nxt_utf8_copy(dst, &p, end);
+
+            } else {
+                /* Byte string. */
+                *dst++ = *p++;
+            }
+        }
+
+        if (dst_end - dst <= 6) {
+            njs_json_buf_written(stringify, dst - stringify->last->pos);
+
+            dst = njs_json_buf_reserve(stringify, 64);
+            if (nxt_slow_path(dst == NULL)) {
+                return NXT_ERROR;
+            }
+
+            dst_end = dst + 64;
+        }
+    }
+
+    njs_json_buf_written(stringify, dst - stringify->last->pos);
+    njs_json_buf_append(stringify, "\"", 1);
+
+    return NXT_OK;
+}
+
+
+static nxt_int_t
+njs_json_append_number(njs_json_stringify_t *stringify, njs_value_t *value)
+{
+    u_char  *p;
+    size_t  size;
+    double  num;
+
+    num = value->data.u.number;
+
+    if (isnan(num) || isinf(num)) {
+        return njs_json_buf_append(stringify, "null", 4);
+
+    } else {
+        p = njs_json_buf_reserve(stringify, 64);
+        if (nxt_slow_path(p == NULL)) {
+            return NXT_ERROR;
+        }
+
+        size = njs_num_to_buf(num, p, 64);
+
+        njs_json_buf_written(stringify, size);
+    }
+
+    return NXT_OK;
+}
+
+
+/*
+ * Wraps a value as '{"": <value>}'.
+ */
+static njs_value_t *
+njs_json_wrap_value(njs_vm_t *vm, njs_value_t *value)
+{
+    nxt_int_t             ret;
+    njs_value_t           *wrapper;
+    njs_object_prop_t     *prop;
+    nxt_lvlhsh_query_t    lhq;
+
+    wrapper = nxt_mem_cache_alloc(vm->mem_cache_pool, sizeof(njs_value_t));
+    if (nxt_slow_path(wrapper == NULL)) {
+        return NULL;
+    }
+
+    wrapper->data.u.object = njs_object_alloc(vm);
+    if (nxt_slow_path(wrapper->data.u.object == NULL)) {
+        return NULL;
+    }
+
+    wrapper->type = NJS_OBJECT;
+    wrapper->data.truth = 1;
+
+    lhq.replace = 0;
+    lhq.proto = &njs_object_hash_proto;
+    lhq.pool = vm->mem_cache_pool;
+    lhq.key = nxt_string_value("");
+    lhq.key_hash = NXT_DJB_HASH_INIT;
+
+    prop = njs_object_prop_alloc(vm, &njs_string_empty, value, 1);
+    if (nxt_slow_path(prop == NULL)) {
+        return NULL;
+    }
+
+    lhq.value = prop;
+
+    ret = nxt_lvlhsh_insert(&wrapper->data.u.object->hash, &lhq);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return NULL;
+    }
+
+    return wrapper;
+}
+
+
+static nxt_int_t
+njs_json_buf_append(njs_json_stringify_t *stringify, const char* msg,
+    size_t len)
+{
+    u_char  *p;
+
+    p = njs_json_buf_reserve(stringify, len);
+    if (nxt_slow_path(p == NULL)) {
+        return NXT_ERROR;
+    }
+
+    memcpy(p, msg, len);
+
+    njs_json_buf_written(stringify, len);
+
+    return NXT_OK;
+}
+
+
+static u_char *
+njs_json_buf_reserve(njs_json_stringify_t *stringify, size_t size)
+{
+    njs_chb_node_t  *n;
+
+    if (nxt_slow_path(size == 0)) {
+        return NULL;
+    }
+
+    n = stringify->last;
+
+    if (nxt_fast_path(n != NULL && njs_json_buf_node_room(n) >= size)) {
+        return n->pos;
+    }
+
+    if (size < NJS_JSON_BUF_MIN_SIZE) {
+        size = NJS_JSON_BUF_MIN_SIZE;
+    }
+
+    n = nxt_mem_cache_alloc(stringify->pool, sizeof(njs_chb_node_t) + size);
+    if (nxt_slow_path(n == NULL)) {
+        return NULL;
+    }
+
+    n->next = NULL;
+    n->start = (u_char *) n + sizeof(njs_chb_node_t);
+    n->pos = n->start;
+    n->end = n->pos + size;
+
+    if (stringify->last != NULL) {
+        stringify->last->next = n;
+
+    } else {
+        stringify->nodes = n;
+    }
+
+    stringify->last = n;
+
+    return n->start;
+}
+
+
+static nxt_int_t
+njs_json_buf_pullup(njs_json_stringify_t *stringify, nxt_str_t *str)
+{
+    u_char          *start;
+    size_t          size;
+    njs_chb_node_t  *n;
+
+    n = stringify->nodes;
+
+    if (n == NULL) {
+        str->length = 0;
+        str->start = NULL;
+        return NXT_OK;
+    }
+
+    if (n->next == NULL) {
+        str->length = njs_json_buf_node_size(n);
+        str->start = n->start;
+        return NXT_OK;
+    }
+
+    size = 0;
+
+    while (n != NULL) {
+        size += njs_json_buf_node_size(n);
+        n = n->next;
+    }
+
+    start = nxt_mem_cache_alloc(stringify->pool, size);
+    if (nxt_slow_path(start == NULL)) {
+        return NXT_ERROR;
+    }
+
+    n = stringify->nodes;
+    str->length = size;
+    str->start = start;
+
+    while (n != NULL) {
+        size = njs_json_buf_node_size(n);
+        memcpy(start, n->start, size);
+        start += size;
+        n = n->next;
+    }
+
+    return NXT_OK;
+}
+
+
+static const njs_object_prop_t  njs_json_object_properties[] =
+{
+    /* JSON.parse(). */
+    {
+        .type = NJS_METHOD,
+        .name = njs_string("parse"),
+        .value = njs_native_function(njs_json_parse,
+                                    njs_continuation_size(njs_json_parse_t),
+                                    NJS_SKIP_ARG, NJS_STRING_ARG,
+                                    NJS_OBJECT_ARG),
+    },
+
+    /* JSON.stringify(). */
+    {
+        .type = NJS_METHOD,
+        .name = njs_string("stringify"),
+        .value = njs_native_function(njs_json_stringify,
+                                    njs_continuation_size(njs_json_stringify_t),
+                                    NJS_SKIP_ARG, NJS_SKIP_ARG, NJS_SKIP_ARG,
+                                    NJS_SKIP_ARG),
+    },
+};
+
+
+const njs_object_init_t  njs_json_object_init = {
+    nxt_string("JSON"),
+    njs_json_object_properties,
+    nxt_nitems(njs_json_object_properties),
+};
diff --git a/njs/njs_json.h b/njs/njs_json.h
new file mode 100644 (file)
index 0000000..179bd15
--- /dev/null
@@ -0,0 +1,14 @@
+
+/*
+ * Copyright (C) Dmitry Volyntsev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#ifndef _NJS_JSON_H_INCLUDED_
+#define _NJS_JSON_H_INCLUDED_
+
+
+extern const njs_object_init_t  njs_json_object_init;
+
+
+#endif /* _NJS_JSON_H_INCLUDED_ */
index a6f4313a712ed3a66e8c9cd35bda44dc71206368..9bba043aa383afad50526a81d5af8275872f11fd 100644 (file)
@@ -69,6 +69,7 @@ static const njs_keyword_t  njs_keywords[] = {
 
     { nxt_string("this"),          NJS_TOKEN_THIS, 0 },
     { nxt_string("Math"),          NJS_TOKEN_MATH, 0 },
+    { nxt_string("JSON"),          NJS_TOKEN_JSON, 0 },
 
     /* Builtin functions. */
 
index 4e19d7de36a2ea97dcd85ad605543e7b754ab9d1..bd731699f4dc0cc22f7acab48cc2c65af86cf282 100644 (file)
@@ -257,9 +257,8 @@ njs_ret_t
 njs_number_to_string(njs_vm_t *vm, njs_value_t *string,
     const njs_value_t *number)
 {
-    double             n, num;
+    double             num;
     size_t             size;
-    const char         *fmt;
     const njs_value_t  *value;
     u_char             buf[128];
 
@@ -278,32 +277,42 @@ njs_number_to_string(njs_vm_t *vm, njs_value_t *string,
         }
 
     } else {
-        n = fabs(num);
+        size = njs_num_to_buf(num, buf, sizeof(buf));
 
-        if (n == 0) {
-            fmt = "%g";
+        return njs_string_new(vm, string, buf, size, size);
+    }
 
-        } else if (n < 1) {
-            fmt = "%f";
+    *string = *value;
 
-        } else if (n < 1000000) {
-            fmt = "%g";
+    return NXT_OK;
+}
 
-        } else if (n < 1e20) {
-            fmt = "%1.f";
 
-        } else {
-            fmt = "%1.e";
-        }
+size_t
+njs_num_to_buf(double num, u_char *buf, size_t size)
+{
+    double      n;
+    const char  *fmt;
 
-        size = snprintf((char *) buf, sizeof(buf), fmt, num);
+    n = fabs(num);
 
-        return njs_string_new(vm, string, buf, size, size);
-    }
+    if (n == 0) {
+        fmt = "%g";
 
-    *string = *value;
+    } else if (n < 1) {
+        fmt = "%f";
 
-    return NXT_OK;
+    } else if (n < 1000000) {
+        fmt = "%g";
+
+    } else if (n < 1e20) {
+        fmt = "%1.f";
+
+    } else {
+        fmt = "%1.e";
+    }
+
+    return snprintf((char *) buf, size, fmt, num);
 }
 
 
index ee3c3b5e2d924b7b0e33b664003a32012f7d2915..3fabdcab0f32c6d18346a2f0278d91829727edfc 100644 (file)
@@ -17,6 +17,7 @@ uint64_t njs_number_hex_parse(u_char **start, u_char *end);
 int64_t njs_number_radix_parse(u_char **start, u_char *end, uint8_t radix);
 njs_ret_t njs_number_to_string(njs_vm_t *vm, njs_value_t *string,
     const njs_value_t *number);
+size_t njs_num_to_buf(double num, u_char *buf, size_t size);
 njs_ret_t njs_number_constructor(njs_vm_t *vm, njs_value_t *args,
     nxt_uint_t nargs, njs_index_t unused);
 njs_ret_t njs_number_global_is_nan(njs_vm_t *vm, njs_value_t *args,
index 2cb03a2cec06edbec34ddffaa7e1a6dbfaa35375..bb6e84c2f74fd1470fe67eacad446f745ac9ad85 100644 (file)
@@ -320,6 +320,29 @@ njs_object_create(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs,
 static njs_ret_t
 njs_object_keys(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs,
     njs_index_t unused)
+{
+    njs_array_t  *keys;
+
+    if (nargs < 2 || !njs_is_object(&args[1])) {
+        vm->exception = &njs_exception_type_error;
+        return NXT_ERROR;
+    }
+
+    keys = njs_object_keys_array(vm, &args[1]);
+    if (keys == NULL) {
+        vm->exception = &njs_exception_memory_error;
+        return NXT_ERROR;
+    }
+
+    vm->retval.data.u.array = keys;
+    vm->retval.type = NJS_ARRAY;
+    vm->retval.data.truth = 1;
+
+    return NXT_OK;
+}
+
+njs_array_t*
+njs_object_keys_array(njs_vm_t *vm, njs_value_t *object)
 {
     size_t             size;
     uint32_t           i, n, keys_length, array_length;
@@ -329,17 +352,12 @@ njs_object_keys(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs,
     njs_object_prop_t  *prop;
     nxt_lvlhsh_each_t  lhe;
 
-    if (nargs < 2 || !njs_is_object(&args[1])) {
-        vm->exception = &njs_exception_type_error;
-        return NXT_ERROR;
-    }
-
     array = NULL;
     keys_length = 0;
     array_length = 0;
 
-    if (njs_is_array(&args[1])) {
-        array = args[1].data.u.array;
+    if (njs_is_array(object)) {
+        array = object->data.u.array;
         array_length = array->length;
 
         for (i = 0; i < array_length; i++) {
@@ -351,7 +369,7 @@ njs_object_keys(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs,
 
     nxt_lvlhsh_each_init(&lhe, &njs_object_hash_proto);
 
-    hash = &args[1].data.u.object->hash;
+    hash = &object->data.u.object->hash;
 
     for ( ;; ) {
         prop = nxt_lvlhsh_each(hash, &lhe);
@@ -367,7 +385,7 @@ njs_object_keys(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs,
 
     keys = njs_array_alloc(vm, keys_length, NJS_ARRAY_SPARE);
     if (nxt_slow_path(keys == NULL)) {
-        return NXT_ERROR;
+        return NULL;
     }
 
     n = 0;
@@ -399,11 +417,7 @@ njs_object_keys(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs,
         }
     }
 
-    vm->retval.data.u.array = keys;
-    vm->retval.type = NJS_ARRAY;
-    vm->retval.data.truth = 1;
-
-    return NXT_OK;
+    return keys;
 }
 
 
index bee87d3ebd7b88215616471353e3515b98cf8000..14a57d502aa05abbe24e715410b155e0cbd1ab74 100644 (file)
@@ -42,6 +42,7 @@ njs_object_t *njs_object_alloc(njs_vm_t *vm);
 njs_object_t *njs_object_value_copy(njs_vm_t *vm, njs_value_t *value);
 njs_object_t *njs_object_value_alloc(njs_vm_t *vm, const njs_value_t *value,
     nxt_uint_t type);
+njs_array_t *njs_object_keys_array(njs_vm_t *vm, njs_value_t *object);
 njs_object_prop_t *njs_object_property(njs_vm_t *vm, njs_object_t *obj,
     nxt_lvlhsh_query_t *lhq);
 nxt_int_t njs_object_hash_create(njs_vm_t *vm, nxt_lvlhsh_t *hash,
@@ -63,5 +64,4 @@ njs_ret_t njs_object_prototype_to_string(njs_vm_t *vm, njs_value_t *args,
 extern const njs_object_init_t  njs_object_constructor_init;
 extern const njs_object_init_t  njs_object_prototype_init;
 
-
 #endif /* _NJS_OBJECT_H_INCLUDED_ */
index 3a9edd01491a18b2f5fdc26eaefb7e0b8ffd0f90..ba8f6c9b6735746be3af18a2f53d4f27df51626f 100644 (file)
         'p'), 'r'), 'o'), 't'), 'o'), 't'), 'y'), 'p'), 'e')
 
 
+#define NJS_TO_JSON_HASH                                                      \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(NXT_DJB_HASH_INIT,                                       \
+        't'), 'o'), 'J'), 'S'), 'O'), 'N')
+
+
 #define NJS_TO_STRING_HASH                                                    \
     nxt_djb_hash_add(                                                         \
     nxt_djb_hash_add(                                                         \
index f2b06a07f5f638f61ac5115e8271f7f7feee177f..4b99dd80a41353841188a7357e47e2c6f75a4cc7 100644 (file)
@@ -1989,6 +1989,7 @@ njs_parser_terminal(njs_vm_t *vm, njs_parser_t *parser, njs_token_t token)
         /* Fall through. */
 
     case NJS_TOKEN_MATH:
+    case NJS_TOKEN_JSON:
         return njs_parser_builtin_object(vm, parser, node);
 
     case NJS_TOKEN_OBJECT_CONSTRUCTOR:
index 13dfbda8e83f70e61280a17e721014694cd27b61..f39fa419bf1ad8e76755bda16cd3eb002894bde5 100644 (file)
@@ -166,6 +166,7 @@ typedef enum {
 
     NJS_TOKEN_GLOBAL_THIS,
     NJS_TOKEN_MATH,
+    NJS_TOKEN_JSON,
 
     NJS_TOKEN_OBJECT_CONSTRUCTOR,
     NJS_TOKEN_ARRAY_CONSTRUCTOR,
index 0f74dde99c347cd913901a4084ac916f525e5385..79a6f122cae288dff524415339daa99aa2b4f8b9 100644 (file)
@@ -797,7 +797,8 @@ enum njs_constructor_e {
 enum njs_object_e {
     NJS_OBJECT_THIS = 0,
     NJS_OBJECT_MATH,
-#define NJS_OBJECT_MAX         (NJS_OBJECT_MATH + 1)
+    NJS_OBJECT_JSON,
+#define NJS_OBJECT_MAX         (NJS_OBJECT_JSON + 1)
 };
 
 
index ec07deaafad55cc84e7b9b32c897aaaac4be1205..69caedd4717aae1d659292d36ec4f17e3f88b856 100644 (file)
@@ -7837,6 +7837,595 @@ static njs_unit_test_t  njs_test[] =
     { nxt_string("parseFloat('-5.7e+abc')"),
       nxt_string("-5.7") },
 
+    /* JSON.parse() */
+
+    { nxt_string("JSON.parse('null')"),
+      nxt_string("null") },
+
+    { nxt_string("JSON.parse('true')"),
+      nxt_string("true") },
+
+    { nxt_string("JSON.parse('false')"),
+      nxt_string("false") },
+
+    { nxt_string("JSON.parse('0')"),
+      nxt_string("0") },
+
+    { nxt_string("JSON.parse('-1234.56e2')"),
+      nxt_string("-123456") },
+
+    { nxt_string("typeof(JSON.parse('true'))"),
+      nxt_string("boolean") },
+
+    { nxt_string("typeof(JSON.parse('false'))"),
+      nxt_string("boolean") },
+
+    { nxt_string("typeof(JSON.parse('1'))"),
+      nxt_string("number") },
+
+    { nxt_string("typeof(JSON.parse('\"\"'))"),
+      nxt_string("string") },
+
+    { nxt_string("typeof(JSON.parse('{}'))"),
+      nxt_string("object") },
+
+    { nxt_string("typeof(JSON.parse('[]'))"),
+      nxt_string("object") },
+
+    { nxt_string("JSON.parse('\"abc\"')"),
+      nxt_string("abc") },
+
+    { nxt_string("JSON.parse('\"\\\\\"\"')"),
+      nxt_string("\"") },
+
+    { nxt_string("JSON.parse('\"\\\\n\"')"),
+      nxt_string("\n") },
+
+    { nxt_string("JSON.parse('\"\\\\t\"')"),
+      nxt_string("\t") },
+
+    { nxt_string("JSON.parse('\"ab\\\\\"c\"')"),
+      nxt_string("ab\"c") },
+
+    { nxt_string("JSON.parse('\"abcdefghijklmopqr\\\\\"s\"')"),
+      nxt_string("abcdefghijklmopqr\"s") },
+
+    { nxt_string("JSON.parse('\"ab\\\\\"c\"').length"),
+      nxt_string("4") },
+
+    { nxt_string("JSON.parse('\"аб\\\\\"в\"')"),
+      nxt_string("аб\"в") },
+
+    { nxt_string("JSON.parse('\"аб\\\\\"в\"').length"),
+      nxt_string("4") },
+
+    { nxt_string("JSON.parse('\"абвгдеёжзийкл\"').length"),
+      nxt_string("13") },
+
+    { nxt_string("JSON.parse('\"абвгдеёжзийкл\"').length"),
+      nxt_string("13") },
+
+    { nxt_string("JSON.parse('\"\\\\u03B1\"')"),
+      nxt_string("α") },
+
+    { nxt_string("JSON.parse('\"\\\\uD801\\\\uDC00\"')"),
+      nxt_string("𐐀") },
+
+    { nxt_string("JSON.parse('\"\\\\u03B1\"') == JSON.parse('\"\\\\u03b1\"')"),
+      nxt_string("true") },
+
+    { nxt_string("JSON.parse('\"\\\\u03B1\"').length"),
+      nxt_string("1") },
+
+    { nxt_string("JSON.parse('{\"a\":1}').a"),
+      nxt_string("1") },
+
+    { nxt_string("JSON.parse('{\"a\":1,\"a\":2}').a"),
+      nxt_string("2") },
+
+    { nxt_string("JSON.parse('{   \"a\" :  \"b\"   }').a"),
+      nxt_string("b") },
+
+    { nxt_string("JSON.parse('{\"a\":{\"b\":1}}').a.b"),
+      nxt_string("1") },
+
+    { nxt_string("JSON.parse('[{}, true ,1.1e2, {\"a\":[3,\"b\"]}]')[3].a[1]"),
+      nxt_string("b") },
+
+    { nxt_string("var o = JSON.parse('{\"a\":2}');"
+                 "Object.getOwnPropertyDescriptor(o, 'a').configurable"),
+      nxt_string("true") },
+
+    { nxt_string("var o = JSON.parse('{\"a\":2}');"
+                 "Object.getOwnPropertyDescriptor(o, 'a').writable"),
+      nxt_string("true") },
+
+    { nxt_string("var o = JSON.parse('{\"a\":2}');"
+                 "Object.getOwnPropertyDescriptor(o, 'a').enumerable"),
+      nxt_string("true") },
+
+    { nxt_string("var o = JSON.parse('{\"a\":2}');"
+                 "o.a = 3; o.a"),
+      nxt_string("3") },
+
+    { nxt_string("var o = JSON.parse('{\"a\":2}');"
+                 "o.b = 3; o.b"),
+      nxt_string("3") },
+
+    { nxt_string("var o = JSON.parse('{}', function(k, v) {return v;}); o"),
+      nxt_string("[object Object]") },
+
+    { nxt_string("var o = JSON.parse('{\"a\":2, \"b\":4, \"a\":{}}',"
+                 "                    function(k, v) {return undefined;});"
+                 "o"),
+      nxt_string("undefined") },
+
+    { nxt_string("var o = JSON.parse('{\"a\":2, \"c\":4, \"b\":\"x\"}',"
+                 "  function(k, v) {if (k === '' || typeof v === 'number') return v });"
+                 "Object.keys(o)"),
+      nxt_string("a,c") },
+
+    { nxt_string("var o = JSON.parse('{\"a\":2, \"b\":{}}',"
+                 "                    function(k, v) {return k;});"
+                 "o+typeof(o)"),
+      nxt_string("string") },
+
+    { nxt_string("var o = JSON.parse('[\"a\", \"b\"]',"
+                 "                    function(k, v) {return v;});"
+                 "o"),
+      nxt_string("a,b") },
+
+    { nxt_string("var o = JSON.parse('{\"a\":[1,{\"b\":1},3]}',"
+                 "                    function(k, v) {return v;});"
+                 "o.a[1].b"),
+      nxt_string("1") },
+
+    { nxt_string("var o = JSON.parse('{\"a\":[1,2]}',"
+                 "  function(k, v) {if (k === '' || k === 'a') {return v;}});"
+                 "o.a"),
+      nxt_string(",") },
+
+    { nxt_string("var o = JSON.parse('{\"a\":[1,2]}',"
+                 "  function(k, v) {return (k === '' || k === 'a') ? v : v*2});"
+                 "o.a"),
+      nxt_string("2,4") },
+
+    { nxt_string("var o = JSON.parse('{\"a\":2, \"b\":{\"c\":[\"xx\"]}}',"
+                 "   function(k, v) {return typeof v === 'number' ? v * 2 : v;});"
+                 "o.a+o.b.c[0]"),
+      nxt_string("4xx") },
+
+    { nxt_string("var o = JSON.parse('{\"aa\":{\"b\":1}, \"abb\":1, \"c\":1}',"
+                 "   function(k, v) {return (k === '' || /^a/.test(k)) ? v : undefined;});"
+                 "Object.keys(o)"),
+      nxt_string("aa,abb") },
+
+    { nxt_string("var o = JSON.parse('{\"a\":\"x\"}',"
+                 "   function(k, v) {if (k === 'a') {this.b='y';} return v});"
+                 "o.a+o.b"),
+      nxt_string("xy") },
+
+    { nxt_string("var o = JSON.parse('{\"a\":\"x\"}',"
+                 "   function(k, v) {return (k === 'a' ? {x:1} : v)});"
+                 "o.a.x"),
+      nxt_string("1") },
+
+    { nxt_string("var keys = []; var o = JSON.parse('{\"a\":2, \"b\":{\"c\":\"xx\"}}',"
+                 "   function(k, v) {keys.push(k); return v;});"
+                 "keys"),
+      nxt_string("a,c,b,") },
+
+    { nxt_string("var args = []; var o = JSON.parse('[2,{\"a\":3}]',"
+                 "   function(k, v) {args.push(k+\":\"+v); return v;});"
+                 "args.join('|')"),
+      nxt_string("0:2|a:3|1:[object Object]|:2,[object Object]") },
+
+    { nxt_string("JSON.parse()"),
+      nxt_string("SyntaxError: Unexpected token at position 0") },
+
+    { nxt_string("JSON.parse([])"),
+      nxt_string("SyntaxError: Unexpected end of input at position 0") },
+
+    { nxt_string("JSON.parse('')"),
+      nxt_string("SyntaxError: Unexpected end of input at position 0") },
+
+    { nxt_string("JSON.parse('fals')"),
+      nxt_string("SyntaxError: Unexpected token at position 0") },
+
+    { nxt_string("JSON.parse(' t')"),
+      nxt_string("SyntaxError: Unexpected token at position 1") },
+
+    { nxt_string("JSON.parse('nu')"),
+      nxt_string("SyntaxError: Unexpected token at position 0") },
+
+    { nxt_string("JSON.parse('-')"),
+      nxt_string("SyntaxError: Unexpected number at position 0") },
+
+    { nxt_string("JSON.parse('--')"),
+      nxt_string("SyntaxError: Unexpected number at position 1") },
+
+    { nxt_string("JSON.parse('1-')"),
+      nxt_string("SyntaxError: Unexpected token at position 1") },
+
+    { nxt_string("JSON.parse('1ee1')"),
+      nxt_string("SyntaxError: Unexpected token at position 1") },
+
+    { nxt_string("JSON.parse('1eg')"),
+      nxt_string("SyntaxError: Unexpected token at position 1") },
+
+    { nxt_string("JSON.parse('0x01')"),
+      nxt_string("SyntaxError: Unexpected token at position 1") },
+
+    { nxt_string("JSON.parse('\"абв')"),
+      nxt_string("SyntaxError: Unexpected end of input at position 4") },
+
+    { nxt_string("JSON.parse('\"\b')"),
+      nxt_string("SyntaxError: Forbidden source char at position 1") },
+
+    { nxt_string("JSON.parse('\"\\\\u')"),
+      nxt_string("SyntaxError: Unexpected end of input at position 3") },
+
+    { nxt_string("JSON.parse('\"\\\\q\"')"),
+      nxt_string("SyntaxError: Unknown escape char at position 2") },
+
+    { nxt_string("JSON.parse('\"\\\\uDC01\"')"),
+      nxt_string("SyntaxError: Invalid Unicode char at position 7") },
+
+    { nxt_string("JSON.parse('\"\\\\uD801\\\\uE000\"')"),
+      nxt_string("SyntaxError: Invalid surrogate pair at position 13") },
+
+    { nxt_string("JSON.parse('{')"),
+      nxt_string("SyntaxError: Unexpected end of input at position 1") },
+
+    { nxt_string("JSON.parse('{{')"),
+      nxt_string("SyntaxError: Unexpected token at position 1") },
+
+    { nxt_string("JSON.parse('{[')"),
+      nxt_string("SyntaxError: Unexpected token at position 1") },
+
+    { nxt_string("JSON.parse('{\"a\"')"),
+      nxt_string("SyntaxError: Unexpected token at position 4") },
+
+    { nxt_string("JSON.parse('{\"a\":')"),
+      nxt_string("SyntaxError: Unexpected end of input at position 5") },
+
+    { nxt_string("JSON.parse('{\"a\":{')"),
+      nxt_string("SyntaxError: Unexpected end of input at position 6") },
+
+    { nxt_string("JSON.parse('{\"a\":{}')"),
+      nxt_string("SyntaxError: Unexpected end of input at position 7") },
+
+    { nxt_string("JSON.parse('{\"a\":{}g')"),
+      nxt_string("SyntaxError: Unexpected token at position 7") },
+
+    { nxt_string("JSON.parse('{\"a\":{},')"),
+      nxt_string("SyntaxError: Unexpected end of input at position 8") },
+
+    { nxt_string("JSON.parse('{\"a\":{},}')"),
+      nxt_string("SyntaxError: Trailing comma at position 7") },
+
+    { nxt_string("JSON.parse('{\"a\":{},,')"),
+      nxt_string("SyntaxError: Unexpected token at position 8") },
+
+    { nxt_string("JSON.parse('{\"a\":{},,}')"),
+      nxt_string("SyntaxError: Unexpected token at position 8") },
+
+    { nxt_string("JSON.parse('[')"),
+      nxt_string("SyntaxError: Unexpected end of input at position 1") },
+
+    { nxt_string("JSON.parse('[q')"),
+      nxt_string("SyntaxError: Unexpected token at position 1") },
+
+    { nxt_string("JSON.parse('[\"a')"),
+      nxt_string("SyntaxError: Unexpected end of input at position 3") },
+
+    { nxt_string("JSON.parse('[1 ')"),
+      nxt_string("SyntaxError: Unexpected end of input at position 3") },
+
+    { nxt_string("JSON.parse('[1,]')"),
+      nxt_string("SyntaxError: Trailing comma at position 2") },
+
+    { nxt_string("JSON.parse('[1 , 5 ')"),
+      nxt_string("SyntaxError: Unexpected end of input at position 7") },
+
+    { nxt_string("JSON.parse('{\"a\":'.repeat(32))"),
+      nxt_string("SyntaxError: Nested too deep at position 155") },
+
+    { nxt_string("JSON.parse('['.repeat(32))"),
+      nxt_string("SyntaxError: Nested too deep at position 31") },
+
+    { nxt_string("var o = JSON.parse('{', function(k, v) {return v;});o"),
+      nxt_string("SyntaxError: Unexpected end of input at position 1") },
+
+    { nxt_string("var o = JSON.parse('{\"a\":1}', "
+                 "                   function(k, v) {return v.a.a;}); o"),
+      nxt_string("TypeError") },
+
+    /* JSON.stringify() */
+
+    { nxt_string("JSON.stringify()"),
+      nxt_string("undefined") },
+
+    { nxt_string("JSON.stringify('')"),
+      nxt_string("\"\"") },
+
+    { nxt_string("JSON.stringify('abc')"),
+      nxt_string("\"abc\"") },
+
+    { nxt_string("JSON.stringify(new String('abc'))"),
+      nxt_string("\"abc\"") },
+
+    { nxt_string("JSON.stringify(123)"),
+      nxt_string("123") },
+
+    { nxt_string("JSON.stringify(new Number(123))"),
+      nxt_string("123") },
+
+    { nxt_string("JSON.stringify(true)"),
+      nxt_string("true") },
+
+    { nxt_string("JSON.stringify(false)"),
+      nxt_string("false") },
+
+    { nxt_string("JSON.stringify(new Boolean(1))"),
+      nxt_string("true") },
+
+    { nxt_string("JSON.stringify(new Boolean(0))"),
+      nxt_string("false") },
+
+    { nxt_string("JSON.stringify(null)"),
+      nxt_string("null") },
+
+    { nxt_string("JSON.stringify(undefined)"),
+      nxt_string("undefined") },
+
+    { nxt_string("JSON.stringify({})"),
+      nxt_string("{}") },
+
+    { nxt_string("JSON.stringify([])"),
+      nxt_string("[]") },
+
+    { nxt_string("JSON.stringify({a:\"b\",c:19,e:null,t:true,f:false})"),
+      nxt_string("{\"a\":\"b\",\"c\":19,\"e\":null,\"t\":true,\"f\":false}") },
+
+    { nxt_string("JSON.stringify({a:1, b:undefined})"),
+      nxt_string("{\"a\":1}") },
+
+    { nxt_string("var o = {a:1, c:2};"
+                 "Object.defineProperty(o, 'b', {enumerable:false, value:3});"
+                 "JSON.stringify(o)"),
+      nxt_string("{\"a\":1,\"c\":2}") },
+
+    { nxt_string("JSON.stringify({a:{}, b:[function(v){}]})"),
+      nxt_string("{\"a\":{},\"b\":[null]}") },
+
+    /* Ignoring named properties of an array. */
+
+    { nxt_string("var a = [1,2]; a.a = 1;"
+                 "JSON.stringify(a)"),
+      nxt_string("[1,2]") },
+
+    { nxt_string("JSON.stringify({a:{b:{c:{d:1}, e:function(v){}}}})"),
+      nxt_string("{\"a\":{\"b\":{\"c\":{\"d\":1}}}}") },
+
+    { nxt_string("JSON.stringify([[\"b\",undefined],1,[5],{a:1}])"),
+      nxt_string("[[\"b\",null],1,[5],{\"a\":1}]") },
+
+    { nxt_string("var json = '{\"a\":{\"b\":{\"c\":{\"d\":1},\"e\":[true]}}}';"
+                 "json == JSON.stringify(JSON.parse(json))"),
+      nxt_string("true") },
+
+    { nxt_string("var json = '{\"a\":\"абв\",\"b\":\"α\"}';"
+                 "json == JSON.stringify(JSON.parse(json))"),
+      nxt_string("true") },
+
+    /* Multibyte characters: z - 1 byte, α - 2 bytes, 𐐀 - 4 bytes */
+
+    { nxt_string("JSON.stringify('α𐐀z'.repeat(10))"),
+      nxt_string("\"α𐐀zα𐐀zα𐐀zα𐐀zα𐐀zα𐐀zα𐐀zα𐐀zα𐐀zα𐐀z\"") },
+
+    { nxt_string("JSON.stringify('α𐐀z'.repeat(10)).length"),
+      nxt_string("32") },
+
+    { nxt_string("JSON.stringify('a\nbc')"),
+      nxt_string("\"a\\nbc\"") },
+
+    { nxt_string("JSON.stringify('а\tбв')"),
+      nxt_string("\"а\\tбв\"") },
+
+    { nxt_string("JSON.stringify('\n\t\r\"\f\b ')"),
+      nxt_string("\"\\n\\t\\r\\\"\\f\\b \"") },
+
+    { nxt_string("JSON.stringify('\x00\x01\x02\x1f')"),
+      nxt_string("\"\\u0000\\u0001\\u0002\\u001F\"") },
+
+    { nxt_string("JSON.stringify('abc\x00')"),
+      nxt_string("\"abc\\u0000\"") },
+
+    { nxt_string("JSON.stringify('\x00zz')"),
+      nxt_string("\"\\u0000zz\"") },
+
+    { nxt_string("JSON.stringify('\x00')"),
+      nxt_string("\"\\u0000\"") },
+
+    { nxt_string("JSON.stringify('a\x00z')"),
+      nxt_string("\"a\\u0000z\"") },
+
+    { nxt_string("JSON.stringify('\x00z\x00')"),
+      nxt_string("\"\\u0000z\\u0000\"") },
+
+    { nxt_string("var i, s, r = true;"
+                 " for (i = 0; i < 128; i++) {"
+                 "  s = 'α𐐀z'.repeat(i);"
+                 "  r &= (JSON.stringify(s) == ('\"' + s + '\"'));"
+                 "}; r"),
+      nxt_string("1") },
+
+    { nxt_string("JSON.stringify('\\u0000'.repeat(10)) == ('\"' + '\\\\u0000'.repeat(10) + '\"')"),
+      nxt_string("true") },
+
+    { nxt_string("JSON.stringify('abc'.repeat(100)).length"),
+      nxt_string("302") },
+
+    { nxt_string("JSON.stringify('абв'.repeat(100)).length"),
+      nxt_string("302") },
+
+    /* Byte strings. */
+
+    { nxt_string("JSON.stringify('\\u00CE\\u00B1\\u00C2\\u00B6'.toBytes())"),
+      nxt_string("\"α¶\"") },
+
+    { nxt_string("JSON.stringify('µ§±®'.toBytes())"),
+      nxt_string("\"\xB5\xA7\xB1\xAE\"") },
+
+    /* Optional arguments. */
+
+    { nxt_string("JSON.stringify(undefined, undefined, 1)"),
+      nxt_string("undefined") },
+
+    { nxt_string("JSON.stringify([{a:1,b:{c:2}},1], undefined, 0)"),
+      nxt_string("[{\"a\":1,\"b\":{\"c\":2}},1]") },
+
+    { nxt_string("JSON.stringify([{a:1,b:{c:2}},1], undefined, 1)"),
+      nxt_string("[\n {\n  \"a\": 1,\n  \"b\": {\n   \"c\": 2\n  }\n },\n 1\n]") },
+
+    { nxt_string("JSON.stringify([{a:1,b:{c:2}},1], undefined, ' ')"),
+      nxt_string("[\n {\n  \"a\": 1,\n  \"b\": {\n   \"c\": 2\n  }\n },\n 1\n]") },
+
+    { nxt_string("JSON.stringify([{a:1,b:{c:2}},1], undefined, '#')"),
+      nxt_string("[\n#{\n##\"a\": 1,\n##\"b\": {\n###\"c\": 2\n##}\n#},\n#1\n]") },
+
+    { nxt_string("JSON.stringify([1], undefined, 'AAAAABBBBBC')"),
+      nxt_string("[\nAAAAABBBBB1\n]") },
+
+    { nxt_string("JSON.stringify([1], undefined, 11)"),
+      nxt_string("[\n          1\n]") },
+
+    { nxt_string("JSON.stringify([{a:1,b:{c:2}},1], undefined, -1)"),
+      nxt_string("[{\"a\":1,\"b\":{\"c\":2}},1]") },
+
+    { nxt_string("JSON.stringify([{a:1,b:{c:2}},1], undefined, new Date())"),
+      nxt_string("[{\"a\":1,\"b\":{\"c\":2}},1]") },
+
+    { nxt_string("JSON.stringify({toJSON:function(k){}})"),
+      nxt_string("undefined") },
+
+    { nxt_string("JSON.stringify({toJSON:function(k){return k}})"),
+      nxt_string("\"\"") },
+
+    { nxt_string("JSON.stringify(new Date(1308895323625))"),
+      nxt_string("\"2011-06-24T06:02:03.625Z\"") },
+
+    { nxt_string("JSON.stringify({a:new Date(1308895323625)})"),
+      nxt_string("{\"a\":\"2011-06-24T06:02:03.625Z\"}") },
+
+    { nxt_string("JSON.stringify({b:{toJSON:function(k){return undefined}}})"),
+      nxt_string("{}") },
+
+    { nxt_string("JSON.stringify({b:{toJSON:function(k){}},c:1})"),
+      nxt_string("{\"c\":1}") },
+
+    { nxt_string("JSON.stringify({b:{toJSON:function(k){return k}}})"),
+      nxt_string("{\"b\":\"b\"}") },
+
+    { nxt_string("JSON.stringify({a:1,b:new Date(1308895323625),c:2})"),
+      nxt_string("{\"a\":1,\"b\":\"2011-06-24T06:02:03.625Z\",\"c\":2}") },
+
+    { nxt_string("JSON.stringify({a:{b:new Date(1308895323625)}})"),
+      nxt_string("{\"a\":{\"b\":\"2011-06-24T06:02:03.625Z\"}}") },
+
+    { nxt_string("function key(k){return k}; function und(k){}"
+                 "JSON.stringify([{toJSON:key},{toJSON:und},{toJSON:key}])"),
+      nxt_string("[\"0\",null,\"2\"]") },
+
+    { nxt_string("JSON.stringify({b:{a:1,c:[2]}}, function(k,v){return v})"),
+      nxt_string("{\"b\":{\"a\":1,\"c\":[2]}}") },
+
+    { nxt_string("JSON.stringify([{a:1}, 2], function(k,v){return v})"),
+      nxt_string("[{\"a\":1},2]") },
+
+    { nxt_string("JSON.stringify({a:{toJSON:function(k){}}}, function(k,v){return v})"),
+      nxt_string("{}") },
+
+    { nxt_string("JSON.stringify({a:{toJSON:function(k){return 1}}}, function(k,v){return v})"),
+      nxt_string("{\"a\":1}") },
+
+    { nxt_string("JSON.stringify([{toJSON:function(k){}}], function(k,v){return v})"),
+      nxt_string("[null]") },
+
+    { nxt_string("JSON.stringify([{toJSON:function(k){return 1}}], function(k,v){return v})"),
+      nxt_string("[1]") },
+
+    { nxt_string("JSON.stringify({a:new Date(1308895323625)}, function(k,v){return v})"),
+      nxt_string("{\"a\":\"2011-06-24T06:02:03.625Z\"}") },
+
+    { nxt_string("JSON.stringify([new Date(1308895323625)], function(k,v){return v})"),
+      nxt_string("[\"2011-06-24T06:02:03.625Z\"]") },
+
+    { nxt_string("JSON.stringify([new Date(1308895323625)], "
+                 "  function(k,v){return (typeof v === 'string') ? v.toLowerCase() : v})"),
+      nxt_string("[\"2011-06-24t06:02:03.625z\"]") },
+
+    { nxt_string("JSON.stringify([new Date(1308895323625)], "
+                 "  function(k,v){return (typeof v === 'string') ? v.toLowerCase() : v}, '#')"),
+      nxt_string("[\n#\"2011-06-24t06:02:03.625z\"\n]") },
+
+    { nxt_string("JSON.stringify({a:new Date(1308895323625),b:1,c:'a'}, "
+                 "  function(k,v){return (typeof v === 'string') ? undefined : v})"),
+      nxt_string("{\"b\":1}") },
+
+    { nxt_string("JSON.stringify({a:new Date(1308895323625),b:1,c:'a'}, "
+                 "  function(k,v){return (typeof v === 'string') ? undefined : v}, '#')"),
+      nxt_string("{\n#\"b\": 1\n}") },
+
+    { nxt_string("JSON.stringify([new Date(1308895323625),1,'a'], "
+                 "  function(k,v){return (typeof v === 'string') ? undefined : v})"),
+      nxt_string("[null,1,null]") },
+
+    { nxt_string("var keys = []; var o = JSON.stringify({a:2, b:{c:1}},"
+                 "   function(k, v) {keys.push(k); return v;});"
+                 "keys"),
+      nxt_string(",a,b,c") },
+
+    { nxt_string("JSON.stringify(['a', 'b', 'c'], "
+                 "    function(i, v) { if (i === '0') {return undefined} "
+                 "                     else if (i == 1) {return 2} "
+                 "                     else {return v}})"),
+      nxt_string("[null,2,\"c\"]") },
+
+    { nxt_string("JSON.stringify({a:2, b:{c:1}},"
+                 "               function(k, v) {delete this['b']; return v;})"),
+      nxt_string("{\"a\":2}") },
+
+    { nxt_string("JSON.stringify(JSON.parse('{\"a\":1,\"b\":2}', "
+                 "          function(k, v) {delete this['b']; return v;}))"),
+      nxt_string("{\"a\":1}") },
+
+    { nxt_string("var keys = []; var o = JSON.stringify([[1,2],{a:3}, 4],"
+                 "   function(k, v) {keys.push(k); return v;});"
+                 "keys"),
+      nxt_string(",0,0,1,1,a,2") },
+
+    { nxt_string("JSON.stringify({b:{a:1,c:[2]}}, ['a', undefined, 'b', {}, 'a'])"),
+      nxt_string("{\"b\":{\"a\":1}}") },
+
+    { nxt_string("JSON.stringify({b:{a:1,c:[2]}}, [new String('a'), new String('b')])"),
+      nxt_string("{\"b\":{\"a\":1}}") },
+
+    { nxt_string("JSON.stringify({'1':1,'2':2,'3':3}, [1, new Number(2)])"),
+      nxt_string("{\"1\":1,\"2\":2}") },
+
+    { nxt_string("var objs = []; var o = JSON.stringify({a:1},"
+                 "   function(k, v) {objs.push(this); return v});"
+                 "JSON.stringify(objs)"),
+      nxt_string("[{\"\":{\"a\":1}},{\"a\":1}]") },
+
+    { nxt_string("var a = []; a[0] = a; JSON.stringify(a)"),
+      nxt_string("TypeError: Nested too deep or a cyclic structure") },
+
+    { nxt_string("var a = {}; a.a = a; JSON.stringify(a)"),
+      nxt_string("TypeError: Nested too deep or a cyclic structure") },
+
     /* Trick: number to boolean. */
 
     { nxt_string("var a = 0; !!a"),
index b6305bfce656014ad428e55c5ba63c606d058cd7..8362b40a16a312ceccd1e69e0b09b6d803c3bf5d 100644 (file)
@@ -76,6 +76,36 @@ nxt_utf8_prev(const u_char *p)
 }
 
 
+nxt_inline u_char *
+nxt_utf8_copy(u_char *dst, const u_char **src, const u_char *end)
+{
+    u_char        c;
+    const u_char  *p;
+
+    p = *src;
+    c = *p++;
+    *dst++ = c;
+
+    if ((c & 0x80) != 0) {
+
+        do {
+            c = *p;
+
+            if ((c & 0xC0) != 0x80) {
+                break;
+            }
+
+            *dst++ = c;
+            p++;
+
+        } while (p < end);
+    }
+
+    *src = p;
+    return dst;
+}
+
+
 #define nxt_utf8_size(u)                                                      \
     ((u < 0x80) ? 1 : ((u < 0x0800) ? 2 : ((u < 0x10000) ? 3 : 4)))