[njs] JSON object.

Dmitry Volyntsev xeioex at nginx.com
Tue Oct 3 18:25:29 UTC 2017


details:   http://hg.nginx.org/njs/rev/94c42736a730
branches:  
changeset: 410:94c42736a730
user:      Dmitry Volyntsev <xeioex at nginx.com>
date:      Tue Oct 03 21:24:58 2017 +0300
description:
JSON object.

diffstat:

 Makefile                 |    14 +
 njs/njs_array.c          |     2 +-
 njs/njs_array.h          |     1 +
 njs/njs_builtin.c        |     2 +
 njs/njs_generator.c      |     1 +
 njs/njs_json.c           |  2088 ++++++++++++++++++++++++++++++++++++++++++++++
 njs/njs_json.h           |    14 +
 njs/njs_lexer_keyword.c  |     1 +
 njs/njs_number.c         |    51 +-
 njs/njs_number.h         |     1 +
 njs/njs_object.c         |    42 +-
 njs/njs_object.h         |     2 +-
 njs/njs_object_hash.h    |    10 +
 njs/njs_parser.c         |     1 +
 njs/njs_parser.h         |     1 +
 njs/njs_vm.h             |     3 +-
 njs/test/njs_unit_test.c |   589 ++++++++++++
 nxt/nxt_utf8.h           |    30 +
 18 files changed, 2815 insertions(+), 38 deletions(-)

diffs (truncated from 3094 to 1000 lines):

diff -r f6b9efd315c5 -r 94c42736a730 Makefile
--- a/Makefile	Tue Sep 26 14:19:49 2017 +0300
+++ b/Makefile	Tue Oct 03 21:24:58 2017 +0300
@@ -16,6 +16,7 @@ NXT_BUILDDIR =	build
 	$(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 =	build
 		$(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 @@ dist:
 		-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 \
diff -r f6b9efd315c5 -r 94c42736a730 njs/njs_array.c
--- a/njs/njs_array.c	Tue Sep 26 14:19:49 2017 +0300
+++ b/njs/njs_array.c	Tue Oct 03 21:24:58 2017 +0300
@@ -157,7 +157,7 @@ njs_array_alloc(njs_vm_t *vm, uint32_t l
 }
 
 
-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;
diff -r f6b9efd315c5 -r 94c42736a730 njs/njs_array.h
--- a/njs/njs_array.h	Tue Sep 26 14:19:49 2017 +0300
+++ b/njs/njs_array.h	Tue Oct 03 21:24:58 2017 +0300
@@ -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,
diff -r f6b9efd315c5 -r 94c42736a730 njs/njs_builtin.c
--- a/njs/njs_builtin.c	Tue Sep 26 14:19:49 2017 +0300
+++ b/njs/njs_builtin.c	Tue Oct 03 21:24:58 2017 +0300
@@ -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
 const njs_object_init_t    *njs_object_init[] = {
     NULL,                         /* global this        */
     &njs_math_object_init,        /* Math               */
+    &njs_json_object_init,        /* JSON               */
 };
 
 
diff -r f6b9efd315c5 -r 94c42736a730 njs/njs_generator.c
--- a/njs/njs_generator.c	Tue Sep 26 14:19:49 2017 +0300
+++ b/njs/njs_generator.c	Tue Oct 03 21:24:58 2017 +0300
@@ -298,6 +298,7 @@ njs_generator(njs_vm_t *vm, njs_parser_t
 
     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 -r f6b9efd315c5 -r 94c42736a730 njs/njs_json.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/njs/njs_json.c	Tue Oct 03 21:24:58 2017 +0300
@@ -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;


More information about the nginx-devel mailing list