From be06b3e92b77a343adefa49a4b29e1ad523014ea Mon Sep 17 00:00:00 2001 From: Fabrice Bellard Date: Wed, 30 Apr 2025 13:40:15 +0200 Subject: [PATCH] added JS_PrintValue() and use it in console.log(), print() and the REPL (#256) --- quickjs-libc.c | 57 ++- quickjs.c | 940 +++++++++++++++++++++++++++++++++++-------------- quickjs.h | 17 + repl.js | 149 ++------ run-test262.c | 29 +- 5 files changed, 752 insertions(+), 440 deletions(-) diff --git a/quickjs-libc.c b/quickjs-libc.c index 091fd42..b59c6ac 100644 --- a/quickjs-libc.c +++ b/quickjs-libc.c @@ -1083,6 +1083,13 @@ static JSValue js_std_file_printf(JSContext *ctx, JSValueConst this_val, return js_printf_internal(ctx, argc, argv, f); } +static JSValue js_std_file_printObject(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JS_PrintValue(ctx, stdout, argv[0], NULL); + return JS_UNDEFINED; +} + static JSValue js_std_file_flush(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { @@ -1540,6 +1547,7 @@ static const JSCFunctionListEntry js_std_funcs[] = { JS_PROP_INT32_DEF("SEEK_CUR", SEEK_CUR, JS_PROP_CONFIGURABLE ), JS_PROP_INT32_DEF("SEEK_END", SEEK_END, JS_PROP_CONFIGURABLE ), JS_OBJECT_DEF("Error", js_std_error_props, countof(js_std_error_props), JS_PROP_CONFIGURABLE), + JS_CFUNC_DEF("__printObject", 1, js_std_file_printObject ), }; static const JSCFunctionListEntry js_std_file_proto_funcs[] = { @@ -3891,17 +3899,23 @@ static JSValue js_print(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { int i; - const char *str; - size_t len; - + JSValueConst v; + for(i = 0; i < argc; i++) { if (i != 0) putchar(' '); - str = JS_ToCStringLen(ctx, &len, argv[i]); - if (!str) - return JS_EXCEPTION; - fwrite(str, 1, len, stdout); - JS_FreeCString(ctx, str); + v = argv[i]; + if (JS_IsString(v)) { + const char *str; + size_t len; + str = JS_ToCStringLen(ctx, &len, v); + if (!str) + return JS_EXCEPTION; + fwrite(str, 1, len, stdout); + JS_FreeCString(ctx, str); + } else { + JS_PrintValue(ctx, stdout, v, NULL); + } } putchar('\n'); return JS_UNDEFINED; @@ -4012,33 +4026,10 @@ void js_std_free_handlers(JSRuntime *rt) JS_SetRuntimeOpaque(rt, NULL); /* fail safe */ } -static void js_dump_obj(JSContext *ctx, FILE *f, JSValueConst val) -{ - const char *str; - - str = JS_ToCString(ctx, val); - if (str) { - fprintf(f, "%s\n", str); - JS_FreeCString(ctx, str); - } else { - fprintf(f, "[exception]\n"); - } -} - static void js_std_dump_error1(JSContext *ctx, JSValueConst exception_val) { - JSValue val; - BOOL is_error; - - is_error = JS_IsError(ctx, exception_val); - js_dump_obj(ctx, stderr, exception_val); - if (is_error) { - val = JS_GetPropertyStr(ctx, exception_val, "stack"); - if (!JS_IsUndefined(val)) { - js_dump_obj(ctx, stderr, val); - } - JS_FreeValue(ctx, val); - } + JS_PrintValue(ctx, stderr, exception_val, NULL); + fputc('\n', stderr); } void js_std_dump_error(JSContext *ctx) diff --git a/quickjs.c b/quickjs.c index aa174e9..428288b 100644 --- a/quickjs.c +++ b/quickjs.c @@ -984,6 +984,27 @@ struct JSObject { } u; }; +typedef struct JSMapRecord { + int ref_count; /* used during enumeration to avoid freeing the record */ + BOOL empty : 8; /* TRUE if the record is deleted */ + struct list_head link; + struct JSMapRecord *hash_next; + JSValue key; + JSValue value; +} JSMapRecord; + +typedef struct JSMapState { + BOOL is_weak; /* TRUE if WeakSet/WeakMap */ + struct list_head records; /* list of JSMapRecord.link */ + uint32_t record_count; + JSMapRecord **hash_table; + int hash_bits; + uint32_t hash_size; /* = 2 ^ hash_bits */ + uint32_t record_count_threshold; /* count at which a hash table + resize is needed */ + JSWeakRefHeader weakref_header; /* only used if is_weak = TRUE */ +} JSMapState; + enum { __JS_ATOM_NULL = JS_ATOM_NULL, #define DEF(name, str) JS_ATOM_ ## name, @@ -1057,16 +1078,14 @@ static __exception int JS_ToArrayLengthFree(JSContext *ctx, uint32_t *plen, static JSValue JS_EvalObject(JSContext *ctx, JSValueConst this_obj, JSValueConst val, int flags, int scope_idx); JSValue __attribute__((format(printf, 2, 3))) JS_ThrowInternalError(JSContext *ctx, const char *fmt, ...); +static void js_print_atom(JSRuntime *rt, FILE *fo, JSAtom atom); static __maybe_unused void JS_DumpAtoms(JSRuntime *rt); static __maybe_unused void JS_DumpString(JSRuntime *rt, const JSString *p); static __maybe_unused void JS_DumpObjectHeader(JSRuntime *rt); static __maybe_unused void JS_DumpObject(JSRuntime *rt, JSObject *p); static __maybe_unused void JS_DumpGCObject(JSRuntime *rt, JSGCObjectHeader *p); -static __maybe_unused void JS_DumpValueShort(JSRuntime *rt, JSValueConst val); -static __maybe_unused void JS_DumpValue(JSContext *ctx, JSValueConst val); -static __maybe_unused void JS_PrintValue(JSContext *ctx, - const char *str, - JSValueConst val); +static __maybe_unused void JS_DumpValueRT(JSRuntime *rt, const char *str, JSValueConst val); +static __maybe_unused void JS_DumpValue(JSContext *ctx, const char *str, JSValueConst val); static __maybe_unused void JS_DumpShapes(JSRuntime *rt); static JSValue js_function_apply(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic); @@ -1261,6 +1280,12 @@ static void JS_RunGCInternal(JSRuntime *rt, BOOL remove_weak_objects); static JSValue js_array_from_iterator(JSContext *ctx, uint32_t *plen, JSValueConst obj, JSValueConst method); static int js_string_find_invalid_codepoint(JSString *p); +static JSValue js_regexp_toString(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv); +static JSValue get_date_string(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int magic); +static JSValue js_error_toString(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv); static const JSClassExoticMethods js_arguments_exotic_methods; static const JSClassExoticMethods js_string_exotic_methods; @@ -2405,18 +2430,18 @@ static uint32_t hash_string_rope(JSValueConst val, uint32_t h) } } -static __maybe_unused void JS_DumpChar(JSRuntime *rt, int c, int sep) +static __maybe_unused void JS_DumpChar(FILE *fo, int c, int sep) { if (c == sep || c == '\\') { - putchar('\\'); - putchar(c); + fputc('\\', fo); + fputc(c, fo); } else if (c >= ' ' && c <= 126) { - putchar(c); + fputc(c, fo); } else if (c == '\n') { - putchar('\\'); - putchar('n'); + fputc('\\', fo); + fputc('n', fo); } else { - printf("\\u%04x", c); + fprintf(fo, "\\u%04x", c); } } @@ -2432,7 +2457,7 @@ static __maybe_unused void JS_DumpString(JSRuntime *rt, const JSString *p) sep = (p->header.ref_count == 1) ? '\"' : '\''; putchar(sep); for(i = 0; i < p->len; i++) { - JS_DumpChar(rt, string_get(p, i), sep); + JS_DumpChar(stdout, string_get(p, i), sep); } putchar(sep); } @@ -3178,40 +3203,7 @@ static BOOL JS_AtomSymbolHasDescription(JSContext *ctx, JSAtom v) static __maybe_unused void print_atom(JSContext *ctx, JSAtom atom) { - char buf[ATOM_GET_STR_BUF_SIZE]; - const char *p; - int i; - - /* XXX: should handle embedded null characters */ - /* XXX: should move encoding code to JS_AtomGetStr */ - p = JS_AtomGetStr(ctx, buf, sizeof(buf), atom); - for (i = 0; p[i]; i++) { - int c = (unsigned char)p[i]; - if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || - (c == '_' || c == '$') || (c >= '0' && c <= '9' && i > 0))) - break; - } - if (i > 0 && p[i] == '\0') { - printf("%s", p); - } else { - putchar('"'); - printf("%.*s", i, p); - for (; p[i]; i++) { - int c = (unsigned char)p[i]; - if (c == '\"' || c == '\\') { - putchar('\\'); - putchar(c); - } else if (c >= ' ' && c <= 126) { - putchar(c); - } else if (c == '\n') { - putchar('\\'); - putchar('n'); - } else { - printf("\\u%04x", c); - } - } - putchar('\"'); - } + js_print_atom(ctx->rt, stdout, atom); } /* free with JS_FreeCString() */ @@ -12964,22 +12956,637 @@ static JSValue JS_ToQuotedString(JSContext *ctx, JSValueConst val1) return JS_EXCEPTION; } +#define JS_PRINT_MAX_DEPTH 8 + +typedef struct { + JSRuntime *rt; + JSContext *ctx; /* may be NULL */ + JSPrintValueOptions options; + FILE *fo; + int level; + JSObject *print_stack[JS_PRINT_MAX_DEPTH]; /* level values */ +} JSPrintValueState; + +static void js_print_value(JSPrintValueState *s, JSValueConst val); + +static void js_print_float64(JSPrintValueState *s, double d) +{ + JSDTOATempMem dtoa_mem; + char buf[32]; + int len; + len = js_dtoa(buf, d, 10, 0, JS_DTOA_FORMAT_FREE | JS_DTOA_MINUS_ZERO, &dtoa_mem); + fwrite(buf, 1, len, s->fo); +} + +static uint32_t js_string_get_length(JSValueConst val) +{ + if (JS_VALUE_GET_TAG(val) == JS_TAG_STRING) { + JSString *p = JS_VALUE_GET_STRING(val); + return p->len; + } else if (JS_VALUE_GET_TAG(val) == JS_TAG_STRING_ROPE) { + JSStringRope *r = JS_VALUE_GET_PTR(val); + return r->len; + } else { + return 0; + } +} + +static void js_print_string_rec(JSPrintValueState *s, JSValueConst val, + int sep, uint32_t pos) +{ + if (JS_VALUE_GET_TAG(val) == JS_TAG_STRING) { + JSString *p = JS_VALUE_GET_STRING(val); + uint32_t i, len; + if (pos < s->options.max_string_length) { + len = min_uint32(p->len, s->options.max_string_length - pos); + for(i = 0; i < len; i++) { + JS_DumpChar(s->fo, string_get(p, i), sep); + } + } + } else if (JS_VALUE_GET_TAG(val) == JS_TAG_STRING_ROPE) { + JSStringRope *r = JS_VALUE_GET_PTR(val); + js_print_string_rec(s, r->left, sep, pos); + js_print_string_rec(s, r->right, sep, pos + js_string_get_length(r->left)); + } else { + fprintf(s->fo, "", (int)JS_VALUE_GET_TAG(val)); + } +} + +static void js_print_string(JSPrintValueState *s, JSValueConst val) +{ + int sep; + if (s->options.raw_dump && JS_VALUE_GET_TAG(val) == JS_TAG_STRING) { + JSString *p = JS_VALUE_GET_STRING(val); + fprintf(s->fo, "%d", p->header.ref_count); + sep = (p->header.ref_count == 1) ? '\"' : '\''; + } else { + sep = '\"'; + } + fputc(sep, s->fo); + js_print_string_rec(s, val, sep, 0); + fputc(sep, s->fo); + if (js_string_get_length(val) > s->options.max_string_length) { + uint32_t n = js_string_get_length(val) - s->options.max_string_length; + fprintf(s->fo, "... %u more character%s", n, n > 1 ? "s" : ""); + } +} + +static void js_print_raw_string2(JSPrintValueState *s, JSValueConst val, BOOL remove_last_lf) +{ + const char *cstr; + size_t len; + cstr = JS_ToCStringLen(s->ctx, &len, val); + if (cstr) { + if (remove_last_lf && len > 0 && cstr[len - 1] == '\n') + len--; + fwrite(cstr, 1, len, s->fo); + JS_FreeCString(s->ctx, cstr); + } +} + +static void js_print_raw_string(JSPrintValueState *s, JSValueConst val) +{ + js_print_raw_string2(s, val, FALSE); +} + +static BOOL is_ascii_ident(const JSString *p) +{ + int i, c; + + if (p->len == 0) + return FALSE; + for(i = 0; i < p->len; i++) { + c = string_get(p, i); + if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || + (c == '_' || c == '$') || (c >= '0' && c <= '9' && i > 0))) + return FALSE; + } + return TRUE; +} + +static void js_print_atom(JSRuntime *rt, FILE *fo, JSAtom atom) +{ + int i; + if (__JS_AtomIsTaggedInt(atom)) { + fprintf(fo, "%u", __JS_AtomToUInt32(atom)); + } else if (atom == JS_ATOM_NULL) { + fprintf(fo, ""); + } else { + assert(atom < rt->atom_size); + JSString *p; + p = rt->atom_array[atom]; + if (is_ascii_ident(p)) { + for(i = 0; i < p->len; i++) { + fputc(string_get(p, i), fo); + } + } else { + fputc('"', fo); + for(i = 0; i < p->len; i++) { + JS_DumpChar(fo, string_get(p, i), '\"'); + } + fputc('"', fo); + } + } +} + +/* return 0 if invalid length */ +static uint32_t js_print_array_get_length(JSObject *p) +{ + JSProperty *pr; + JSShapeProperty *prs; + JSValueConst val; + + prs = find_own_property(&pr, p, JS_ATOM_length); + if (!prs) + return 0; + if ((prs->flags & JS_PROP_TMASK) != JS_PROP_NORMAL) + return 0; + val = pr->u.value; + switch(JS_VALUE_GET_NORM_TAG(val)) { + case JS_TAG_INT: + return JS_VALUE_GET_INT(val); + case JS_TAG_FLOAT64: + return (uint32_t)JS_VALUE_GET_FLOAT64(val); + default: + return 0; + } +} + +static void js_print_comma(JSPrintValueState *s, int *pcomma_state) +{ + switch(*pcomma_state) { + case 0: + break; + case 1: + fprintf(s->fo, ", "); + break; + case 2: + fprintf(s->fo, " { "); + break; + } + *pcomma_state = 1; +} + +static void js_print_more_items(JSPrintValueState *s, int *pcomma_state, + uint32_t n) +{ + js_print_comma(s, pcomma_state); + fprintf(s->fo, "... %u more item%s", n, n > 1 ? "s" : ""); +} + +static void js_print_object(JSPrintValueState *s, JSObject *p) +{ + JSRuntime *rt = s->rt; + JSShape *sh; + JSShapeProperty *prs; + JSProperty *pr; + int comma_state; + BOOL is_array; + uint32_t i; + + comma_state = 0; + is_array = FALSE; + if (p->class_id == JS_CLASS_ARRAY) { + is_array = TRUE; + fprintf(s->fo, "[ "); + /* XXX: print array like properties even if not fast array */ + if (p->fast_array) { + uint32_t len, n, len1; + len = js_print_array_get_length(p); + + len1 = min_uint32(p->u.array.count, s->options.max_item_count); + for(i = 0; i < len1; i++) { + js_print_comma(s, &comma_state); + js_print_value(s, p->u.array.u.values[i]); + } + if (len1 < p->u.array.count) + js_print_more_items(s, &comma_state, p->u.array.count - len1); + if (p->u.array.count < len) { + n = len - p->u.array.count; + js_print_comma(s, &comma_state); + fprintf(s->fo, "<%u empty item%s>", n, n > 1 ? "s" : ""); + } + } + } else if (p->class_id >= JS_CLASS_UINT8C_ARRAY && p->class_id <= JS_CLASS_FLOAT64_ARRAY) { + uint32_t size = 1 << typed_array_size_log2(p->class_id); + uint32_t len1; + int64_t v; + + js_print_atom(s->rt, s->fo, rt->class_array[p->class_id].class_name); + fprintf(s->fo, "(%u) [ ", p->u.array.count); + + is_array = TRUE; + len1 = min_uint32(p->u.array.count, s->options.max_item_count); + for(i = 0; i < len1; i++) { + const uint8_t *ptr = p->u.array.u.uint8_ptr + i * size; + js_print_comma(s, &comma_state); + switch(p->class_id) { + case JS_CLASS_UINT8C_ARRAY: + case JS_CLASS_UINT8_ARRAY: + v = *ptr; + goto ta_int64; + case JS_CLASS_INT8_ARRAY: + v = *(int8_t *)ptr; + goto ta_int64; + case JS_CLASS_INT16_ARRAY: + v = *(int16_t *)ptr; + goto ta_int64; + case JS_CLASS_UINT16_ARRAY: + v = *(uint16_t *)ptr; + goto ta_int64; + case JS_CLASS_INT32_ARRAY: + v = *(int32_t *)ptr; + goto ta_int64; + case JS_CLASS_UINT32_ARRAY: + v = *(uint32_t *)ptr; + goto ta_int64; + case JS_CLASS_BIG_INT64_ARRAY: + v = *(int64_t *)ptr; + ta_int64: + fprintf(s->fo, "%" PRId64, v); + break; + case JS_CLASS_BIG_UINT64_ARRAY: + fprintf(s->fo, "%" PRIu64, *(uint64_t *)ptr); + break; + case JS_CLASS_FLOAT32_ARRAY: + js_print_float64(s, *(float *)ptr); + break; + case JS_CLASS_FLOAT64_ARRAY: + js_print_float64(s, *(double *)ptr); + break; + } + } + if (len1 < p->u.array.count) + js_print_more_items(s, &comma_state, p->u.array.count - len1); + } else if (p->class_id == JS_CLASS_BYTECODE_FUNCTION || + (rt->class_array[p->class_id].call != NULL && + p->class_id != JS_CLASS_PROXY)) { + fprintf(s->fo, "[Function"); + /* XXX: allow dump without ctx */ + if (!s->options.raw_dump && s->ctx) { + const char *func_name_str; + fputc(' ', s->fo); + func_name_str = get_func_name(s->ctx, JS_MKPTR(JS_TAG_OBJECT, p)); + if (!func_name_str || func_name_str[0] == '\0') + fputs("(anonymous)", s->fo); + else + fputs(func_name_str, s->fo); + JS_FreeCString(s->ctx, func_name_str); + } + fprintf(s->fo, "]"); + comma_state = 2; + } else if (p->class_id == JS_CLASS_MAP || p->class_id == JS_CLASS_SET) { + JSMapState *ms = p->u.opaque; + struct list_head *el; + + if (!ms) + goto default_obj; + js_print_atom(s->rt, s->fo, rt->class_array[p->class_id].class_name); + fprintf(s->fo, "(%u) { ", ms->record_count); + i = 0; + list_for_each(el, &ms->records) { + JSMapRecord *mr = list_entry(el, JSMapRecord, link); + js_print_comma(s, &comma_state); + if (mr->empty) + continue; + js_print_value(s, mr->key); + if (p->class_id == JS_CLASS_MAP) { + fprintf(s->fo, " => "); + js_print_value(s, mr->value); + } + i++; + if (i >= s->options.max_item_count) + break; + } + if (i < ms->record_count) + js_print_more_items(s, &comma_state, ms->record_count - i); + } else if (p->class_id == JS_CLASS_REGEXP && s->ctx && !s->options.raw_dump) { + JSValue str = js_regexp_toString(s->ctx, JS_MKPTR(JS_TAG_OBJECT, p), 0, NULL); + if (JS_IsException(str)) + goto default_obj; + js_print_raw_string(s, str); + JS_FreeValueRT(s->rt, str); + comma_state = 2; + } else if (p->class_id == JS_CLASS_DATE && s->ctx && !s->options.raw_dump) { + JSValue str = get_date_string(s->ctx, JS_MKPTR(JS_TAG_OBJECT, p), 0, NULL, 0x23); /* toISOString() */ + if (JS_IsException(str)) + goto default_obj; + js_print_raw_string(s, str); + JS_FreeValueRT(s->rt, str); + comma_state = 2; + } else if (p->class_id == JS_CLASS_ERROR && s->ctx && !s->options.raw_dump) { + JSValue str = js_error_toString(s->ctx, JS_MKPTR(JS_TAG_OBJECT, p), 0, NULL); + if (JS_IsException(str)) + goto default_obj; + js_print_raw_string(s, str); + JS_FreeValueRT(s->rt, str); + /* dump the stack if present */ + str = JS_GetProperty(s->ctx, JS_MKPTR(JS_TAG_OBJECT, p), JS_ATOM_stack); + if (JS_IsString(str)) { + fputc('\n', s->fo); + /* XXX: should remove the last '\n' in stack as + v8. SpiderMonkey does not do it */ + js_print_raw_string2(s, str, TRUE); + } + JS_FreeValueRT(s->rt, str); + comma_state = 2; + } else { + default_obj: + if (p->class_id != JS_CLASS_OBJECT) { + js_print_atom(s->rt, s->fo, rt->class_array[p->class_id].class_name); + fprintf(s->fo, " "); + } + fprintf(s->fo, "{ "); + } + + sh = p->shape; /* the shape can be NULL while freeing an object */ + if (sh) { + uint32_t j; + + j = 0; + for(i = 0, prs = get_shape_prop(sh); i < sh->prop_count; i++, prs++) { + if (prs->atom != JS_ATOM_NULL) { + if (!(prs->flags & JS_PROP_ENUMERABLE) && + !s->options.show_hidden) { + continue; + } + if (j < s->options.max_item_count) { + pr = &p->prop[i]; + js_print_comma(s, &comma_state); + js_print_atom(s->rt, s->fo, prs->atom); + fprintf(s->fo, ": "); + + /* XXX: autoinit property */ + if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { + if (s->options.raw_dump) { + fprintf(s->fo, "[Getter %p Setter %p]", + pr->u.getset.getter, pr->u.getset.setter); + } else { + if (pr->u.getset.getter && pr->u.getset.setter) { + fprintf(s->fo, "[Getter/Setter]"); + } else if (pr->u.getset.setter) { + fprintf(s->fo, "[Setter]"); + } else { + fprintf(s->fo, "[Getter]"); + } + } + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { + if (s->options.raw_dump) { + fprintf(s->fo, "[varref %p]", (void *)pr->u.var_ref); + } else { + js_print_value(s, *pr->u.var_ref->pvalue); + } + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { + if (s->options.raw_dump) { + fprintf(s->fo, "[autoinit %p %d %p]", + (void *)js_autoinit_get_realm(pr), + js_autoinit_get_id(pr), + (void *)pr->u.init.opaque); + } else { + /* XXX: could autoinit but need to restart + the iteration */ + fprintf(s->fo, "[autoinit]"); + } + } else { + js_print_value(s, pr->u.value); + } + } + j++; + } + } + if (j > s->options.max_item_count) + js_print_more_items(s, &comma_state, j - s->options.max_item_count); + } + if (s->options.show_closure && js_class_has_bytecode(p->class_id)) { + JSFunctionBytecode *b = p->u.func.function_bytecode; + if (b->closure_var_count) { + JSVarRef **var_refs; + var_refs = p->u.func.var_refs; + + js_print_comma(s, &comma_state); + fprintf(s->fo, "[[Closure]]: ["); + for(i = 0; i < b->closure_var_count; i++) { + if (i != 0) + fprintf(s->fo, ", "); + js_print_value(s, var_refs[i]->value); + } + fprintf(s->fo, " ]"); + } + if (p->u.func.home_object) { + js_print_comma(s, &comma_state); + fprintf(s->fo, "[[HomeObject]]: "); + js_print_value(s, JS_MKPTR(JS_TAG_OBJECT, p->u.func.home_object)); + } + } + + if (!is_array) { + if (comma_state != 2) { + fprintf(s->fo, " }"); + } + } else { + fprintf(s->fo, " ]"); + } +} + +static int js_print_stack_index(JSPrintValueState *s, JSObject *p) +{ + int i; + for(i = 0; i < s->level; i++) + if (s->print_stack[i] == p) + return i; + return -1; +} + +static void js_print_value(JSPrintValueState *s, JSValueConst val) +{ + uint32_t tag = JS_VALUE_GET_NORM_TAG(val); + const char *str; + + switch(tag) { + case JS_TAG_INT: + fprintf(s->fo, "%d", JS_VALUE_GET_INT(val)); + break; + case JS_TAG_BOOL: + if (JS_VALUE_GET_BOOL(val)) + str = "true"; + else + str = "false"; + goto print_str; + case JS_TAG_NULL: + str = "null"; + goto print_str; + case JS_TAG_EXCEPTION: + str = "exception"; + goto print_str; + case JS_TAG_UNINITIALIZED: + str = "uninitialized"; + goto print_str; + case JS_TAG_UNDEFINED: + str = "undefined"; + print_str: + fprintf(s->fo, "%s", str); + break; + case JS_TAG_FLOAT64: + js_print_float64(s, JS_VALUE_GET_FLOAT64(val)); + break; + case JS_TAG_SHORT_BIG_INT: + fprintf(s->fo, "%" PRId64 "n", (int64_t)JS_VALUE_GET_SHORT_BIG_INT(val)); + break; + case JS_TAG_BIG_INT: + if (!s->options.raw_dump && s->ctx) { + JSValue str = js_bigint_to_string(s->ctx, val); + if (JS_IsException(str)) + goto raw_bigint; + js_print_raw_string(s, str); + fputc('n', s->fo); + JS_FreeValueRT(s->rt, str); + } else { + JSBigInt *p; + int sgn, i; + raw_bigint: + p = JS_VALUE_GET_PTR(val); + /* In order to avoid allocations we just dump the limbs */ + sgn = js_bigint_sign(p); + if (sgn) + fprintf(s->fo, "BigInt.asIntN(%d,", p->len * JS_LIMB_BITS); + fprintf(s->fo, "0x"); + for(i = p->len - 1; i >= 0; i--) { + if (i != p->len - 1) + fprintf(s->fo, "_"); +#if JS_LIMB_BITS == 32 + fprintf(s->fo, "%08x", p->tab[i]); +#else + fprintf(s->fo, "%016" PRIx64, p->tab[i]); +#endif + } + fprintf(s->fo, "n"); + if (sgn) + fprintf(s->fo, ")"); + } + break; + case JS_TAG_STRING: + case JS_TAG_STRING_ROPE: + if (s->options.raw_dump && tag == JS_TAG_STRING_ROPE) { + JSStringRope *r = JS_VALUE_GET_STRING_ROPE(val); + fprintf(s->fo, "[rope len=%d depth=%d]", r->len, r->depth); + } else { + js_print_string(s, val); + } + break; + case JS_TAG_FUNCTION_BYTECODE: + { + JSFunctionBytecode *b = JS_VALUE_GET_PTR(val); + fprintf(s->fo, "[bytecode "); + js_print_atom(s->rt, s->fo, b->func_name); + fprintf(s->fo, "]"); + } + break; + case JS_TAG_OBJECT: + { + JSObject *p = JS_VALUE_GET_OBJ(val); + int idx; + idx = js_print_stack_index(s, p); + if (idx >= 0) { + fprintf(s->fo, "[circular %d]", idx); + } else if (s->level < s->options.max_depth) { + s->print_stack[s->level++] = p; + js_print_object(s, JS_VALUE_GET_OBJ(val)); + s->level--; + } else { + JSAtom atom = s->rt->class_array[p->class_id].class_name; + fprintf(s->fo, "["); + js_print_atom(s->rt, s->fo, atom); + if (s->options.raw_dump) { + fprintf(s->fo, " %p", (void *)p); + } + fprintf(s->fo, "]"); + } + } + break; + case JS_TAG_SYMBOL: + { + JSAtomStruct *p = JS_VALUE_GET_PTR(val); + fprintf(s->fo, "Symbol("); + js_print_atom(s->rt, s->fo, js_get_atom_index(s->rt, p)); + fprintf(s->fo, ")"); + } + break; + case JS_TAG_MODULE: + fprintf(s->fo, "[module]"); + break; + default: + fprintf(s->fo, "[unknown tag %d]", tag); + break; + } +} + +void JS_PrintValueSetDefaultOptions(JSPrintValueOptions *options) +{ + memset(options, 0, sizeof(*options)); + options->max_depth = 2; + options->max_string_length = 1000; + options->max_item_count = 100; +} + +static void JS_PrintValueInternal(JSRuntime *rt, JSContext *ctx, + FILE *fo, JSValueConst val, const JSPrintValueOptions *options) +{ + JSPrintValueState ss, *s = &ss; + if (options) + s->options = *options; + else + JS_PrintValueSetDefaultOptions(&s->options); + if (s->options.max_depth <= 0) + s->options.max_depth = JS_PRINT_MAX_DEPTH; + else + s->options.max_depth = min_int(s->options.max_depth, JS_PRINT_MAX_DEPTH); + if (s->options.max_string_length == 0) + s->options.max_string_length = UINT32_MAX; + if (s->options.max_item_count == 0) + s->options.max_item_count = UINT32_MAX; + s->rt = rt; + s->ctx = ctx; + s->fo = fo; + s->level = 0; + js_print_value(s, val); +} + +void JS_PrintValueRT(JSRuntime *rt, FILE *fo, JSValueConst val, const JSPrintValueOptions *options) +{ + JS_PrintValueInternal(rt, NULL, fo, val, options); +} + +void JS_PrintValue(JSContext *ctx, FILE *fo, JSValueConst val, const JSPrintValueOptions *options) +{ + JS_PrintValueInternal(ctx->rt, ctx, fo, val, options); +} + +static __maybe_unused void JS_DumpValue(JSContext *ctx, const char *str, JSValueConst val) +{ + printf("%s=", str); + JS_PrintValue(ctx, stdout, val, NULL); + printf("\n"); +} + +static __maybe_unused void JS_DumpValueRT(JSRuntime *rt, const char *str, JSValueConst val) +{ + printf("%s=", str); + JS_PrintValueRT(rt, stdout, val, NULL); + printf("\n"); +} + static __maybe_unused void JS_DumpObjectHeader(JSRuntime *rt) { - printf("%14s %4s %4s %14s %10s %s\n", - "ADDRESS", "REFS", "SHRF", "PROTO", "CLASS", "PROPS"); + printf("%14s %4s %4s %14s %s\n", + "ADDRESS", "REFS", "SHRF", "PROTO", "CONTENT"); } /* for debug only: dump an object without side effect */ static __maybe_unused void JS_DumpObject(JSRuntime *rt, JSObject *p) { - uint32_t i; - char atom_buf[ATOM_GET_STR_BUF_SIZE]; JSShape *sh; - JSShapeProperty *prs; - JSProperty *pr; - BOOL is_first = TRUE; - + JSPrintValueOptions options; + /* XXX: should encode atoms with special characters */ sh = p->shape; /* the shape can be NULL while freeing an object */ printf("%14p %4d ", @@ -12993,85 +13600,13 @@ static __maybe_unused void JS_DumpObject(JSRuntime *rt, JSObject *p) } else { printf("%3s %14s ", "-", "-"); } - printf("%10s ", - JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), rt->class_array[p->class_id].class_name)); - if (p->is_exotic && p->fast_array) { - printf("[ "); - for(i = 0; i < p->u.array.count; i++) { - if (i != 0) - printf(", "); - switch (p->class_id) { - case JS_CLASS_ARRAY: - case JS_CLASS_ARGUMENTS: - JS_DumpValueShort(rt, p->u.array.u.values[i]); - break; - case JS_CLASS_UINT8C_ARRAY: - case JS_CLASS_INT8_ARRAY: - case JS_CLASS_UINT8_ARRAY: - case JS_CLASS_INT16_ARRAY: - case JS_CLASS_UINT16_ARRAY: - case JS_CLASS_INT32_ARRAY: - case JS_CLASS_UINT32_ARRAY: - case JS_CLASS_BIG_INT64_ARRAY: - case JS_CLASS_BIG_UINT64_ARRAY: - case JS_CLASS_FLOAT32_ARRAY: - case JS_CLASS_FLOAT64_ARRAY: - { - int size = 1 << typed_array_size_log2(p->class_id); - const uint8_t *b = p->u.array.u.uint8_ptr + i * size; - while (size-- > 0) - printf("%02X", *b++); - } - break; - } - } - printf(" ] "); - } - if (sh) { - printf("{ "); - for(i = 0, prs = get_shape_prop(sh); i < sh->prop_count; i++, prs++) { - if (prs->atom != JS_ATOM_NULL) { - pr = &p->prop[i]; - if (!is_first) - printf(", "); - printf("%s: ", - JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), prs->atom)); - if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { - printf("[getset %p %p]", (void *)pr->u.getset.getter, - (void *)pr->u.getset.setter); - } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { - printf("[varref %p]", (void *)pr->u.var_ref); - } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { - printf("[autoinit %p %d %p]", - (void *)js_autoinit_get_realm(pr), - js_autoinit_get_id(pr), - (void *)pr->u.init.opaque); - } else { - JS_DumpValueShort(rt, pr->u.value); - } - is_first = FALSE; - } - } - printf(" }"); - } + JS_PrintValueSetDefaultOptions(&options); + options.max_depth = 1; + options.show_hidden = TRUE; + options.raw_dump = TRUE; + JS_PrintValueRT(rt, stdout, JS_MKPTR(JS_TAG_OBJECT, p), &options); - if (js_class_has_bytecode(p->class_id)) { - JSFunctionBytecode *b = p->u.func.function_bytecode; - JSVarRef **var_refs; - if (b->closure_var_count) { - var_refs = p->u.func.var_refs; - printf(" Closure:"); - for(i = 0; i < b->closure_var_count; i++) { - printf(" "); - JS_DumpValueShort(rt, var_refs[i]->value); - } - if (p->u.func.home_object) { - printf(" HomeObject: "); - JS_DumpValueShort(rt, JS_MKPTR(JS_TAG_OBJECT, p->u.func.home_object)); - } - } - } printf("\n"); } @@ -13107,126 +13642,6 @@ static __maybe_unused void JS_DumpGCObject(JSRuntime *rt, JSGCObjectHeader *p) } } -static __maybe_unused void JS_DumpValueShort(JSRuntime *rt, - JSValueConst val) -{ - uint32_t tag = JS_VALUE_GET_NORM_TAG(val); - const char *str; - - switch(tag) { - case JS_TAG_INT: - printf("%d", JS_VALUE_GET_INT(val)); - break; - case JS_TAG_BOOL: - if (JS_VALUE_GET_BOOL(val)) - str = "true"; - else - str = "false"; - goto print_str; - case JS_TAG_NULL: - str = "null"; - goto print_str; - case JS_TAG_EXCEPTION: - str = "exception"; - goto print_str; - case JS_TAG_UNINITIALIZED: - str = "uninitialized"; - goto print_str; - case JS_TAG_UNDEFINED: - str = "undefined"; - print_str: - printf("%s", str); - break; - case JS_TAG_FLOAT64: - printf("%.14g", JS_VALUE_GET_FLOAT64(val)); - break; - case JS_TAG_SHORT_BIG_INT: - printf("%" PRId64 "n", (int64_t)JS_VALUE_GET_SHORT_BIG_INT(val)); - break; - case JS_TAG_BIG_INT: - { - JSBigInt *p = JS_VALUE_GET_PTR(val); - int sgn, i; - /* In order to avoid allocations we just dump the limbs */ - sgn = js_bigint_sign(p); - if (sgn) - printf("BigInt.asIntN(%d,", p->len * JS_LIMB_BITS); - printf("0x"); - for(i = p->len - 1; i >= 0; i--) { - if (i != p->len - 1) - printf("_"); -#if JS_LIMB_BITS == 32 - printf("%08x", p->tab[i]); -#else - printf("%016" PRIx64, p->tab[i]); -#endif - } - printf("n"); - if (sgn) - printf(")"); - } - break; - case JS_TAG_STRING: - { - JSString *p; - p = JS_VALUE_GET_STRING(val); - JS_DumpString(rt, p); - } - break; - case JS_TAG_STRING_ROPE: - { - JSStringRope *r = JS_VALUE_GET_STRING_ROPE(val); - printf("[rope len=%d depth=%d]", r->len, r->depth); - } - break; - case JS_TAG_FUNCTION_BYTECODE: - { - JSFunctionBytecode *b = JS_VALUE_GET_PTR(val); - char buf[ATOM_GET_STR_BUF_SIZE]; - printf("[bytecode %s]", JS_AtomGetStrRT(rt, buf, sizeof(buf), b->func_name)); - } - break; - case JS_TAG_OBJECT: - { - JSObject *p = JS_VALUE_GET_OBJ(val); - JSAtom atom = rt->class_array[p->class_id].class_name; - char atom_buf[ATOM_GET_STR_BUF_SIZE]; - printf("[%s %p]", - JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), atom), (void *)p); - } - break; - case JS_TAG_SYMBOL: - { - JSAtomStruct *p = JS_VALUE_GET_PTR(val); - char atom_buf[ATOM_GET_STR_BUF_SIZE]; - printf("Symbol(%s)", - JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), js_get_atom_index(rt, p))); - } - break; - case JS_TAG_MODULE: - printf("[module]"); - break; - default: - printf("[unknown tag %d]", tag); - break; - } -} - -static __maybe_unused void JS_DumpValue(JSContext *ctx, - JSValueConst val) -{ - JS_DumpValueShort(ctx->rt, val); -} - -static __maybe_unused void JS_PrintValue(JSContext *ctx, - const char *str, - JSValueConst val) -{ - printf("%s=", str); - JS_DumpValueShort(ctx->rt, val); - printf("\n"); -} - /* return -1 if exception (proxy case) or TRUE/FALSE */ // TODO: should take flags to make proxy resolution and exceptions optional int JS_IsArray(JSContext *ctx, JSValueConst val) @@ -47150,27 +47565,6 @@ static const JSCFunctionListEntry js_symbol_funcs[] = { /* Set/Map/WeakSet/WeakMap */ -typedef struct JSMapRecord { - int ref_count; /* used during enumeration to avoid freeing the record */ - BOOL empty : 8; /* TRUE if the record is deleted */ - struct list_head link; - struct JSMapRecord *hash_next; - JSValue key; - JSValue value; -} JSMapRecord; - -typedef struct JSMapState { - BOOL is_weak; /* TRUE if WeakSet/WeakMap */ - struct list_head records; /* list of JSMapRecord.link */ - uint32_t record_count; - JSMapRecord **hash_table; - int hash_bits; - uint32_t hash_size; /* = 2 ^ hash_bits */ - uint32_t record_count_threshold; /* count at which a hash table - resize is needed */ - JSWeakRefHeader weakref_header; /* only used if is_weak = TRUE */ -} JSMapState; - static BOOL js_weakref_is_target(JSValueConst val) { switch (JS_VALUE_GET_TAG(val)) { diff --git a/quickjs.h b/quickjs.h index adc8620..b851cd9 100644 --- a/quickjs.h +++ b/quickjs.h @@ -1113,6 +1113,23 @@ int JS_SetModuleExport(JSContext *ctx, JSModuleDef *m, const char *export_name, int JS_SetModuleExportList(JSContext *ctx, JSModuleDef *m, const JSCFunctionListEntry *tab, int len); +/* debug value output */ + +typedef struct { + JS_BOOL show_hidden : 8; /* only show enumerable properties */ + JS_BOOL show_closure : 8; /* show closure variables */ + JS_BOOL raw_dump : 8; /* avoid doing autoinit and avoid any malloc() call (for internal use) */ + uint32_t max_depth; /* recurse up to this depth, 0 = no limit */ + uint32_t max_string_length; /* print no more than this length for + strings, 0 = no limit */ + uint32_t max_item_count; /* print no more than this count for + arrays or objects, 0 = no limit */ +} JSPrintValueOptions; + +void JS_PrintValueSetDefaultOptions(JSPrintValueOptions *options); +void JS_PrintValueRT(JSRuntime *rt, FILE *fo, JSValueConst val, const JSPrintValueOptions *options); +void JS_PrintValue(JSContext *ctx, FILE *fo, JSValueConst val, const JSPrintValueOptions *options); + #undef js_unlikely #undef js_force_inline diff --git a/repl.js b/repl.js index c65100d..06d4f26 100644 --- a/repl.js +++ b/repl.js @@ -875,126 +875,19 @@ import * as os from "os"; } var hex_mode = false; - var eval_mode = "std"; - function number_to_string(a, radix) { + function number_to_string_hex(a) { var s; - if (!isFinite(a)) { - /* NaN, Infinite */ - return a.toString(); + if (a < 0) { + a = -a; + s = "-"; } else { - if (a == 0) { - if (1 / a < 0) - s = "-0"; - else - s = "0"; - } else { - if (radix == 16 && a === Math.floor(a)) { - var s; - if (a < 0) { - a = -a; - s = "-"; - } else { - s = ""; - } - s += "0x" + a.toString(16); - } else { - s = a.toString(); - } - } - return s; + s = ""; } - } - - function bigint_to_string(a, radix) { - var s; - if (radix == 16) { - var s; - if (a < 0) { - a = -a; - s = "-"; - } else { - s = ""; - } - s += "0x" + a.toString(16); - } else { - s = a.toString(); - } - if (eval_mode === "std") - s += "n"; + s += "0x" + a.toString(16); return s; } - - function print(a) { - var stack = []; - - function print_rec(a) { - var n, i, keys, key, type, s; - - type = typeof(a); - if (type === "object") { - if (a === null) { - std.puts(a); - } else if (stack.indexOf(a) >= 0) { - std.puts("[circular]"); - } else if (a instanceof Date) { - std.puts("Date " + a.toGMTString().__quote()); - } else { - stack.push(a); - if (Array.isArray(a)) { - n = a.length; - std.puts("[ "); - for(i = 0; i < n; i++) { - if (i !== 0) - std.puts(", "); - if (i in a) { - print_rec(a[i]); - } else { - std.puts(""); - } - if (i > 20) { - std.puts("..."); - break; - } - } - std.puts(" ]"); - } else if (Object.__getClass(a) === "RegExp") { - std.puts(a.toString()); - } else { - keys = Object.keys(a); - n = keys.length; - std.puts("{ "); - for(i = 0; i < n; i++) { - if (i !== 0) - std.puts(", "); - key = keys[i]; - std.puts(key, ": "); - print_rec(a[key]); - } - std.puts(" }"); - } - stack.pop(a); - } - } else if (type === "string") { - s = a.__quote(); - if (s.length > 79) - s = s.substring(0, 75) + "...\""; - std.puts(s); - } else if (type === "number") { - std.puts(number_to_string(a, hex_mode ? 16 : 10)); - } else if (type === "bigint") { - std.puts(bigint_to_string(a, hex_mode ? 16 : 10)); - } else if (type === "symbol") { - std.puts(String(a)); - } else if (type === "function") { - std.puts("function " + a.name + "()"); - } else { - std.puts(a); - } - } - print_rec(a); - } - + function extract_directive(a) { var pos; if (a[0] !== '\\') @@ -1116,10 +1009,25 @@ import * as os from "os"; } function print_eval_result(result) { + var default_print = true; + result = result.value; eval_time = os.now() - eval_start_time; std.puts(colors[styles.result]); - print(result); + if (hex_mode) { + if (typeof result == "number" && + result === Math.floor(result)) { + std.puts(number_to_string_hex(result)); + default_print = false; + } else if (typeof result == "bigint") { + std.puts(number_to_string_hex(result)); + std.puts("n"); + default_print = false; + } + } + if (default_print) { + std.__printObject(result); + } std.puts("\n"); std.puts(colors.none); /* set the last result */ @@ -1130,15 +1038,10 @@ import * as os from "os"; function print_eval_error(error) { std.puts(colors[styles.error_msg]); - if (error instanceof Error) { - console.log(error); - if (error.stack) { - std.puts(error.stack); - } - } else { + if (!(error instanceof Error)) std.puts("Throw: "); - console.log(error); - } + std.__printObject(error); + std.puts("\n"); std.puts(colors.none); handle_cmd_end(); diff --git a/run-test262.c b/run-test262.c index 4397a1d..d1fa4f3 100644 --- a/run-test262.c +++ b/run-test262.c @@ -376,22 +376,29 @@ static JSValue js_print(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { int i; - const char *str; - + JSValueConst v; + if (outfile) { for (i = 0; i < argc; i++) { if (i != 0) fputc(' ', outfile); - str = JS_ToCString(ctx, argv[i]); - if (!str) - return JS_EXCEPTION; - if (!strcmp(str, "Test262:AsyncTestComplete")) { - async_done++; - } else if (strstart(str, "Test262:AsyncTestFailure", NULL)) { - async_done = 2; /* force an error */ + v = argv[i]; + if (JS_IsString(v)) { + const char *str; + size_t len; + str = JS_ToCStringLen(ctx, &len, v); + if (!str) + return JS_EXCEPTION; + if (!strcmp(str, "Test262:AsyncTestComplete")) { + async_done++; + } else if (strstart(str, "Test262:AsyncTestFailure", NULL)) { + async_done = 2; /* force an error */ + } + fwrite(str, 1, len, outfile); + JS_FreeCString(ctx, str); + } else { + JS_PrintValue(ctx, outfile, v, NULL); } - fputs(str, outfile); - JS_FreeCString(ctx, str); } fputc('\n', outfile); }