[njs] Added the pretty string representation for values.

Dmitry Volyntsev xeioex at nginx.com
Fri Jul 27 14:02:21 UTC 2018


details:   http://hg.nginx.org/njs/rev/058162fce59a
branches:  
changeset: 572:058162fce59a
user:      Dmitry Volyntsev <xeioex at nginx.com>
date:      Fri Jul 27 17:01:52 2018 +0300
description:
Added the pretty string representation for values.

Interactive shell: dumping the pretty string representation of
the last expression.

>> [1, new Number(2), {a: new String('??Z?')}, true, console.log,
    /^undef$/m, new Date(0)]
[
 1,
 [Number: 2],
 {
  a: [String: '??Z?']
 },
 true,
 [Function: native],
 /^undef$/m,
 1970-01-01T00:00:00.000Z
]

njs.dump(value[, indent]):
    Returns the pretty string representation of a value.
    value - a value of any type.
    indent - a number.
        A number of space characters per indentation level.

    njs.dump({a:[]}) -> '{a:[]}'
    njs.dump({a:[]}, 1) -> '{\n a: [\n  \n ]\n}'

console.log([value1[, values]])
    Prints to stdout the flat pretty string representation
    of values (no new lines).

console.dump([value1[, values]])
    Prints to stdout the pretty string representation of values.

This fixes #11 issue on GitHub.

diffstat:

 njs/njs.h                    |    2 +
 njs/njs_builtin.c            |   29 ++
 njs/njs_date.c               |   11 +-
 njs/njs_date.h               |    3 +
 njs/njs_error.c              |   28 +-
 njs/njs_error.h              |    2 +
 njs/njs_extern.c             |   54 ++++
 njs/njs_extern.h             |    1 +
 njs/njs_json.c               |  485 +++++++++++++++++++++++++++++++++++++++++-
 njs/njs_regexp.c             |   37 +-
 njs/njs_regexp.h             |    2 +
 njs/njs_shell.c              |   49 ++++-
 njs/test/njs_expect_test.exp |  100 ++++++--
 njs/test/njs_unit_test.c     |   11 +
 14 files changed, 738 insertions(+), 76 deletions(-)

diffs (truncated from 1288 to 1000 lines):

diff -r 766fcec15744 -r 058162fce59a njs/njs.h
--- a/njs/njs.h	Tue Jul 24 19:50:02 2018 +0300
+++ b/njs/njs.h	Fri Jul 27 17:01:52 2018 +0300
@@ -217,6 +217,8 @@ NXT_EXPORT nxt_int_t njs_value_is_string
 NXT_EXPORT nxt_int_t njs_value_is_object(njs_value_t *value);
 NXT_EXPORT nxt_int_t njs_value_is_function(njs_value_t *value);
 
+NXT_EXPORT njs_ret_t njs_vm_value_dump(njs_vm_t *vm, nxt_str_t *retval,
+    const njs_value_t *value, nxt_uint_t indent);
 NXT_EXPORT njs_value_t *njs_vm_object_prop(njs_vm_t *vm, njs_value_t *value,
     const nxt_str_t *key);
 
diff -r 766fcec15744 -r 058162fce59a njs/njs_builtin.c
--- a/njs/njs_builtin.c	Tue Jul 24 19:50:02 2018 +0300
+++ b/njs/njs_builtin.c	Fri Jul 27 17:01:52 2018 +0300
@@ -1069,6 +1069,28 @@ njs_builtin_match_native_function(njs_vm
 }
 
 
+static njs_ret_t
+njs_dump_value(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs,
+    njs_index_t unused)
+{
+    nxt_str_t          str;
+    nxt_uint_t         n;
+    const njs_value_t  *value, *indent;
+
+    value = njs_arg(args, nargs, 1);
+    indent = njs_arg(args, nargs, 2);
+
+    n = indent->data.u.number;
+    n = nxt_min(n, 5);
+
+    if (njs_vm_value_dump(vm, &str, value, n) != NXT_OK) {
+        return NXT_ERROR;
+    }
+
+    return njs_string_new(vm, &vm->retval, str.start, str.length, 0);
+}
+
+
 static const njs_object_prop_t  njs_njs_object_properties[] =
 {
     {
@@ -1076,6 +1098,13 @@ static const njs_object_prop_t  njs_njs_
         .name = njs_string("version"),
         .value = njs_string(NJS_VERSION),
     },
+
+    {
+        .type = NJS_METHOD,
+        .name = njs_string("dump"),
+        .value = njs_native_function(njs_dump_value, 0,
+                                    NJS_SKIP_ARG, NJS_SKIP_ARG, NJS_NUMBER_ARG),
+    },
 };
 
 
diff -r 766fcec15744 -r 058162fce59a njs/njs_date.c
--- a/njs/njs_date.c	Tue Jul 24 19:50:02 2018 +0300
+++ b/njs/njs_date.c	Fri Jul 27 17:01:52 2018 +0300
@@ -1019,6 +1019,13 @@ static njs_ret_t
 njs_date_prototype_to_iso_string(njs_vm_t *vm, njs_value_t *args,
     nxt_uint_t nargs, njs_index_t unused)
 {
+    return njs_date_to_string(vm, &vm->retval, &args[0]);
+}
+
+
+njs_ret_t
+njs_date_to_string(njs_vm_t *vm, njs_value_t *retval, const njs_value_t *date)
+{
     int32_t    year;
     double     time;
     size_t     size;
@@ -1026,7 +1033,7 @@ njs_date_prototype_to_iso_string(njs_vm_
     u_char     buf[NJS_ISO_DATE_TIME_LEN];
     struct tm  tm;
 
-    time = args[0].data.u.date->time;
+    time = date->data.u.date->time;
 
     if (!isnan(time)) {
         clock = time / 1000;
@@ -1042,7 +1049,7 @@ njs_date_prototype_to_iso_string(njs_vm_
                         tm.tm_hour, tm.tm_min, tm.tm_sec,
                         (int) ((int64_t) time % 1000));
 
-        return njs_string_new(vm, &vm->retval, buf, size, size);
+        return njs_string_new(vm, retval, buf, size, size);
     }
 
     njs_range_error(vm, NULL);
diff -r 766fcec15744 -r 058162fce59a njs/njs_date.h
--- a/njs/njs_date.h	Tue Jul 24 19:50:02 2018 +0300
+++ b/njs/njs_date.h	Fri Jul 27 17:01:52 2018 +0300
@@ -11,6 +11,9 @@
 njs_ret_t njs_date_constructor(njs_vm_t *vm, njs_value_t *args,
     nxt_uint_t nargs, njs_index_t unused);
 
+njs_ret_t njs_date_to_string(njs_vm_t *vm, njs_value_t *retval,
+    const njs_value_t *date);
+
 
 extern const njs_object_init_t  njs_date_constructor_init;
 extern const njs_object_init_t  njs_date_prototype_init;
diff -r 766fcec15744 -r 058162fce59a njs/njs_error.c
--- a/njs/njs_error.c	Tue Jul 24 19:50:02 2018 +0300
+++ b/njs/njs_error.c	Fri Jul 27 17:01:52 2018 +0300
@@ -586,6 +586,18 @@ static njs_ret_t
 njs_error_prototype_to_string(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs,
     njs_index_t unused)
 {
+    if (nargs < 1 || !njs_is_object(&args[0])) {
+        njs_type_error(vm, "'this' argument is not an object");
+        return NXT_ERROR;
+    }
+
+    return njs_error_to_string(vm, &vm->retval, &args[0]);
+}
+
+
+njs_ret_t
+njs_error_to_string(njs_vm_t *vm, njs_value_t *retval, const njs_value_t *error)
+{
     size_t              size;
     u_char              *p;
     nxt_str_t           name, message;
@@ -595,16 +607,11 @@ njs_error_prototype_to_string(njs_vm_t *
 
     static const njs_value_t  default_name = njs_string("Error");
 
-    if (nargs < 1 || !njs_is_object(&args[0])) {
-        njs_type_error(vm, "'this' argument is not an object");
-        return NXT_ERROR;
-    }
-
     lhq.key_hash = NJS_NAME_HASH;
     lhq.key = nxt_string_value("name");
     lhq.proto = &njs_object_hash_proto;
 
-    prop = njs_object_property(vm, args[0].data.u.object, &lhq);
+    prop = njs_object_property(vm, error->data.u.object, &lhq);
 
     if (prop != NULL) {
         name_value = &prop->value;
@@ -618,7 +625,7 @@ njs_error_prototype_to_string(njs_vm_t *
     lhq.key_hash = NJS_MESSAGE_HASH;
     lhq.key = nxt_string_value("message");
 
-    prop = njs_object_property(vm, args[0].data.u.object, &lhq);
+    prop = njs_object_property(vm, error->data.u.object, &lhq);
 
     if (prop != NULL) {
         message_value = &prop->value;
@@ -630,18 +637,18 @@ njs_error_prototype_to_string(njs_vm_t *
     njs_string_get(message_value, &message);
 
     if (name.length == 0) {
-        vm->retval = *message_value;
+        *retval = *message_value;
         return NJS_OK;
     }
 
     if (message.length == 0) {
-        vm->retval = *name_value;
+        *retval = *name_value;
         return NJS_OK;
     }
 
     size = name.length + message.length + 2;
 
-    p = njs_string_alloc(vm, &vm->retval, size, size);
+    p = njs_string_alloc(vm, retval, size, size);
 
     if (nxt_fast_path(p != NULL)) {
         p = nxt_cpymem(p, name.start, name.length);
@@ -653,6 +660,7 @@ njs_error_prototype_to_string(njs_vm_t *
     }
 
     njs_memory_error(vm);
+
     return NJS_ERROR;
 }
 
diff -r 766fcec15744 -r 058162fce59a njs/njs_error.h
--- a/njs/njs_error.h	Tue Jul 24 19:50:02 2018 +0300
+++ b/njs/njs_error.h	Fri Jul 27 17:01:52 2018 +0300
@@ -53,6 +53,8 @@ njs_ret_t njs_uri_error_constructor(njs_
 njs_ret_t njs_memory_error_constructor(njs_vm_t *vm, njs_value_t *args,
     nxt_uint_t nargs, njs_index_t unused);
 
+njs_ret_t njs_error_to_string(njs_vm_t *vm, njs_value_t *retval,
+    const njs_value_t *error);
 
 extern const njs_object_init_t  njs_error_constructor_init;
 extern const njs_object_init_t  njs_eval_error_constructor_init;
diff -r 766fcec15744 -r 058162fce59a njs/njs_extern.c
--- a/njs/njs_extern.c	Tue Jul 24 19:50:02 2018 +0300
+++ b/njs/njs_extern.c	Fri Jul 27 17:01:52 2018 +0300
@@ -228,6 +228,60 @@ njs_vm_external_bind(njs_vm_t *vm, const
 }
 
 
+njs_array_t *
+njs_extern_keys_array(njs_vm_t *vm, const njs_extern_t *external)
+{
+    uint32_t            n, keys_length;
+    njs_ret_t           ret;
+    njs_array_t         *keys;
+    const nxt_lvlhsh_t  *hash;
+    nxt_lvlhsh_each_t   lhe;
+    const njs_extern_t  *ext;
+
+    keys_length = 0;
+
+    nxt_lvlhsh_each_init(&lhe, &njs_extern_hash_proto);
+
+    hash = &external->hash;
+
+    for ( ;; ) {
+        ext = nxt_lvlhsh_each(hash, &lhe);
+
+        if (ext == NULL) {
+            break;
+        }
+
+        keys_length++;
+    }
+
+    keys = njs_array_alloc(vm, keys_length, NJS_ARRAY_SPARE);
+    if (nxt_slow_path(keys == NULL)) {
+        return NULL;
+    }
+
+    n = 0;
+
+    nxt_lvlhsh_each_init(&lhe, &njs_extern_hash_proto);
+
+    for ( ;; ) {
+        ext = nxt_lvlhsh_each(hash, &lhe);
+
+        if (ext == NULL) {
+            break;
+        }
+
+        ret = njs_string_create(vm, &keys->start[n++], ext->name.start,
+                                ext->name.length, 0);
+
+        if (ret != NXT_OK) {
+            return NULL;
+        }
+    }
+
+    return keys;
+}
+
+
 njs_value_t *
 njs_parser_external(njs_vm_t *vm, njs_parser_t *parser)
 {
diff -r 766fcec15744 -r 058162fce59a njs/njs_extern.h
--- a/njs/njs_extern.h	Tue Jul 24 19:50:02 2018 +0300
+++ b/njs/njs_extern.h	Fri Jul 27 17:01:52 2018 +0300
@@ -38,6 +38,7 @@ typedef struct {
 } njs_extern_value_t;
 
 
+njs_array_t *njs_extern_keys_array(njs_vm_t *vm, const njs_extern_t *external);
 nxt_int_t njs_external_match_native_function(njs_vm_t *vm,
     njs_function_native_t func, nxt_str_t *name);
 
diff -r 766fcec15744 -r 058162fce59a njs/njs_json.c
--- a/njs/njs_json.c	Tue Jul 24 19:50:02 2018 +0300
+++ b/njs/njs_json.c	Fri Jul 27 17:01:52 2018 +0300
@@ -5,6 +5,9 @@
  */
 
 #include <njs_core.h>
+#include <njs_json.h>
+#include <njs_date.h>
+#include <njs_regexp.h>
 #include <stdio.h>
 #include <string.h>
 
@@ -127,16 +130,16 @@ static njs_ret_t njs_json_stringify_repl
 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);
+    njs_json_stringify_t *stringify, const njs_value_t *value);
 static njs_json_state_t *njs_json_pop_stringify_state(
     njs_json_stringify_t *stringify);
 
 static nxt_int_t njs_json_append_value(njs_json_stringify_t *stringify,
-    njs_value_t *value);
+    const njs_value_t *value);
 static nxt_int_t njs_json_append_string(njs_json_stringify_t *stringify,
-    njs_value_t *value);
+    const njs_value_t *value, char quote);
 static nxt_int_t njs_json_append_number(njs_json_stringify_t *stringify,
-    njs_value_t *value);
+    const njs_value_t *value);
 
 static njs_value_t *njs_json_wrap_value(njs_vm_t *vm, njs_value_t *value);
 
@@ -1162,7 +1165,7 @@ njs_json_parse_exception(njs_json_parse_
     }                                                                         \
                                                                               \
     state->written = 1;                                                       \
-    njs_json_append_string(stringify, key);                                   \
+    njs_json_append_string(stringify, key, '\"');                             \
     njs_json_stringify_append(":", 1);                                        \
     if (stringify->space.length != 0) {                                       \
         njs_json_stringify_append(" ", 1);                                    \
@@ -1621,7 +1624,7 @@ njs_json_stringify_array(njs_vm_t *vm, n
 
 static njs_json_state_t *
 njs_json_push_stringify_state(njs_vm_t *vm, njs_json_stringify_t *stringify,
-    njs_value_t *value)
+    const njs_value_t *value)
 {
     njs_json_state_t  *state;
 
@@ -1654,7 +1657,13 @@ njs_json_push_stringify_state(njs_vm_t *
             state->keys = stringify->replacer.data.u.array;
 
         } else {
-            state->keys = njs_object_keys_array(vm, value);
+            if (njs_is_external(value)) {
+                state->keys = njs_extern_keys_array(vm, value->external.proto);
+
+            } else {
+                state->keys = njs_object_keys_array(vm, value);
+            }
+
             if (state->keys == NULL) {
                 return NULL;
             }
@@ -1681,7 +1690,7 @@ njs_json_pop_stringify_state(njs_json_st
 
 
 static nxt_int_t
-njs_json_append_value(njs_json_stringify_t *stringify, njs_value_t *value)
+njs_json_append_value(njs_json_stringify_t *stringify, const njs_value_t *value)
 {
     switch (value->type) {
     case NJS_OBJECT_STRING:
@@ -1689,7 +1698,7 @@ njs_json_append_value(njs_json_stringify
         /* Fall through. */
 
     case NJS_STRING:
-        return njs_json_append_string(stringify, value);
+        return njs_json_append_string(stringify, value, '\"');
 
     case NJS_OBJECT_NUMBER:
         value = &value->data.u.object_value->value;
@@ -1724,7 +1733,8 @@ njs_json_append_value(njs_json_stringify
 
 
 static nxt_int_t
-njs_json_append_string(njs_json_stringify_t *stringify, njs_value_t *value)
+njs_json_append_string(njs_json_stringify_t *stringify,
+    const njs_value_t *value, char quote)
 {
     u_char             c, *dst, *dst_end;
     size_t             length;
@@ -1734,7 +1744,7 @@ njs_json_append_string(njs_json_stringif
     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);
+    (void) njs_string_prop(&str, (njs_value_t *) value);
 
     p = str.start;
     end = p + str.size;
@@ -1747,11 +1757,14 @@ njs_json_append_string(njs_json_stringif
 
     dst_end = dst + 64;
 
-    *dst++ = '\"';
+    *dst++ = quote;
 
     while (p < end) {
 
-        if (*p < ' ' || *p == '\"' || *p == '\\') {
+        if (*p < ' '
+            || *p == '\\'
+            || (*p == '\"' && quote == '\"'))
+        {
             c = (u_char) *p++;
             *dst++ = '\\';
 
@@ -1793,7 +1806,7 @@ njs_json_append_string(njs_json_stringif
          */
 
         while (p < end && (dst_end - dst) > 6) {
-            if (*p < ' ' || *p == '\"' || *p == '\\') {
+            if (*p < ' ' || (*p == '\"' && quote == '\"') || *p == '\\') {
                 break;
             }
 
@@ -1820,14 +1833,15 @@ njs_json_append_string(njs_json_stringif
     }
 
     njs_json_buf_written(stringify, dst - stringify->last->pos);
-    njs_json_buf_append(stringify, "\"", 1);
+    njs_json_buf_append(stringify, &quote, 1);
 
     return NXT_OK;
 }
 
 
 static nxt_int_t
-njs_json_append_number(njs_json_stringify_t *stringify, njs_value_t *value)
+njs_json_append_number(njs_json_stringify_t *stringify,
+    const njs_value_t *value)
 {
     u_char  *p;
     size_t  size;
@@ -2037,3 +2051,442 @@ const njs_object_init_t  njs_json_object
     njs_json_object_properties,
     nxt_nitems(njs_json_object_properties),
 };
+
+
+#define njs_dump(str)                                                         \
+    ret = njs_json_buf_append(stringify, str, nxt_length(str));               \
+    if (nxt_slow_path(ret != NXT_OK)) {                                       \
+        goto memory_error;                                                    \
+    }
+
+
+#define njs_dump_item(str)                                                    \
+    if (written) {                                                            \
+        njs_json_buf_append(stringify, ",", 1);                               \
+    }                                                                         \
+                                                                              \
+    written = 1;                                                              \
+    ret = njs_json_buf_append(stringify, str, nxt_length(str));               \
+    if (nxt_slow_path(ret != NXT_OK)) {                                       \
+        goto memory_error;                                                    \
+    }
+
+
+static nxt_int_t
+njs_dump_value(njs_json_stringify_t *stringify, const njs_value_t *value)
+{
+    size_t              len;
+    njs_ret_t           ret;
+    nxt_str_t           str;
+    nxt_uint_t          written;
+    njs_value_t         str_val;
+    const njs_extern_t  *ext_proto;
+    char                buf[32];
+
+    njs_ret_t           (*to_string)(njs_vm_t *, njs_value_t *,
+                                     const njs_value_t *);
+
+    switch (value->type) {
+    case NJS_OBJECT_STRING:
+        value = &value->data.u.object_value->value;
+
+        njs_string_get(value, &str);
+
+        njs_dump("[String: ");
+        njs_json_append_string(stringify, value, '\'');
+        njs_dump("]")
+        break;
+
+    case NJS_STRING:
+        njs_string_get(value, &str);
+        return njs_json_append_string(stringify, value, '\'');
+
+    case NJS_OBJECT_NUMBER:
+        value = &value->data.u.object_value->value;
+
+        ret = njs_number_to_string(stringify->vm, &str_val, value);
+        if (nxt_slow_path(ret != NXT_OK)) {
+            return NXT_ERROR;
+        }
+
+        njs_string_get(&str_val, &str);
+
+        njs_dump("[Number: ");
+        njs_json_buf_append(stringify, (char *) str.start, str.length);
+        njs_dump("]")
+        break;
+
+    case NJS_OBJECT_BOOLEAN:
+        value = &value->data.u.object_value->value;
+
+        if (njs_is_true(value)) {
+            njs_dump("[Boolean: true]");
+
+        } else {
+            njs_dump("[Boolean: false]");
+        }
+
+        break;
+
+    case NJS_BOOLEAN:
+        if (njs_is_true(value)) {
+            njs_dump("true");
+
+        } else {
+            njs_dump("false");
+        }
+
+        break;
+
+    case NJS_VOID:
+        njs_dump("undefined");
+        break;
+
+    case NJS_NULL:
+        njs_dump("null");
+        break;
+
+    case NJS_INVALID:
+        njs_dump("<empty>");
+        break;
+
+    case NJS_FUNCTION:
+        if (value->data.u.function->native) {
+            njs_dump("[Function: native]");
+
+        } else {
+            njs_dump("[Function]");
+        }
+
+        break;
+
+    case NJS_EXTERNAL:
+        ext_proto = value->external.proto;
+
+        written = 0;
+        njs_dump_item("{type:");
+
+        switch (ext_proto->type) {
+        case NJS_EXTERN_PROPERTY:
+            njs_dump("\"property\"");
+            break;
+        case NJS_EXTERN_METHOD:
+            njs_dump("\"method\"");
+            break;
+        case NJS_EXTERN_OBJECT:
+            njs_dump("\"object\"");
+            break;
+        case NJS_EXTERN_CASELESS_OBJECT:
+            njs_dump("\"caseless_object\"");
+            break;
+        }
+
+        njs_dump_item("props:[");
+        written = 0;
+
+        if (ext_proto->get != NULL) {
+            njs_dump_item("\"getter\"");
+        }
+
+        if (ext_proto->set != NULL) {
+            njs_dump_item("\"setter\"");
+        }
+
+        if (ext_proto->function != NULL) {
+            njs_dump_item("\"method\"");
+        }
+
+        if (ext_proto->find != NULL) {
+            njs_dump_item("\"find\"");
+        }
+
+        if (ext_proto->foreach != NULL) {
+            njs_dump_item("\"foreach\"");
+        }
+
+        if (ext_proto->next != NULL) {
+            njs_dump_item("\"next\"");
+        }
+
+        return njs_json_buf_append(stringify, "]}", 2);
+
+    case NJS_NUMBER:
+    case NJS_REGEXP:
+    case NJS_DATE:
+    case NJS_OBJECT_ERROR:
+    case NJS_OBJECT_EVAL_ERROR:
+    case NJS_OBJECT_INTERNAL_ERROR:
+    case NJS_OBJECT_RANGE_ERROR:
+    case NJS_OBJECT_REF_ERROR:
+    case NJS_OBJECT_SYNTAX_ERROR:
+    case NJS_OBJECT_TYPE_ERROR:
+    case NJS_OBJECT_URI_ERROR:
+
+        switch (value->type) {
+        case NJS_NUMBER:
+            to_string = njs_number_to_string;
+            break;
+
+        case NJS_REGEXP:
+            to_string = njs_regexp_to_string;
+            break;
+
+        case NJS_DATE:
+            to_string = njs_date_to_string;
+            break;
+
+        default:
+            to_string = njs_error_to_string;
+        }
+
+        ret = to_string(stringify->vm, &str_val, value);
+        if (nxt_slow_path(ret != NXT_OK)) {
+            return NXT_ERROR;
+        }
+
+        njs_string_get(&str_val, &str);
+
+        return njs_json_buf_append(stringify, (char *) str.start, str.length);
+
+    default:
+        len = snprintf(buf, sizeof(buf), "[Unknown value type:%d]",
+                       value->type);
+        return njs_json_buf_append(stringify, buf, len);
+    }
+
+    return ret;
+
+memory_error:
+
+    njs_memory_error(stringify->vm);
+
+    return NXT_ERROR;
+}
+
+
+#define njs_dump_is_object(value)                                             \
+    (((value)->type == NJS_OBJECT)                                            \
+     || ((value)->type == NJS_ARRAY)                                          \
+     || ((value)->type == NJS_OBJECT_VALUE)                                   \
+     || ((value)->type == NJS_EXTERNAL                                        \
+         && !nxt_lvlhsh_is_empty(&(value)->external.proto->hash)))
+
+
+#define njs_dump_append_value(value)                                          \
+    state->written = 1;                                                       \
+    ret = njs_dump_value(stringify, value);                                   \
+    if (nxt_slow_path(ret != NXT_OK)) {                                       \
+        if (ret == NXT_DECLINED) {                                            \
+            goto exception;                                                   \
+        }                                                                     \
+                                                                              \
+        goto memory_error;                                                    \
+    }
+
+
+njs_ret_t
+njs_vm_value_dump(njs_vm_t *vm, nxt_str_t *retval, const njs_value_t *value,
+    nxt_uint_t indent)
+{
+    nxt_int_t             i;
+    njs_ret_t             ret;
+    nxt_str_t             str;
+    njs_value_t           *key, *val, ext_val;
+    njs_json_state_t      *state;
+    njs_object_prop_t     *prop;
+    nxt_lvlhsh_query_t    lhq;
+    njs_json_stringify_t  *stringify;
+
+    if (njs_vm_backtrace(vm) != NULL) {
+        goto exception;
+    }
+
+    stringify = nxt_mem_cache_alloc(vm->mem_cache_pool,
+                                    sizeof(njs_json_stringify_t));
+
+    if (nxt_slow_path(stringify == NULL)) {
+        goto memory_error;
+    }
+
+    stringify->vm = vm;
+    stringify->pool = vm->mem_cache_pool;
+    stringify->nodes = NULL;
+    stringify->last = NULL;
+
+    if (!njs_dump_is_object(value)) {
+        ret = njs_dump_value(stringify, value);
+        if (nxt_slow_path(ret != NXT_OK)) {
+            goto memory_error;
+        }
+
+        goto done;
+    }
+
+    stringify->space.length = indent;
+    stringify->space.start = nxt_mem_cache_alloc(vm->mem_cache_pool, indent);
+    if (nxt_slow_path(stringify->space.start == NULL)) {
+        goto memory_error;
+    }
+
+    memset(stringify->space.start, ' ', indent);
+
+    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;
+    }
+
+    if (njs_json_push_stringify_state(vm, stringify, value) == NULL) {
+        goto memory_error;
+    }
+
+    state = stringify->state;
+
+    for ( ;; ) {
+        switch (state->type) {
+        case NJS_JSON_OBJECT_START:
+            njs_json_stringify_append("{", 1);
+            njs_json_stringify_indent(stringify->stack.items + 1);
+            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);
+                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);
+
+            if (njs_is_external(&state->value)) {
+                lhq.proto = &njs_extern_hash_proto;
+
+                ret = nxt_lvlhsh_find(&state->value.external.proto->hash, &lhq);
+                if (nxt_slow_path(ret == NXT_DECLINED)) {
+                    break;
+                }
+
+                ext_val.type = NJS_EXTERNAL;
+                ext_val.data.truth = 1;
+                ext_val.external.proto = lhq.value;
+
+                val = &ext_val;
+
+            } else {
+                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;
+                val = &prop->value;
+
+                if (!prop->enumerable) {
+                    break;
+                }
+            }
+
+            if (state->written) {
+                njs_json_stringify_append(",", 1);
+                njs_json_stringify_indent(stringify->stack.items + 1);
+            }
+
+            state->written = 1;
+            njs_json_stringify_append((char *) lhq.key.start, lhq.key.length);
+            njs_json_stringify_append(":", 1);
+            if (stringify->space.length != 0) {
+                njs_json_stringify_append(" ", 1);
+            }
+
+            if (njs_dump_is_object(val)) {
+                state = njs_json_push_stringify_state(vm, stringify, val);
+                if (state == NULL) {
+                    goto exception;
+                }
+
+                break;
+            }
+
+            njs_dump_append_value(val);
+
+            break;
+
+        case NJS_JSON_ARRAY_START:
+            njs_json_stringify_append("[", 1);
+            njs_json_stringify_indent(stringify->stack.items + 1);
+            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);
+                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 + 1);
+            }
+
+            val = &state->value.data.u.array->start[state->index++];
+
+            if (njs_dump_is_object(val)) {
+                state = njs_json_push_stringify_state(vm, stringify, val);
+                if (state == NULL) {
+                    goto exception;
+                }
+
+                break;
+            }
+
+            njs_dump_append_value(val);
+
+            break;
+
+        default:
+            nxt_unreachable();
+        }
+    }
+
+done:
+
+    ret = njs_json_buf_pullup(stringify, &str);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        goto memory_error;
+    }
+
+    *retval = str;
+
+    return NXT_OK;
+
+memory_error:
+
+    njs_memory_error(vm);
+
+exception:
+
+    njs_vm_value_to_ext_string(vm, retval, &vm->retval, 1);
+
+    return NXT_OK;
+}
diff -r 766fcec15744 -r 058162fce59a njs/njs_regexp.c
--- a/njs/njs_regexp.c	Tue Jul 24 19:50:02 2018 +0300
+++ b/njs/njs_regexp.c	Fri Jul 27 17:01:52 2018 +0300
@@ -527,22 +527,8 @@ static njs_ret_t
 njs_regexp_prototype_to_string(njs_vm_t *vm, njs_value_t *args,
     nxt_uint_t nargs, njs_index_t unused)
 {
-    u_char                *source;
-    int32_t               length;
-    uint32_t              size;
-    njs_value_t           *value;
-    njs_regexp_pattern_t  *pattern;
-
-    value = &args[0];
-
-    if (njs_is_regexp(value)) {
-        pattern = value->data.u.regexp->pattern;
-        source = pattern->source;
-
-        size = strlen((char *) source);
-        length = nxt_utf8_length(source, size);
-
-        return njs_regexp_string_create(vm, &vm->retval, source, size, length);
+    if (njs_is_regexp(&args[0])) {
+        return njs_regexp_to_string(vm, &vm->retval, &args[0]);
     }
 
     njs_type_error(vm, "'this' argument is not a regexp");
@@ -551,6 +537,25 @@ njs_regexp_prototype_to_string(njs_vm_t 
 }
 
 
+njs_ret_t
+njs_regexp_to_string(njs_vm_t *vm, njs_value_t *retval,
+    const njs_value_t *value)
+{
+    u_char                *source;
+    int32_t               length;
+    uint32_t              size;
+    njs_regexp_pattern_t  *pattern;
+
+    pattern = value->data.u.regexp->pattern;
+    source = pattern->source;
+
+    size = strlen((char *) source);
+    length = nxt_utf8_length(source, size);
+
+    return njs_regexp_string_create(vm, retval, source, size, length);
+}
+
+
 static njs_ret_t
 njs_regexp_prototype_test(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs,
     njs_index_t unused)
diff -r 766fcec15744 -r 058162fce59a njs/njs_regexp.h
--- a/njs/njs_regexp.h	Tue Jul 24 19:50:02 2018 +0300
+++ b/njs/njs_regexp.h	Fri Jul 27 17:01:52 2018 +0300
@@ -31,6 +31,8 @@ njs_regexp_t *njs_regexp_alloc(njs_vm_t 
 njs_ret_t njs_regexp_prototype_exec(njs_vm_t *vm, njs_value_t *args,
     nxt_uint_t nargs, njs_index_t unused);
 
+njs_ret_t njs_regexp_to_string(njs_vm_t *vm, njs_value_t *retval,
+    const njs_value_t *regexp);
 
 extern const njs_object_init_t  njs_regexp_constructor_init;
 extern const njs_object_init_t  njs_regexp_prototype_init;
diff -r 766fcec15744 -r 058162fce59a njs/njs_shell.c
--- a/njs/njs_shell.c	Tue Jul 24 19:50:02 2018 +0300
+++ b/njs/njs_shell.c	Fri Jul 27 17:01:52 2018 +0300
@@ -58,6 +58,8 @@ static char *njs_completion_generator(co
 
 static njs_ret_t njs_ext_console_log(njs_vm_t *vm, njs_value_t *args,
     nxt_uint_t nargs, njs_index_t unused);
+static njs_ret_t njs_ext_console_dump(njs_vm_t *vm, njs_value_t *args,
+    nxt_uint_t nargs, njs_index_t unused);
 static njs_ret_t njs_ext_console_help(njs_vm_t *vm, njs_value_t *args,
     nxt_uint_t nargs, njs_index_t unused);
 
@@ -76,6 +78,18 @@ static njs_external_t  njs_ext_console[]
       njs_ext_console_log,
       0 },
 
+    { nxt_string("dump"),
+      NJS_EXTERN_METHOD,
+      NULL,
+      0,
+      NULL,
+      NULL,
+      NULL,
+      NULL,
+      NULL,
+      njs_ext_console_dump,
+      0 },
+
     { nxt_string("help"),
       NJS_EXTERN_METHOD,
       NULL,
@@ -431,7 +445,7 @@ njs_process_script(njs_vm_t *vm, njs_opt
         ret = njs_vm_run(vm);
     }
 
-    if (njs_vm_retval_to_ext_string(vm, out) != NXT_OK) {
+    if (njs_vm_value_dump(vm, out, njs_vm_retval(vm), 1) != NXT_OK) {
         *out = nxt_string_value("failed to get retval from VM");
         return NXT_ERROR;
     }
@@ -625,7 +639,38 @@ njs_ext_console_log(njs_vm_t *vm, njs_va
     n = 1;
 
     while (n < nargs) {
-        if (njs_vm_value_to_ext_string(vm, &msg, njs_argument(args, n), 0)
+        if (njs_vm_value_dump(vm, &msg, njs_argument(args, n), 0)
+            == NJS_ERROR)
+        {
+            return NJS_ERROR;
+        }
+
+        printf("%s%.*s", (n != 1) ? " " : "", (int) msg.length, msg.start);
+
+        n++;
+    }
+
+    if (nargs > 1) {
+        printf("\n");
+    }
+
+    vm->retval = njs_value_void;
+
+    return NJS_OK;
+}
+
+
+static njs_ret_t
+njs_ext_console_dump(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs,
+    njs_index_t unused)
+{
+    nxt_str_t   msg;


More information about the nginx-devel mailing list