JS_CLASS_REGEXP_STRING_ITERATOR, /* u.regexp_string_iterator_data */
JS_CLASS_GENERATOR, /* u.generator_data */
JS_CLASS_GLOBAL_OBJECT, /* u.global_object */
+ JS_CLASS_RAWJSON,
JS_CLASS_PROXY, /* u.proxy_data */
JS_CLASS_PROMISE, /* u.promise_data */
JS_CLASS_PROMISE_RESOLVE_FUNCTION, /* u.promise_function_data */
{ JS_ATOM_RegExp_String_Iterator, js_regexp_string_iterator_finalizer, js_regexp_string_iterator_mark }, /* JS_CLASS_REGEXP_STRING_ITERATOR */
{ JS_ATOM_Generator, js_generator_finalizer, js_generator_mark }, /* JS_CLASS_GENERATOR */
{ JS_ATOM_Object, js_global_object_finalizer, js_global_object_mark }, /* JS_CLASS_GLOBAL_OBJECT */
+ { JS_ATOM_Object, NULL, NULL }, /* JS_CLASS_RAWJSON */
};
static int init_class_range(JSRuntime *rt, JSClassShortDef const *tab,
JSFunctionDef *cur_func;
BOOL is_module; /* parsing a module */
BOOL allow_html_comments;
- BOOL ext_json; /* true if accepting JSON superset */
+ BOOL ext_json; /* JSON parsing: true if accepting JSON superset */
GetLineColCache get_line_col_cache;
} JSParseState;
return json_next_token(s);
}
-static JSValue json_parse_value(JSParseState *s)
+
+typedef struct {
+ int count;
+ uint32_t hash_size;
+ struct JSONParseRecordEntry *entries;
+ uint32_t *hash_table;
+} JSONParseRecordObject;
+
+typedef struct JSONParseRecord {
+ JSValue value;
+ union {
+ JSONParseRecordObject obj;
+ struct {
+ int count;
+ struct JSONParseRecord *elements;
+ } array;
+ struct {
+ uint32_t source_pos;
+ uint32_t source_len;
+ } primitive;
+ } u;
+} JSONParseRecord;
+
+typedef struct JSONParseRecordEntry {
+ JSAtom atom;
+ uint32_t hash_next;
+ JSONParseRecord parse_record;
+} JSONParseRecordEntry;
+
+static void json_parse_record_init_obj(JSContext *ctx, JSONParseRecord *pr, JSValueConst val)
+{
+ pr->value = JS_DupValue(ctx, val);
+ pr->u.obj.count = 0;
+ pr->u.obj.entries = NULL;
+ pr->u.obj.hash_table = NULL;
+ pr->u.obj.hash_size = 0;
+}
+
+static void json_parse_record_init_array(JSContext *ctx, JSONParseRecord *pr, JSValueConst val)
+{
+ pr->value = JS_DupValue(ctx, val);
+ pr->u.array.count = 0;
+ pr->u.array.elements = NULL;
+}
+
+static void json_parse_record_init_primitive(JSContext *ctx, JSONParseRecord *pr, JSValueConst val,
+ uint32_t source_pos, uint32_t source_len)
+{
+ pr->value = JS_DupValue(ctx, val);
+ pr->u.primitive.source_pos = source_pos;
+ pr->u.primitive.source_len = source_len;
+}
+
+static int json_parse_record_resize_hash(JSContext *ctx, JSONParseRecordObject *po, uint32_t new_hash_size)
+{
+ uint32_t i, h, *new_hash_table;
+ JSONParseRecordEntry *e;
+
+ new_hash_table = js_malloc(ctx, sizeof(new_hash_table[0]) * new_hash_size);
+ if (!new_hash_table)
+ return -1;
+ js_free(ctx, po->hash_table);
+ po->hash_table = new_hash_table;
+ po->hash_size = new_hash_size;
+
+ for(i = 0; i < po->hash_size; i++) {
+ po->hash_table[i] = -1;
+ }
+ for(i = 0; i < po->count; i++) {
+ e = &po->entries[i];
+ h = e->atom & (po->hash_size - 1);
+ e->hash_next = po->hash_table[h];
+ po->hash_table[h] = i;
+ }
+ return 0;
+}
+
+static JSONParseRecord *json_parse_record_add(JSContext *ctx, JSONParseRecord *pr, JSAtom key, int *psize)
+{
+ JSONParseRecordObject *po = &pr->u.obj;
+ JSONParseRecordEntry *e;
+ JSONParseRecord *pr1;
+ uint32_t h;
+
+ if (js_resize_array(ctx, (void **)&po->entries, sizeof(po->entries[0]),
+ psize, po->count + 1)) {
+ return NULL;
+ }
+ /* don't use a hash table when the number of entries is small */
+ if (po->count >= 8 && (po->count + 1) > po->hash_size) {
+ int hash_bits = 32 - clz32(po->count);
+ if (json_parse_record_resize_hash(ctx, po, 1 << hash_bits))
+ return NULL;
+ }
+
+ e = &po->entries[po->count++];
+ e->atom = JS_DupAtom(ctx, key);
+ pr1 = &e->parse_record;
+ pr1->value = JS_UNDEFINED;
+ if (po->hash_size != 0) {
+ h = key & (po->hash_size - 1);
+ e->hash_next = po->hash_table[h];
+ po->hash_table[h] = po->count - 1;
+ }
+ return pr1;
+}
+
+static JSONParseRecord *json_parse_record_find(JSONParseRecord *pr, JSAtom key)
+{
+ JSONParseRecordObject *po = &pr->u.obj;
+ JSONParseRecordEntry *e;
+ uint32_t h, i;
+
+ if (po->hash_size == 0) {
+ for(i = 0; i < po->count; i++) {
+ if (po->entries[i].atom == key)
+ return &po->entries[i].parse_record;
+ }
+ } else {
+ h = key & (po->hash_size - 1);
+ i = po->hash_table[h];
+ while (i != -1) {
+ e = &po->entries[i];
+ if (e->atom == key)
+ return &e->parse_record;
+ i = e->hash_next;
+ }
+ }
+ return NULL;
+}
+
+static void json_free_parse_record(JSContext *ctx, JSONParseRecord *pr)
+{
+ int i;
+ if (!pr)
+ return;
+ if (JS_IsObject(pr->value)) {
+ if (JS_IsArray(ctx, pr->value)) {
+ for(i = 0; i < pr->u.array.count; i++) {
+ json_free_parse_record(ctx, &pr->u.array.elements[i]);
+ }
+ js_free(ctx, pr->u.array.elements);
+ } else {
+ for(i = 0; i < pr->u.obj.count; i++) {
+ JS_FreeAtom(ctx, pr->u.obj.entries[i].atom);
+ json_free_parse_record(ctx, &pr->u.obj.entries[i].parse_record);
+ }
+ js_free(ctx, pr->u.obj.entries);
+ js_free(ctx, pr->u.obj.hash_table);
+ }
+ }
+ JS_FreeValue(ctx, pr->value);
+ pr->value = JS_UNDEFINED; /* fail safe */
+}
+
+/* 'pr' can be NULL */
+static JSValue json_parse_value(JSParseState *s, JSONParseRecord *pr)
{
JSContext *ctx = s->ctx;
JSValue val = JS_NULL;
int ret;
+ if (pr) {
+ pr->value = JS_UNDEFINED;
+ }
+
switch(s->token.val) {
case '{':
{
JSValue prop_val;
JSAtom prop_name;
-
+ JSONParseRecord *pr1;
+ int pr_size;
+
if (json_next_token(s))
goto fail;
val = JS_NewObject(ctx);
if (JS_IsException(val))
goto fail;
+ if (pr) {
+ json_parse_record_init_obj(ctx, pr, val);
+ pr_size = 0;
+ }
if (s->token.val != '}') {
for(;;) {
if (s->token.val == TOK_STRING) {
goto fail1;
if (json_parse_expect(s, ':'))
goto fail1;
- prop_val = json_parse_value(s);
+ if (pr) {
+ pr1 = json_parse_record_add(ctx, pr, prop_name, &pr_size);
+ if (!pr1)
+ goto fail1;
+ } else {
+ pr1 = NULL;
+ }
+ prop_val = json_parse_value(s, pr1);
if (JS_IsException(prop_val)) {
fail1:
JS_FreeAtom(ctx, prop_name);
{
JSValue el;
uint32_t idx;
-
+ JSONParseRecord *pr1;
+ int pr_size;
+
if (json_next_token(s))
goto fail;
val = JS_NewArray(ctx);
if (JS_IsException(val))
goto fail;
+ if (pr) {
+ json_parse_record_init_array(ctx, pr, val);
+ pr_size = 0;
+ }
if (s->token.val != ']') {
idx = 0;
for(;;) {
- el = json_parse_value(s);
+ if (pr) {
+ if (js_resize_array(ctx, (void **)&pr->u.array.elements, sizeof(pr->u.array.elements[0]),
+ &pr_size, pr->u.array.count + 1))
+ goto fail;
+ pr1 = &pr->u.array.elements[pr->u.array.count++];
+ pr1->value = JS_UNDEFINED;
+ } else {
+ pr1 = NULL;
+ }
+ el = json_parse_value(s, pr1);
if (JS_IsException(el))
goto fail;
ret = JS_DefinePropertyValueUint32(ctx, val, idx, el, JS_PROP_C_W_E);
break;
case TOK_STRING:
val = JS_DupValue(ctx, s->token.u.str.str);
+ if (pr) {
+ json_parse_record_init_primitive(ctx, pr, val,
+ s->token.ptr - s->buf_start,
+ s->buf_ptr - s->token.ptr);
+ }
if (json_next_token(s))
goto fail;
break;
case TOK_NUMBER:
val = s->token.u.num.val;
+ if (pr) {
+ json_parse_record_init_primitive(ctx, pr, val,
+ s->token.ptr - s->buf_start,
+ s->buf_ptr - s->token.ptr);
+ }
if (json_next_token(s))
goto fail;
break;
if (s->token.u.ident.atom == JS_ATOM_false ||
s->token.u.ident.atom == JS_ATOM_true) {
val = JS_NewBool(ctx, s->token.u.ident.atom == JS_ATOM_true);
+ if (pr) {
+ json_parse_record_init_primitive(ctx, pr, val,
+ s->token.ptr - s->buf_start,
+ s->buf_ptr - s->token.ptr);
+ }
} else if (s->token.u.ident.atom == JS_ATOM_null) {
val = JS_NULL;
+ if (pr) {
+ json_parse_record_init_primitive(ctx, pr, val,
+ s->token.ptr - s->buf_start,
+ s->buf_ptr - s->token.ptr);
+ }
} else if (s->token.u.ident.atom == JS_ATOM_NaN && s->ext_json) {
/* Note: json5 identifier handling is ambiguous e.g. is
'{ NaN: 1 }' a valid JSON5 production ? */
}
return val;
fail:
+ json_free_parse_record(ctx, pr);
JS_FreeValue(ctx, val);
return JS_EXCEPTION;
}
-JSValue JS_ParseJSON2(JSContext *ctx, const char *buf, size_t buf_len,
- const char *filename, int flags)
+JSValue JS_ParseJSON3(JSContext *ctx, const char *buf, size_t buf_len,
+ const char *filename, int flags, JSONParseRecord *pr)
{
JSParseState s1, *s = &s1;
JSValue val = JS_UNDEFINED;
s->ext_json = ((flags & JS_PARSE_JSON_EXT) != 0);
if (json_next_token(s))
goto fail;
- val = json_parse_value(s);
+ val = json_parse_value(s, pr);
if (JS_IsException(val))
goto fail;
if (s->token.val != TOK_EOF) {
- if (js_parse_error(s, "unexpected data at the end"))
+ if (js_parse_error(s, "unexpected data at the end")) {
+ json_free_parse_record(ctx, pr);
goto fail;
+ }
}
return val;
fail:
return JS_EXCEPTION;
}
+JSValue JS_ParseJSON2(JSContext *ctx, const char *buf, size_t buf_len,
+ const char *filename, int flags)
+{
+ return JS_ParseJSON3(ctx, buf, buf_len, filename, flags, NULL);
+}
+
JSValue JS_ParseJSON(JSContext *ctx, const char *buf, size_t buf_len,
const char *filename)
{
- return JS_ParseJSON2(ctx, buf, buf_len, filename, 0);
+ return JS_ParseJSON3(ctx, buf, buf_len, filename, 0, NULL);
}
+/* if pr != NULL, then pr->value = holder by construction */
static JSValue internalize_json_property(JSContext *ctx, JSValueConst holder,
- JSAtom name, JSValueConst reviver)
+ JSAtom name, JSValueConst reviver,
+ const char *text_str, JSONParseRecord *pr)
{
- JSValue val, new_el, name_val, res;
- JSValueConst args[2];
+ JSValue val, new_el, name_val, res, context;
+ JSValueConst args[3];
int ret, is_array;
uint32_t i, len = 0;
JSAtom prop;
val = JS_GetProperty(ctx, holder, name);
if (JS_IsException(val))
return val;
+
+ if (pr) {
+ if (JS_IsArray(ctx, pr->value)) {
+ if (__JS_AtomIsTaggedInt(name)) {
+ uint32_t idx = __JS_AtomToUInt32(name);
+ if (idx < pr->u.array.count) {
+ pr = &pr->u.array.elements[idx];
+ } else {
+ pr = NULL;
+ }
+ }
+ } else {
+ pr = json_parse_record_find(pr, name);
+ }
+ if (pr && !js_same_value(ctx, pr->value, val)) {
+ pr = NULL;
+ }
+ }
+
+ context = JS_NewObject(ctx);
+ if (JS_IsException(context))
+ goto fail;
+
if (JS_IsObject(val)) {
is_array = JS_IsArray(ctx, val);
if (is_array < 0)
} else {
prop = JS_DupAtom(ctx, atoms[i].atom);
}
- new_el = internalize_json_property(ctx, val, prop, reviver);
+ new_el = internalize_json_property(ctx, val, prop, reviver, text_str, pr);
if (JS_IsException(new_el)) {
JS_FreeAtom(ctx, prop);
goto fail;
if (ret < 0)
goto fail;
}
+ } else {
+ if (pr) {
+ new_el = JS_NewStringLen(ctx, text_str + pr->u.primitive.source_pos,
+ pr->u.primitive.source_len);
+ if (JS_IsException(new_el))
+ goto fail;
+ if (JS_DefinePropertyValue(ctx, context, JS_ATOM_source, new_el, JS_PROP_C_W_E) < 0)
+ goto fail;
+ }
}
JS_FreePropertyEnum(ctx, atoms, len);
atoms = NULL;
goto fail;
args[0] = name_val;
args[1] = val;
- res = JS_Call(ctx, reviver, holder, 2, args);
+ args[2] = context;
+ res = JS_Call(ctx, reviver, holder, 3, args);
JS_FreeValue(ctx, name_val);
JS_FreeValue(ctx, val);
+ JS_FreeValue(ctx, context);
return res;
fail:
JS_FreePropertyEnum(ctx, atoms, len);
+ JS_FreeValue(ctx, context);
JS_FreeValue(ctx, val);
return JS_EXCEPTION;
}
static JSValue js_json_parse(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
- JSValue obj, root;
- JSValueConst reviver;
+ JSValue obj;
const char *str;
size_t len;
-
+
str = JS_ToCStringLen(ctx, &len, argv[0]);
if (!str)
return JS_EXCEPTION;
- obj = JS_ParseJSON(ctx, str, len, "<input>");
- JS_FreeCString(ctx, str);
- if (JS_IsException(obj))
- return obj;
if (argc > 1 && JS_IsFunction(ctx, argv[1])) {
+ JSONParseRecord pr_s, *pr = &pr_s, *pr1;
+ JSValue root;
+ JSValueConst reviver;
+ int size;
+
reviver = argv[1];
root = JS_NewObject(ctx);
- if (JS_IsException(root)) {
- JS_FreeValue(ctx, obj);
- return JS_EXCEPTION;
- }
+ if (JS_IsException(root))
+ goto fail;
+ json_parse_record_init_obj(ctx, pr, root);
+ size = 0;
+ pr1 = json_parse_record_add(ctx, pr, JS_ATOM_empty_string, &size);
+ if (!pr1)
+ goto fail1;
+
+ obj = JS_ParseJSON3(ctx, str, len, "<input>", 0, pr1);
+ if (JS_IsException(obj))
+ goto fail1;
+
if (JS_DefinePropertyValue(ctx, root, JS_ATOM_empty_string, obj,
JS_PROP_C_W_E) < 0) {
+ JS_FreeValue(ctx, obj);
+ fail1:
+ json_free_parse_record(ctx, pr);
JS_FreeValue(ctx, root);
- return JS_EXCEPTION;
+ goto fail;
}
+
obj = internalize_json_property(ctx, root, JS_ATOM_empty_string,
- reviver);
+ reviver, str, pr);
+ json_free_parse_record(ctx, pr);
JS_FreeValue(ctx, root);
+ } else {
+ obj = JS_ParseJSON3(ctx, str, len, "<input>", 0, NULL);
}
+ JS_FreeCString(ctx, str);
return obj;
+ fail:
+ JS_FreeCString(ctx, str);
+ return JS_EXCEPTION;
+}
+
+static JSValue js_json_isRawJSON(JSContext *ctx, JSValueConst this_val,
+ int argc, JSValueConst *argv)
+{
+ JSValueConst obj = argv[0];
+ if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) {
+ JSObject *p = JS_VALUE_GET_OBJ(obj);
+ return JS_NewBool(ctx, p->class_id == JS_CLASS_RAWJSON);
+ } else {
+ return JS_FALSE;
+ }
}
+static BOOL is_valid_raw_json_char(int c)
+{
+ return ((c >= 'a' && c <= 'z') ||
+ (c >= '0' && c <= '9') ||
+ c == '-' ||
+ c == '"');
+}
+
+static JSValue js_json_rawJSON(JSContext *ctx, JSValueConst this_val,
+ int argc, JSValueConst *argv)
+{
+ JSValue str, res, obj;
+ JSString *p;
+ str = JS_ToString(ctx, argv[0]);
+ if (JS_IsException(str))
+ return str;
+ p = JS_VALUE_GET_STRING(str);
+ if (p->len == 0 ||
+ !is_valid_raw_json_char(string_get(p, 0)) ||
+ !is_valid_raw_json_char(string_get(p, p->len - 1))) {
+ goto syntax_error;
+ }
+ res = js_json_parse(ctx, JS_UNDEFINED, 1, (JSValueConst *)&str);
+ if (JS_IsException(res)) {
+ syntax_error:
+ JS_ThrowSyntaxError(ctx, "invalid rawJSON string");
+ goto fail;
+ }
+ JS_FreeValue(ctx, res);
+
+ obj = JS_NewObjectProtoClass(ctx, JS_NULL, JS_CLASS_RAWJSON);
+ if (JS_IsException(obj))
+ goto fail;
+ if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_rawJSON, str, JS_PROP_ENUMERABLE) < 0) {
+ JS_FreeValue(ctx, obj);
+ return JS_EXCEPTION;
+ }
+ JS_PreventExtensions(ctx, obj);
+ return obj;
+ fail:
+ JS_FreeValue(ctx, str);
+ return JS_EXCEPTION;
+}
+
+
typedef struct JSONStringifyContext {
JSValueConst replacer_func;
JSValue stack;
if (JS_IsException(val))
goto exception;
goto concat_primitive;
- } else if (cl == JS_CLASS_BOOLEAN || cl == JS_CLASS_BIG_INT)
- {
+ } else if (cl == JS_CLASS_BOOLEAN || cl == JS_CLASS_BIG_INT) {
/* This will thow the same error as for the primitive object */
set_value(ctx, &val, JS_DupValue(ctx, p->u.object_data));
goto concat_primitive;
+ } else if (cl == JS_CLASS_RAWJSON) {
+ JSValue val1;
+ val1 = JS_GetProperty(ctx, val, JS_ATOM_rawJSON);
+ if (JS_IsException(val1))
+ goto exception;
+ JS_FreeValue(ctx, val);
+ val = val1;
+ goto concat_value;
}
v = js_array_includes(ctx, jsc->stack, 1, (JSValueConst *)&val);
if (JS_IsException(v))
}
static const JSCFunctionListEntry js_json_funcs[] = {
+ JS_CFUNC_DEF("isRawJSON", 1, js_json_isRawJSON ),
JS_CFUNC_DEF("parse", 2, js_json_parse ),
+ JS_CFUNC_DEF("rawJSON", 1, js_json_rawJSON ),
JS_CFUNC_DEF("stringify", 3, js_json_stringify ),
JS_PROP_STRING_DEF("[Symbol.toStringTag]", "JSON", JS_PROP_CONFIGURABLE ),
};