forked from KolibriOS/kolibrios
415 lines
8.7 KiB
C
415 lines
8.7 KiB
C
|
#include "jsi.h"
|
||
|
#include "jslex.h"
|
||
|
#include "jsvalue.h"
|
||
|
#include "jsbuiltin.h"
|
||
|
|
||
|
#include "utf.h"
|
||
|
|
||
|
int js_isnumberobject(js_State *J, int idx)
|
||
|
{
|
||
|
return js_isobject(J, idx) && js_toobject(J, idx)->type == JS_CNUMBER;
|
||
|
}
|
||
|
|
||
|
int js_isstringobject(js_State *J, int idx)
|
||
|
{
|
||
|
return js_isobject(J, idx) && js_toobject(J, idx)->type == JS_CSTRING;
|
||
|
}
|
||
|
|
||
|
static void jsonnext(js_State *J)
|
||
|
{
|
||
|
J->lookahead = jsY_lexjson(J);
|
||
|
}
|
||
|
|
||
|
static int jsonaccept(js_State *J, int t)
|
||
|
{
|
||
|
if (J->lookahead == t) {
|
||
|
jsonnext(J);
|
||
|
return 1;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void jsonexpect(js_State *J, int t)
|
||
|
{
|
||
|
if (!jsonaccept(J, t))
|
||
|
js_syntaxerror(J, "JSON: unexpected token: %s (expected %s)",
|
||
|
jsY_tokenstring(J->lookahead), jsY_tokenstring(t));
|
||
|
}
|
||
|
|
||
|
static void jsonvalue(js_State *J)
|
||
|
{
|
||
|
int i;
|
||
|
const char *name;
|
||
|
|
||
|
switch (J->lookahead) {
|
||
|
case TK_STRING:
|
||
|
js_pushstring(J, J->text);
|
||
|
jsonnext(J);
|
||
|
break;
|
||
|
|
||
|
case TK_NUMBER:
|
||
|
js_pushnumber(J, J->number);
|
||
|
jsonnext(J);
|
||
|
break;
|
||
|
|
||
|
case '{':
|
||
|
js_newobject(J);
|
||
|
jsonnext(J);
|
||
|
if (jsonaccept(J, '}'))
|
||
|
return;
|
||
|
do {
|
||
|
if (J->lookahead != TK_STRING)
|
||
|
js_syntaxerror(J, "JSON: unexpected token: %s (expected string)", jsY_tokenstring(J->lookahead));
|
||
|
name = J->text;
|
||
|
jsonnext(J);
|
||
|
jsonexpect(J, ':');
|
||
|
jsonvalue(J);
|
||
|
js_setproperty(J, -2, name);
|
||
|
} while (jsonaccept(J, ','));
|
||
|
jsonexpect(J, '}');
|
||
|
break;
|
||
|
|
||
|
case '[':
|
||
|
js_newarray(J);
|
||
|
jsonnext(J);
|
||
|
i = 0;
|
||
|
if (jsonaccept(J, ']'))
|
||
|
return;
|
||
|
do {
|
||
|
jsonvalue(J);
|
||
|
js_setindex(J, -2, i++);
|
||
|
} while (jsonaccept(J, ','));
|
||
|
jsonexpect(J, ']');
|
||
|
break;
|
||
|
|
||
|
case TK_TRUE:
|
||
|
js_pushboolean(J, 1);
|
||
|
jsonnext(J);
|
||
|
break;
|
||
|
|
||
|
case TK_FALSE:
|
||
|
js_pushboolean(J, 0);
|
||
|
jsonnext(J);
|
||
|
break;
|
||
|
|
||
|
case TK_NULL:
|
||
|
js_pushnull(J);
|
||
|
jsonnext(J);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
js_syntaxerror(J, "JSON: unexpected token: %s", jsY_tokenstring(J->lookahead));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void jsonrevive(js_State *J, const char *name)
|
||
|
{
|
||
|
const char *key;
|
||
|
char buf[32];
|
||
|
|
||
|
/* revive is in 2 */
|
||
|
/* holder is in -1 */
|
||
|
|
||
|
js_getproperty(J, -1, name); /* get value from holder */
|
||
|
|
||
|
if (js_isobject(J, -1)) {
|
||
|
if (js_isarray(J, -1)) {
|
||
|
int i = 0;
|
||
|
int n = js_getlength(J, -1);
|
||
|
for (i = 0; i < n; ++i) {
|
||
|
jsonrevive(J, js_itoa(buf, i));
|
||
|
if (js_isundefined(J, -1)) {
|
||
|
js_pop(J, 1);
|
||
|
js_delproperty(J, -1, buf);
|
||
|
} else {
|
||
|
js_setproperty(J, -2, buf);
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
js_pushiterator(J, -1, 1);
|
||
|
while ((key = js_nextiterator(J, -1))) {
|
||
|
js_rot2(J);
|
||
|
jsonrevive(J, key);
|
||
|
if (js_isundefined(J, -1)) {
|
||
|
js_pop(J, 1);
|
||
|
js_delproperty(J, -1, key);
|
||
|
} else {
|
||
|
js_setproperty(J, -2, key);
|
||
|
}
|
||
|
js_rot2(J);
|
||
|
}
|
||
|
js_pop(J, 1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
js_copy(J, 2); /* reviver function */
|
||
|
js_copy(J, -3); /* holder as this */
|
||
|
js_pushstring(J, name); /* name */
|
||
|
js_copy(J, -4); /* value */
|
||
|
js_call(J, 2);
|
||
|
js_rot2pop1(J); /* pop old value, leave new value on stack */
|
||
|
}
|
||
|
|
||
|
static void JSON_parse(js_State *J)
|
||
|
{
|
||
|
const char *source = js_tostring(J, 1);
|
||
|
jsY_initlex(J, "JSON", source);
|
||
|
jsonnext(J);
|
||
|
|
||
|
if (js_iscallable(J, 2)) {
|
||
|
js_newobject(J);
|
||
|
jsonvalue(J);
|
||
|
js_defproperty(J, -2, "", 0);
|
||
|
jsonrevive(J, "");
|
||
|
} else {
|
||
|
jsonvalue(J);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void fmtnum(js_State *J, js_Buffer **sb, double n)
|
||
|
{
|
||
|
if (isnan(n)) js_puts(J, sb, "null");
|
||
|
else if (isinf(n)) js_puts(J, sb, "null");
|
||
|
else if (n == 0) js_puts(J, sb, "0");
|
||
|
else {
|
||
|
char buf[40];
|
||
|
js_puts(J, sb, jsV_numbertostring(J, buf, n));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void fmtstr(js_State *J, js_Buffer **sb, const char *s)
|
||
|
{
|
||
|
static const char *HEX = "0123456789ABCDEF";
|
||
|
int i, n;
|
||
|
Rune c;
|
||
|
js_putc(J, sb, '"');
|
||
|
while (*s) {
|
||
|
n = chartorune(&c, s);
|
||
|
switch (c) {
|
||
|
case '"': js_puts(J, sb, "\\\""); break;
|
||
|
case '\\': js_puts(J, sb, "\\\\"); break;
|
||
|
case '\b': js_puts(J, sb, "\\b"); break;
|
||
|
case '\f': js_puts(J, sb, "\\f"); break;
|
||
|
case '\n': js_puts(J, sb, "\\n"); break;
|
||
|
case '\r': js_puts(J, sb, "\\r"); break;
|
||
|
case '\t': js_puts(J, sb, "\\t"); break;
|
||
|
default:
|
||
|
if (c < ' ') {
|
||
|
js_putc(J, sb, '\\');
|
||
|
js_putc(J, sb, 'u');
|
||
|
js_putc(J, sb, HEX[(c>>12)&15]);
|
||
|
js_putc(J, sb, HEX[(c>>8)&15]);
|
||
|
js_putc(J, sb, HEX[(c>>4)&15]);
|
||
|
js_putc(J, sb, HEX[c&15]);
|
||
|
} else if (c < 128) {
|
||
|
js_putc(J, sb, c);
|
||
|
} else {
|
||
|
for (i = 0; i < n; ++i)
|
||
|
js_putc(J, sb, s[i]);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
s += n;
|
||
|
}
|
||
|
js_putc(J, sb, '"');
|
||
|
}
|
||
|
|
||
|
static void fmtindent(js_State *J, js_Buffer **sb, const char *gap, int level)
|
||
|
{
|
||
|
js_putc(J, sb, '\n');
|
||
|
while (level--)
|
||
|
js_puts(J, sb, gap);
|
||
|
}
|
||
|
|
||
|
static int fmtvalue(js_State *J, js_Buffer **sb, const char *key, const char *gap, int level);
|
||
|
|
||
|
static int filterprop(js_State *J, const char *key)
|
||
|
{
|
||
|
int i, n, found;
|
||
|
/* replacer/property-list is in stack slot 2 */
|
||
|
if (js_isarray(J, 2)) {
|
||
|
found = 0;
|
||
|
n = js_getlength(J, 2);
|
||
|
for (i = 0; i < n && !found; ++i) {
|
||
|
js_getindex(J, 2, i);
|
||
|
if (js_isstring(J, -1) || js_isnumber(J, -1) ||
|
||
|
js_isstringobject(J, -1) || js_isnumberobject(J, -1))
|
||
|
found = !strcmp(key, js_tostring(J, -1));
|
||
|
js_pop(J, 1);
|
||
|
}
|
||
|
return found;
|
||
|
}
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
static void fmtobject(js_State *J, js_Buffer **sb, js_Object *obj, const char *gap, int level)
|
||
|
{
|
||
|
const char *key;
|
||
|
int save;
|
||
|
int i, n;
|
||
|
|
||
|
n = js_gettop(J) - 1;
|
||
|
for (i = 4; i < n; ++i)
|
||
|
if (js_isobject(J, i))
|
||
|
if (js_toobject(J, i) == js_toobject(J, -1))
|
||
|
js_typeerror(J, "cyclic object value");
|
||
|
|
||
|
n = 0;
|
||
|
js_putc(J, sb, '{');
|
||
|
js_pushiterator(J, -1, 1);
|
||
|
while ((key = js_nextiterator(J, -1))) {
|
||
|
if (filterprop(J, key)) {
|
||
|
save = (*sb)->n;
|
||
|
if (n) js_putc(J, sb, ',');
|
||
|
if (gap) fmtindent(J, sb, gap, level + 1);
|
||
|
fmtstr(J, sb, key);
|
||
|
js_putc(J, sb, ':');
|
||
|
if (gap)
|
||
|
js_putc(J, sb, ' ');
|
||
|
js_rot2(J);
|
||
|
if (!fmtvalue(J, sb, key, gap, level + 1))
|
||
|
(*sb)->n = save;
|
||
|
else
|
||
|
++n;
|
||
|
js_rot2(J);
|
||
|
}
|
||
|
}
|
||
|
js_pop(J, 1);
|
||
|
if (gap && n) fmtindent(J, sb, gap, level);
|
||
|
js_putc(J, sb, '}');
|
||
|
}
|
||
|
|
||
|
static void fmtarray(js_State *J, js_Buffer **sb, const char *gap, int level)
|
||
|
{
|
||
|
int n, i;
|
||
|
char buf[32];
|
||
|
|
||
|
n = js_gettop(J) - 1;
|
||
|
for (i = 4; i < n; ++i)
|
||
|
if (js_isobject(J, i))
|
||
|
if (js_toobject(J, i) == js_toobject(J, -1))
|
||
|
js_typeerror(J, "cyclic object value");
|
||
|
|
||
|
js_putc(J, sb, '[');
|
||
|
n = js_getlength(J, -1);
|
||
|
for (i = 0; i < n; ++i) {
|
||
|
if (i) js_putc(J, sb, ',');
|
||
|
if (gap) fmtindent(J, sb, gap, level + 1);
|
||
|
if (!fmtvalue(J, sb, js_itoa(buf, i), gap, level + 1))
|
||
|
js_puts(J, sb, "null");
|
||
|
}
|
||
|
if (gap && n) fmtindent(J, sb, gap, level);
|
||
|
js_putc(J, sb, ']');
|
||
|
}
|
||
|
|
||
|
static int fmtvalue(js_State *J, js_Buffer **sb, const char *key, const char *gap, int level)
|
||
|
{
|
||
|
/* replacer/property-list is in 2 */
|
||
|
/* holder is in -1 */
|
||
|
|
||
|
js_getproperty(J, -1, key);
|
||
|
|
||
|
if (js_isobject(J, -1)) {
|
||
|
if (js_hasproperty(J, -1, "toJSON")) {
|
||
|
if (js_iscallable(J, -1)) {
|
||
|
js_copy(J, -2);
|
||
|
js_pushstring(J, key);
|
||
|
js_call(J, 1);
|
||
|
js_rot2pop1(J);
|
||
|
} else {
|
||
|
js_pop(J, 1);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (js_iscallable(J, 2)) {
|
||
|
js_copy(J, 2); /* replacer function */
|
||
|
js_copy(J, -3); /* holder as this */
|
||
|
js_pushstring(J, key); /* name */
|
||
|
js_copy(J, -4); /* old value */
|
||
|
js_call(J, 2);
|
||
|
js_rot2pop1(J); /* pop old value, leave new value on stack */
|
||
|
}
|
||
|
|
||
|
if (js_isobject(J, -1) && !js_iscallable(J, -1)) {
|
||
|
js_Object *obj = js_toobject(J, -1);
|
||
|
switch (obj->type) {
|
||
|
case JS_CNUMBER: fmtnum(J, sb, obj->u.number); break;
|
||
|
case JS_CSTRING: fmtstr(J, sb, obj->u.s.string); break;
|
||
|
case JS_CBOOLEAN: js_puts(J, sb, obj->u.boolean ? "true" : "false"); break;
|
||
|
case JS_CARRAY: fmtarray(J, sb, gap, level); break;
|
||
|
default: fmtobject(J, sb, obj, gap, level); break;
|
||
|
}
|
||
|
}
|
||
|
else if (js_isboolean(J, -1))
|
||
|
js_puts(J, sb, js_toboolean(J, -1) ? "true" : "false");
|
||
|
else if (js_isnumber(J, -1))
|
||
|
fmtnum(J, sb, js_tonumber(J, -1));
|
||
|
else if (js_isstring(J, -1))
|
||
|
fmtstr(J, sb, js_tostring(J, -1));
|
||
|
else if (js_isnull(J, -1))
|
||
|
js_puts(J, sb, "null");
|
||
|
else {
|
||
|
js_pop(J, 1);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
js_pop(J, 1);
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
static void JSON_stringify(js_State *J)
|
||
|
{
|
||
|
js_Buffer *sb = NULL;
|
||
|
char buf[12];
|
||
|
const char *s, *gap;
|
||
|
int n;
|
||
|
|
||
|
gap = NULL;
|
||
|
|
||
|
if (js_isnumber(J, 3) || js_isnumberobject(J, 3)) {
|
||
|
n = js_tointeger(J, 3);
|
||
|
if (n < 0) n = 0;
|
||
|
if (n > 10) n = 10;
|
||
|
memset(buf, ' ', n);
|
||
|
buf[n] = 0;
|
||
|
if (n > 0) gap = buf;
|
||
|
} else if (js_isstring(J, 3) || js_isstringobject(J, 3)) {
|
||
|
s = js_tostring(J, 3);
|
||
|
n = strlen(s);
|
||
|
if (n > 10) n = 10;
|
||
|
memcpy(buf, s, n);
|
||
|
buf[n] = 0;
|
||
|
if (n > 0) gap = buf;
|
||
|
}
|
||
|
|
||
|
if (js_try(J)) {
|
||
|
js_free(J, sb);
|
||
|
js_throw(J);
|
||
|
}
|
||
|
|
||
|
js_newobject(J); /* wrapper */
|
||
|
js_copy(J, 1);
|
||
|
js_defproperty(J, -2, "", 0);
|
||
|
if (!fmtvalue(J, &sb, "", gap, 0)) {
|
||
|
js_pushundefined(J);
|
||
|
} else {
|
||
|
js_putc(J, &sb, 0);
|
||
|
js_pushstring(J, sb ? sb->s : "");
|
||
|
js_rot2pop1(J);
|
||
|
}
|
||
|
|
||
|
js_endtry(J);
|
||
|
js_free(J, sb);
|
||
|
}
|
||
|
|
||
|
void jsB_initjson(js_State *J)
|
||
|
{
|
||
|
js_pushobject(J, jsV_newobject(J, JS_CJSON, J->Object_prototype));
|
||
|
{
|
||
|
jsB_propf(J, "JSON.parse", JSON_parse, 2);
|
||
|
jsB_propf(J, "JSON.stringify", JSON_stringify, 3);
|
||
|
}
|
||
|
js_defglobal(J, "JSON", JS_DONTENUM);
|
||
|
}
|