From bd2e5d89b395f948b2bccec8a0df6beaa88c8b99 Mon Sep 17 00:00:00 2001 From: Dmitry Volyntsev Date: Fri, 27 Jul 2018 17:01:52 +0300 Subject: [PATCH] Added the pretty string representation for values. MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit 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. --- 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 | 33 ++- njs/njs_regexp.h | 2 + njs/njs_shell.c | 49 +++- njs/test/njs_expect_test.exp | 96 +++++-- njs/test/njs_unit_test.c | 11 + 14 files changed, 734 insertions(+), 72 deletions(-) diff --git a/njs/njs.h b/njs/njs.h index dd196928..aa0d6fb5 100644 --- 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); diff --git a/njs/njs_builtin.c b/njs/njs_builtin.c index 5074b22d..3de147eb 100644 --- a/njs/njs_builtin.c +++ b/njs/njs_builtin.c @@ -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), + }, }; diff --git a/njs/njs_date.c b/njs/njs_date.c index 9d5437e4..dc5bef47 100644 --- a/njs/njs_date.c +++ b/njs/njs_date.c @@ -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); diff --git a/njs/njs_date.h b/njs/njs_date.h index 57f8ed92..38be5a3a 100644 --- a/njs/njs_date.h +++ b/njs/njs_date.h @@ -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 --git a/njs/njs_error.c b/njs/njs_error.c index 19fd6c5e..2b426f8f 100644 --- a/njs/njs_error.c +++ b/njs/njs_error.c @@ -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; } diff --git a/njs/njs_error.h b/njs/njs_error.h index 93145357..0cd724fa 100644 --- a/njs/njs_error.h +++ b/njs/njs_error.h @@ -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; diff --git a/njs/njs_extern.c b/njs/njs_extern.c index 29ff3315..4ed6b93d 100644 --- a/njs/njs_extern.c +++ b/njs/njs_extern.c @@ -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) { diff --git a/njs/njs_extern.h b/njs/njs_extern.h index 1df7b73f..6cbb9917 100644 --- a/njs/njs_extern.h +++ b/njs/njs_extern.h @@ -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 --git a/njs/njs_json.c b/njs/njs_json.c index a45741fe..616a4e59 100644 --- a/njs/njs_json.c +++ b/njs/njs_json.c @@ -5,6 +5,9 @@ */ #include +#include +#include +#include #include #include @@ -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, "e, 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(""); + 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 --git a/njs/njs_regexp.c b/njs/njs_regexp.c index 9fa044ee..5294ba0a 100644 --- a/njs/njs_regexp.c +++ b/njs/njs_regexp.c @@ -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); } diff --git a/njs/njs_regexp.h b/njs/njs_regexp.h index 85d3e3e8..8febede4 100644 --- a/njs/njs_regexp.h +++ b/njs/njs_regexp.h @@ -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; diff --git a/njs/njs_shell.c b/njs/njs_shell.c index 8053ac21..5b66bdaa 100644 --- a/njs/njs_shell.c +++ b/njs/njs_shell.c @@ -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; diff --git a/njs/test/njs_expect_test.exp b/njs/test/njs_expect_test.exp index 5247c7c6..075fb419 100644 --- a/njs/test/njs_expect_test.exp +++ b/njs/test/njs_expect_test.exp @@ -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 ,\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>> "} } diff --git a/njs/test/njs_unit_test.c b/njs/test/njs_unit_test.c index 04c48175..c78fc3b6 100644 --- a/njs/test/njs_unit_test.c +++ b/njs/test/njs_unit_test.c @@ -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,,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')"), -- 2.47.3