]> git.kaiwu.me - njs.git/commitdiff
Added the pretty string representation for values.
authorDmitry Volyntsev <xeioex@nginx.com>
Fri, 27 Jul 2018 14:01:52 +0000 (17:01 +0300)
committerDmitry Volyntsev <xeioex@nginx.com>
Fri, 27 Jul 2018 14:01:52 +0000 (17:01 +0300)
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.

14 files changed:
njs/njs.h
njs/njs_builtin.c
njs/njs_date.c
njs/njs_date.h
njs/njs_error.c
njs/njs_error.h
njs/njs_extern.c
njs/njs_extern.h
njs/njs_json.c
njs/njs_regexp.c
njs/njs_regexp.h
njs/njs_shell.c
njs/test/njs_expect_test.exp
njs/test/njs_unit_test.c

index dd196928d46049b485dad6fa607fbe500a9f18f1..aa0d6fb571952ae752e9779cc20ae9661b2e5b8c 100644 (file)
--- a/njs/njs.h
+++ b/njs/njs.h
@@ -217,6 +217,8 @@ NXT_EXPORT nxt_int_t njs_value_is_string(njs_value_t *value);
 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);
 
index 5074b22d4b3414a5b0127992a1e410613025b5b2..3de147eb73f99a41543451fa70b5ac50ac88a09a 100644 (file)
@@ -1069,6 +1069,28 @@ njs_builtin_match_native_function(njs_vm_t *vm, njs_function_t *function,
 }
 
 
+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_object_properties[] =
         .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),
+    },
 };
 
 
index 9d5437e496c984935fff3c263e8e7d34c0a97306..dc5bef478af50e205a703025f484f59cccc77ecb 100644 (file)
@@ -1018,6 +1018,13 @@ njs_date_prototype_to_utc_string(njs_vm_t *vm, njs_value_t *args,
 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;
@@ -1026,7 +1033,7 @@ njs_date_prototype_to_iso_string(njs_vm_t *vm, njs_value_t *args,
     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_t *vm, njs_value_t *args,
                         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);
index 57f8ed92264c49fd4f677009eeb2a96d426096c7..38be5a3a426fdbfe1b84062b411d7eebb10f43d1 100644 (file)
@@ -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;
index 19fd6c5e2f324be13787564f5ff06c5b11a9b232..2b426f8fe0dc7a880272c436d97aacb44a0de938 100644 (file)
@@ -585,6 +585,18 @@ njs_error_prototype_value_of(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs,
 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;
@@ -595,16 +607,11 @@ njs_error_prototype_to_string(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs,
 
     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 *vm, njs_value_t *args, nxt_uint_t nargs,
     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 *vm, njs_value_t *args, nxt_uint_t nargs,
     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 *vm, njs_value_t *args, nxt_uint_t nargs,
     }
 
     njs_memory_error(vm);
+
     return NJS_ERROR;
 }
 
index 93145357a95f19a864a2e4626ac82c04bfe2b497..0cd724fadc189435ee6a5ba279f1e9be124e9c1d 100644 (file)
@@ -53,6 +53,8 @@ njs_ret_t njs_uri_error_constructor(njs_vm_t *vm, njs_value_t *args,
 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;
index 29ff331510929030b3c6e6d4899dcce1b69fadf5..4ed6b93d70ac91f9556671f1f639ca6154774de9 100644 (file)
@@ -228,6 +228,60 @@ njs_vm_external_bind(njs_vm_t *vm, const nxt_str_t *var_name,
 }
 
 
+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)
 {
index 1df7b73f1ef1a6d1f2b3627573d1befa60777654..6cbb99176a494cbfd9cf012e55748038b2c4e080 100644 (file)
@@ -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);
 
index a45741feb397dd0a6b84e52a3d0b11b8d3c25fb5..616a4e59b0adcb68d6f679386b6ac355c315ed66 100644 (file)
@@ -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_replacer(njs_vm_t *vm,
 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_ctx_t *ctx, const char* msg,
     }                                                                         \
                                                                               \
     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, 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)
+    const njs_value_t *value)
 {
     njs_json_state_t  *state;
 
@@ -1654,7 +1657,13 @@ njs_json_push_stringify_state(njs_vm_t *vm, njs_json_stringify_t *stringify,
             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_stringify_t *stringify)
 
 
 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_t *stringify, njs_value_t *value)
         /* 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_t *stringify, njs_value_t *value)
 
 
 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_stringify_t *stringify, njs_value_t *value)
     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_stringify_t *stringify, njs_value_t *value)
 
     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_stringify_t *stringify, njs_value_t *value)
          */
 
         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_stringify_t *stringify, njs_value_t *value)
     }
 
     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_init = {
     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;
+}
index 9fa044ee92437c45feefbe9709fc2d68c7218844..5294ba0a09102f66cbb1a92ee6e78ddb017bde32 100644 (file)
@@ -526,28 +526,33 @@ njs_regexp_prototype_source(njs_vm_t *vm, njs_value_t *value,
 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)
+{
+    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");
+
+    return NXT_ERROR;
+}
+
+
+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_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);
-    }
+    pattern = value->data.u.regexp->pattern;
+    source = pattern->source;
 
-    njs_type_error(vm, "'this' argument is not a regexp");
+    size = strlen((char *) source);
+    length = nxt_utf8_length(source, size);
 
-    return NXT_ERROR;
+    return njs_regexp_string_create(vm, retval, source, size, length);
 }
 
 
index 85d3e3e822d81822b9394acb873e5ca7a453c6c3..8febede4af5cfee7bf5f0d99bb7dcf143c44c123 100644 (file)
@@ -31,6 +31,8 @@ njs_regexp_t *njs_regexp_alloc(njs_vm_t *vm, njs_regexp_pattern_t *pattern);
 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;
index 8053ac21c22a4ec2765e8e9871cd337b32bb3145..5b66bdaaa50b09723d86aca605755bc12f92921e 100644 (file)
@@ -58,6 +58,8 @@ static char *njs_completion_generator(const char *text, int state);
 
 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_opts_t *opts, const nxt_str_t *script,
         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_value_t *args, nxt_uint_t nargs,
     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;
+    nxt_uint_t  n;
+
+    n = 1;
+
+    while (n < nargs) {
+        if (njs_vm_value_dump(vm, &msg, njs_argument(args, n), 1)
             == NJS_ERROR)
         {
             return NJS_ERROR;
index 5247c7c6f2c446311febe4ac65de824e64b36b05..075fb419330ddb14d74ddba55a28f3ac3f7071b3 100644 (file)
@@ -166,7 +166,13 @@ njs_test {
     {"console.log(1)\r\n"
      "console.log(1)\r\n1\r\nundefined\r\n>> "}
     {"console.log(1, 'a')\r\n"
-     "console.log(1, 'a')\r\n1 a\r\nundefined\r\n>> "}
+     "console.log(1, 'a')\r\n1 'a'\r\nundefined\r\n>> "}
+    {"console.dump()\r\n"
+     "console.dump()\r\nundefined\r\n>> "}
+    {"console.dump(1)\r\n"
+     "console.dump(1)\r\n1\r\nundefined\r\n>> "}
+    {"console.dump(1, 'a')\r\n"
+     "console.dump(1, 'a')\r\n1 'a'\r\nundefined\r\n>> "}
     {"console.help()\r\n"
      "console.help()\r\nVM built-in objects:"}
 }
@@ -178,21 +184,50 @@ njs_test {
 
 njs_test {
     {"var print = console.log.bind(console); print(1, 'a', [1, 2])\r\n"
-     "var print = console.log.bind(console); print(1, 'a', \\\[1, 2])\r\n1 a 1,2\r\nundefined\r\n>> "}
+     "1 'a' \\\[1,2]\r\nundefined\r\n>> "}
+    {"var print = console.dump.bind(console); print(1, 'a', [1, 2])\r\n"
+     "1 'a' \\\[\r\n 1,\r\n 2\r\n]\r\nundefined\r\n>> "}
 }
 
 # Backtraces for external objects
 njs_test {
-    {"console.log(console)\r\n"
-     "console.log(console)\r\nTypeError:*at console.log (native)"}
+    {"console.log(console.a.a)\r\n"
+     "console.log(console.a.a)\r\nTypeError:*at console.log (native)"}
 }
 
-# Exception in njs_vm_retval_to_ext_string()
+# dumper
 njs_test {
-    {"var o = { toString: function() { return [1] } }\r\n"
+    {"var o = {toString: function(){}, log: console.log}\r\n"
      "undefined\r\n>> "}
     {"o\r\n"
-     "TypeError: cannot evaluate an object's value"}
+     "o\r\n{\r\n toString: \\\[Function],\r\n log: \\\[Function: native]\r\n}"}
+}
+
+njs_test {
+    {"[1, new Number(2), 'a', new String('αβZγ'), true, new Boolean(false)]\r\n"
+     "\\\[\r\n 1,\r\n \\\[Number: 2],\r\n 'a',\r\n \\\[String: 'αβZγ'],\r\n true,\r\n \\\[Boolean: false]\r\n]"}
+}
+
+njs_test {
+    {"[undefined,,null]\r\n"
+     "\\\[\r\n undefined,\r\n <empty>,\r\n null\r\n]"}
+}
+
+njs_test {
+    {"[InternalError(),TypeError('msg'), new RegExp(), /^undef$/m, new Date(0)]\r\n"
+     "\\\[\r\n InternalError,\r\n TypeError: msg,\r\n /(?:)/,\r\n /^undef$/m,\r\n 1970-01-01T00:00:00.000Z\r\n]"}
+}
+
+# dumper excapes special characters as JSON.stringify()
+# except '\"'
+njs_test {
+    {"\"\\r\\0\\\"\"\r\n"
+     "'\\\\r\\\\u0000\"'"}
+}
+
+njs_test {
+    {"[{a:1}]\r\n"
+     "\r\n\\\[\r\n {\r\n  a: 1\r\n }\r\n]"}
 }
 
 # Backtraces are reset between invocations
@@ -210,12 +245,17 @@ njs_test {
      "undefined"}
 }
 
+njs_test {
+    {"(function() { throw 'test' })()\r\n"
+     "test\r\n    at anonymous (:1)"}
+}
+
 # Non-ASCII characters
 njs_test {
     {"'絵文字'\r\n"
-     "絵文字"}
+     "'絵文字'"}
     {"var v = 'абвгдеёжзийкл';v[10]\r\n"
-     "й"}
+     "'й'"}
 }
 
 # require('fs')
@@ -237,21 +277,21 @@ njs_test {
     {"var fs = require('fs')\r\n"
      "undefined\r\n>> "}
     {"fs.readFile('njs_test_file', 'utf8', function (e, data) {console.log(data[2]+data.length)})\r\n"
-     "Z4\r\nundefined\r\n>> "}
+     "'Z4'\r\nundefined\r\n>> "}
 }
 
 njs_test {
     {"var fs = require('fs')\r\n"
      "undefined\r\n>> "}
     {"fs.readFile('njs_test_file', function (e, data) {console.log(data[4]+data.length)})\r\n"
-     "Z7\r\nundefined\r\n>> "}
+     "'Z7'\r\nundefined\r\n>> "}
 }
 
 njs_test {
     {"var fs = require('fs')\r\n"
      "undefined\r\n>> "}
     {"fs.readFile('njs_test_file', {encoding:'utf8',flag:'r+'}, function (e, data) {console.log(data)})\r\n"
-     "αβZγ\r\nundefined\r\n>> "}
+     "'αβZγ'\r\nundefined\r\n>> "}
 }
 
 exec rm -fr njs_unknown_path
@@ -260,7 +300,7 @@ njs_test {
     {"var fs = require('fs'); \r\n"
      "undefined\r\n>> "}
     {"fs.readFile('njs_unknown_path', 'utf8', function (e) {console.log(JSON.stringify(e))})\r\n"
-     "{\"errno\":2,\"path\":\"njs_unknown_path\",\"syscall\":\"open\"}\r\nundefined\r\n>> "}
+     "'{\"errno\":2,\"path\":\"njs_unknown_path\",\"syscall\":\"open\"}'\r\nundefined\r\n>> "}
 }
 
 njs_test {
@@ -276,35 +316,35 @@ njs_test {
     {"var fs = require('fs')\r\n"
      "undefined\r\n>> "}
     {"fs.readFileSync('njs_test_file').toString('base64')\r\n"
-     "zrHOslrOsw==\r\n>> "}
+     "'zrHOslrOsw=='\r\n>> "}
 }
 
 njs_test {
     {"var fs = require('fs')\r\n"
      "undefined\r\n>> "}
     {"fs.readFileSync('njs_test_file', 'utf8')[2]\r\n"
-     "Z\r\n>> "}
+     "'Z'\r\n>> "}
 }
 
 njs_test {
     {"var fs = require('fs')\r\n"
      "undefined\r\n>> "}
     {"fs.readFileSync('njs_test_file')[4]\r\n"
-     "Z\r\n>> "}
+     "'Z'\r\n>> "}
 }
 
 njs_test {
     {"var fs = require('fs')\r\n"
      "undefined\r\n>> "}
     {"fs.readFileSync('njs_test_file', {encoding:'utf8',flag:'r+'})\r\n"
-     "αβZγ\r\n>> "}
+     "'αβZγ'\r\n>> "}
 }
 
 njs_test {
     {"var fs = require('fs'); \r\n"
      "undefined\r\n>> "}
     {"try { fs.readFileSync('njs_unknown_path')} catch (e) {console.log(JSON.stringify(e))}\r\n"
-     "{\"errno\":2,\"path\":\"njs_unknown_path\",\"syscall\":\"open\"}\r\nundefined\r\n>> "}
+     "'{\"errno\":2,\"path\":\"njs_unknown_path\",\"syscall\":\"open\"}'\r\nundefined\r\n>> "}
 }
 
 njs_test {
@@ -332,21 +372,21 @@ njs_test {
     {"function h1(e) {if (e) {throw e}; console.log(fs.readFileSync('njs_test_file2'))}\r\n"
      "undefined\r\n>> "}
     {"fs.writeFile('njs_test_file2', 'ABC', h1)\r\n"
-     "ABC\r\nundefined\r\n>> "}
+     "'ABC'\r\nundefined\r\n>> "}
 }
 
 njs_test {
     {"var fs = require('fs')\r\n"
      "undefined\r\n>> "}
     {"fs.writeFile('njs_test_file2', 'ABC', 'utf8', function (e) { if (e) {throw e}; console.log(fs.readFileSync('njs_test_file2'))})\r\n"
-     "ABC\r\nundefined\r\n>> "}
+     "'ABC'\r\nundefined\r\n>> "}
 }
 
 njs_test {
     {"var fs = require('fs')\r\n"
      "undefined\r\n>> "}
     {"fs.writeFile('njs_test_file2', 'ABC', {encoding:'utf8', mode:0o666}, function (e) { if (e) {throw e}; console.log(fs.readFileSync('njs_test_file2'))})\r\n"
-     "ABC\r\nundefined\r\n>> "}
+     "'ABC'\r\nundefined\r\n>> "}
 }
 
 exec rm -fr njs_wo_file
@@ -362,7 +402,7 @@ njs_test {
     {"var fs = require('fs')\r\n"
      "undefined\r\n>> "}
     {"fs.writeFile('/invalid_path', 'ABC', function (e) { console.log(JSON.stringify(e))})\r\n"
-     "{\"errno\":13,\"path\":\"/invalid_path\",\"syscall\":\"open\"}\r\nundefined\r\n>> "}
+     "'{\"errno\":13,\"path\":\"/invalid_path\",\"syscall\":\"open\"}'\r\nundefined\r\n>> "}
 }
 
 # require('fs').writeFileSync()
@@ -375,7 +415,7 @@ njs_test {
     {"fs.writeFileSync('njs_test_file2', 'ABC')\r\n"
      "undefined\r\n>> "}
     {"fs.readFileSync('njs_test_file2')\r\n"
-     "ABC\r\n>> "}
+     "'ABC'\r\n>> "}
 }
 
 njs_test {
@@ -384,7 +424,7 @@ njs_test {
     {"fs.writeFileSync('njs_test_file2', 'ABC', 'utf8')\r\n"
      "undefined\r\n>> "}
     {"fs.readFileSync('njs_test_file2')\r\n"
-     "ABC\r\n>> "}
+     "'ABC'\r\n>> "}
 }
 
 njs_test {
@@ -395,7 +435,7 @@ njs_test {
     {"fs.writeFileSync('njs_test_file2', 'ABC')\r\n"
      "undefined\r\n>> "}
     {"fs.readFileSync('njs_test_file2')\r\n"
-     "ABC\r\n>> "}
+     "'ABC'\r\n>> "}
 }
 
 njs_test {
@@ -404,7 +444,7 @@ njs_test {
     {"fs.writeFileSync('njs_test_file2', 'ABC', {encoding:'utf8', mode:0o666})\r\n"
      "undefined\r\n>> "}
     {"fs.readFileSync('njs_test_file2')\r\n"
-     "ABC\r\n>> "}
+     "'ABC'\r\n>> "}
 }
 
 exec rm -fr njs_wo_file
@@ -428,7 +468,7 @@ njs_test {
     {"function h2(e) {fs.appendFile('njs_test_file2', 'ABC', h1)}\r\n"
      "undefined\r\n>> "}
     {"fs.appendFile('njs_test_file2', 'ABC', h2)\r\n"
-     "ABCABC\r\nundefined\r\n>> "}
+     "'ABCABC'\r\nundefined\r\n>> "}
 }
 
 # require('fs').appendFileSync()
@@ -443,5 +483,5 @@ njs_test {
     {"fs.appendFileSync('njs_test_file2', 'ABC')\r\n"
      "undefined\r\n>> "}
     {"fs.readFileSync('njs_test_file2')\r\n"
-     "ABCABC\r\n>> "}
+     "'ABCABC'\r\n>> "}
 }
index 04c4817512504121b51d5d079155a6605d95fbfa..c78fc3b61dc7e5b4dcfed6efb9d2cc9bc924259c 100644 (file)
@@ -9258,6 +9258,17 @@ static njs_unit_test_t  njs_test[] =
     { nxt_string("var a = {}; a.a = a; JSON.stringify(a)"),
       nxt_string("TypeError: Nested too deep or a cyclic structure") },
 
+    /* njs.dump(). */
+
+    { nxt_string("njs.dump({a:1, b:[1,,2,{c:new Boolean(1)}]})"),
+      nxt_string("{a:1,b:[1,<empty>,2,{c:[Boolean: true]}]}") },
+
+    { nxt_string("njs.dump($r.props)"),
+      nxt_string("{a:{type:\"property\",props:[\"getter\"]},b:{type:\"property\",props:[\"getter\"]}}") },
+
+    { nxt_string("njs.dump($r.header)"),
+      nxt_string("{type:\"object\",props:[\"getter\",\"foreach\",\"next\"]}") },
+
     /* require(). */
 
     { nxt_string("require('unknown_module')"),