wine/dlls/jscript/json.c
Arkadiusz Hiler f8f119f565 jscript: Fix JSON.stringify for arrays longer than 10.
We've been passing `ARRAY_SIZE()` (equal to 16 in this case) as a radix
to _itow(). This has caused us trying to get "a" instead of "10", etc.
which resulted in unexpected nulls being interspersed in long arrays as
well as the arrays being cut short.
2025-02-06 16:00:51 +01:00

1044 lines
26 KiB
C

/*
* Copyright 2016 Jacek Caban for CodeWeavers
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include <math.h>
#include <assert.h>
#include "jscript.h"
#include "parser.h"
#include "wine/debug.h"
WINE_DEFAULT_DEBUG_CHANNEL(jscript);
typedef struct {
const WCHAR *ptr;
const WCHAR *end;
script_ctx_t *ctx;
} json_parse_ctx_t;
static BOOL is_json_space(WCHAR c)
{
return c == ' ' || c == '\t' || c == '\n' || c == '\r';
}
static WCHAR skip_spaces(json_parse_ctx_t *ctx)
{
while(is_json_space(*ctx->ptr))
ctx->ptr++;
return *ctx->ptr;
}
static BOOL is_keyword(json_parse_ctx_t *ctx, const WCHAR *keyword)
{
unsigned i;
for(i=0; keyword[i]; i++) {
if(!ctx->ptr[i] || keyword[i] != ctx->ptr[i])
return FALSE;
}
if(is_identifier_char(ctx->ptr[i]))
return FALSE;
ctx->ptr += i;
return TRUE;
}
static BOOL unescape_json_string(WCHAR *str, size_t *len)
{
WCHAR *pd, *p, c, *end = str + *len;
int i;
pd = p = str;
while(p < end) {
if(*p != '\\') {
*pd++ = *p++;
continue;
}
if(++p == end)
return FALSE;
switch(*p) {
case '\"':
case '\\':
case '/':
c = *p;
break;
case 'b':
c = '\b';
break;
case 't':
c = '\t';
break;
case 'n':
c = '\n';
break;
case 'f':
c = '\f';
break;
case 'r':
c = '\r';
break;
case 'u':
if(p + 4 >= end)
return FALSE;
i = hex_to_int(*++p);
if(i == -1)
return FALSE;
c = i << 12;
i = hex_to_int(*++p);
if(i == -1)
return FALSE;
c += i << 8;
i = hex_to_int(*++p);
if(i == -1)
return FALSE;
c += i << 4;
i = hex_to_int(*++p);
if(i == -1)
return FALSE;
c += i;
break;
default:
return FALSE;
}
*pd++ = c;
p++;
}
*len = pd - str;
return TRUE;
}
/* ECMA-262 5.1 Edition 15.12.1.1 */
static HRESULT parse_json_string(json_parse_ctx_t *ctx, WCHAR **r)
{
const WCHAR *ptr = ++ctx->ptr;
size_t len;
WCHAR *buf;
while(*ctx->ptr && *ctx->ptr != '"') {
if(*ctx->ptr++ == '\\')
ctx->ptr++;
}
if(!*ctx->ptr) {
FIXME("unterminated string\n");
return E_FAIL;
}
len = ctx->ptr-ptr;
buf = malloc((len+1)*sizeof(WCHAR));
if(!buf)
return E_OUTOFMEMORY;
if(len)
memcpy(buf, ptr, len*sizeof(WCHAR));
if(!(ctx->ctx->html_mode ? unescape_json_string(buf, &len) : unescape(buf, &len))) {
WARN("unescape failed\n");
free(buf);
return JS_E_INVALID_CHAR;
}
buf[len] = 0;
ctx->ptr++;
*r = buf;
return S_OK;
}
/* ECMA-262 5.1 Edition 15.12.1.2 */
static HRESULT parse_json_value(json_parse_ctx_t *ctx, jsval_t *r)
{
HRESULT hres;
switch(skip_spaces(ctx)) {
/* JSONNullLiteral */
case 'n':
if(!is_keyword(ctx, L"null"))
break;
*r = jsval_null();
return S_OK;
/* JSONBooleanLiteral */
case 't':
if(!is_keyword(ctx, L"true"))
break;
*r = jsval_bool(TRUE);
return S_OK;
case 'f':
if(!is_keyword(ctx, L"false"))
break;
*r = jsval_bool(FALSE);
return S_OK;
/* JSONObject */
case '{': {
WCHAR *prop_name;
jsdisp_t *obj;
jsval_t val;
hres = create_object(ctx->ctx, NULL, &obj);
if(FAILED(hres))
return hres;
ctx->ptr++;
if(skip_spaces(ctx) == '}') {
ctx->ptr++;
*r = jsval_obj(obj);
return S_OK;
}
while(1) {
if(*ctx->ptr != '"')
break;
hres = parse_json_string(ctx, &prop_name);
if(FAILED(hres))
break;
if(skip_spaces(ctx) != ':') {
FIXME("missing ':'\n");
free(prop_name);
break;
}
ctx->ptr++;
hres = parse_json_value(ctx, &val);
if(SUCCEEDED(hres)) {
hres = jsdisp_propput_name(obj, prop_name, val);
jsval_release(val);
}
free(prop_name);
if(FAILED(hres))
break;
if(skip_spaces(ctx) == '}') {
ctx->ptr++;
*r = jsval_obj(obj);
return S_OK;
}
if(*ctx->ptr++ != ',') {
FIXME("expected ','\n");
break;
}
skip_spaces(ctx);
}
jsdisp_release(obj);
break;
}
/* JSONString */
case '"': {
WCHAR *string;
jsstr_t *str;
hres = parse_json_string(ctx, &string);
if(FAILED(hres))
return hres;
/* FIXME: avoid reallocation */
str = jsstr_alloc(string);
free(string);
if(!str)
return E_OUTOFMEMORY;
*r = jsval_string(str);
return S_OK;
}
/* JSONArray */
case '[': {
jsdisp_t *array;
unsigned i = 0;
jsval_t val;
hres = create_array(ctx->ctx, 0, &array);
if(FAILED(hres))
return hres;
ctx->ptr++;
if(skip_spaces(ctx) == ']') {
ctx->ptr++;
*r = jsval_obj(array);
return S_OK;
}
while(1) {
hres = parse_json_value(ctx, &val);
if(FAILED(hres))
break;
hres = jsdisp_propput_idx(array, i, val);
jsval_release(val);
if(FAILED(hres))
break;
if(skip_spaces(ctx) == ']') {
ctx->ptr++;
*r = jsval_obj(array);
return S_OK;
}
if(*ctx->ptr != ',') {
FIXME("expected ','\n");
break;
}
ctx->ptr++;
i++;
}
jsdisp_release(array);
break;
}
/* JSONNumber */
default: {
int sign = 1;
double n;
if(*ctx->ptr == '-') {
sign = -1;
ctx->ptr++;
skip_spaces(ctx);
}
if(*ctx->ptr == '0' && ctx->ptr + 1 < ctx->end && is_digit(ctx->ptr[1]))
break;
hres = parse_decimal(&ctx->ptr, ctx->end, &n);
if(FAILED(hres))
break;
*r = jsval_number(sign*n);
return S_OK;
}
}
FIXME("Syntax error at %s\n", debugstr_w(ctx->ptr));
return E_FAIL;
}
struct transform_json_object_ctx
{
script_ctx_t *ctx;
IDispatch *reviver;
HRESULT hres;
};
static jsval_t transform_json_object(struct transform_json_object_ctx *proc_ctx, jsdisp_t *holder, jsstr_t *name)
{
jsval_t res, args[2];
const WCHAR *str;
if(!(str = jsstr_flatten(name)))
proc_ctx->hres = E_OUTOFMEMORY;
else
proc_ctx->hres = jsdisp_propget_name(holder, str, &args[1]);
if(FAILED(proc_ctx->hres))
return jsval_undefined();
if(is_object_instance(args[1])) {
jsdisp_t *obj = to_jsdisp(get_object(args[1]));
jsstr_t *jsstr;
DISPID id;
BOOL b;
if(!obj) {
FIXME("non-JS obj in JSON object: %p\n", get_object(args[1]));
proc_ctx->hres = E_NOTIMPL;
goto ret;
}else if(is_class(obj, JSCLASS_ARRAY)) {
unsigned i, length = array_get_length(obj);
WCHAR buf[14], *buf_end;
buf_end = buf + ARRAY_SIZE(buf) - 1;
*buf_end-- = 0;
for(i = 0; i < length; i++) {
str = idx_to_str(i, buf_end);
if(!(jsstr = jsstr_alloc(str))) {
proc_ctx->hres = E_OUTOFMEMORY;
goto ret;
}
res = transform_json_object(proc_ctx, obj, jsstr);
jsstr_release(jsstr);
if(is_undefined(res)) {
if(FAILED(proc_ctx->hres))
goto ret;
if(FAILED(jsdisp_get_id(obj, str, 0, &id)))
continue;
proc_ctx->hres = disp_delete(to_disp(obj), id, &b);
}else {
proc_ctx->hres = jsdisp_define_data_property(obj, str, PROPF_WRITABLE | PROPF_ENUMERABLE | PROPF_CONFIGURABLE, res);
jsval_release(res);
}
if(FAILED(proc_ctx->hres))
goto ret;
}
}else {
id = DISPID_STARTENUM;
for(;;) {
proc_ctx->hres = jsdisp_next_prop(obj, id, JSDISP_ENUM_OWN_ENUMERABLE, &id);
if(proc_ctx->hres == S_FALSE)
break;
if(FAILED(proc_ctx->hres) || FAILED(proc_ctx->hres = jsdisp_get_prop_name(obj, id, &jsstr)))
goto ret;
res = transform_json_object(proc_ctx, obj, jsstr);
if(is_undefined(res)) {
if(SUCCEEDED(proc_ctx->hres))
proc_ctx->hres = disp_delete(to_disp(obj), id, &b);
}else {
if(!(str = jsstr_flatten(jsstr)))
proc_ctx->hres = E_OUTOFMEMORY;
else
proc_ctx->hres = jsdisp_define_data_property(obj, str, PROPF_WRITABLE | PROPF_ENUMERABLE | PROPF_CONFIGURABLE, res);
jsval_release(res);
}
jsstr_release(jsstr);
if(FAILED(proc_ctx->hres))
goto ret;
}
}
}
args[0] = jsval_string(name);
proc_ctx->hres = disp_call_value(proc_ctx->ctx, proc_ctx->reviver, jsval_obj(holder),
DISPATCH_METHOD, ARRAY_SIZE(args), args, &res);
ret:
jsval_release(args[1]);
return FAILED(proc_ctx->hres) ? jsval_undefined() : res;
}
/* ECMA-262 5.1 Edition 15.12.2 */
static HRESULT JSON_parse(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned argc, jsval_t *argv, jsval_t *r)
{
json_parse_ctx_t parse_ctx;
const WCHAR *buf;
jsdisp_t *root;
jsstr_t *str;
jsval_t ret;
HRESULT hres;
hres = to_flat_string(ctx, argv[0], &str, &buf);
if(FAILED(hres))
return hres;
TRACE("%s\n", debugstr_w(buf));
parse_ctx.ptr = buf;
parse_ctx.end = buf + jsstr_length(str);
parse_ctx.ctx = ctx;
hres = parse_json_value(&parse_ctx, &ret);
if(SUCCEEDED(hres) && skip_spaces(&parse_ctx)) {
FIXME("syntax error\n");
jsval_release(ret);
hres = E_FAIL;
}
jsstr_release(str);
if(FAILED(hres))
return hres;
/* FIXME: check IsCallable */
if(argc > 1 && is_object_instance(argv[1])) {
hres = create_object(ctx, NULL, &root);
if(FAILED(hres)) {
jsval_release(ret);
return hres;
}
hres = jsdisp_define_data_property(root, L"", PROPF_WRITABLE | PROPF_ENUMERABLE | PROPF_CONFIGURABLE, ret);
jsval_release(ret);
if(SUCCEEDED(hres)) {
struct transform_json_object_ctx proc_ctx = { ctx, get_object(argv[1]), S_OK };
str = jsstr_empty();
ret = transform_json_object(&proc_ctx, root, str);
jsstr_release(str);
hres = proc_ctx.hres;
}
jsdisp_release(root);
if(FAILED(hres))
return hres;
}
if(r)
*r = ret;
else
jsval_release(ret);
return S_OK;
}
typedef struct {
script_ctx_t *ctx;
WCHAR *buf;
size_t buf_size;
size_t buf_len;
jsdisp_t **stack;
size_t stack_top;
size_t stack_size;
WCHAR gap[11]; /* according to the spec, it's no longer than 10 chars */
jsdisp_t *replacer;
} stringify_ctx_t;
static BOOL stringify_push_obj(stringify_ctx_t *ctx, jsdisp_t *obj)
{
if(!ctx->stack_size) {
ctx->stack = malloc(4*sizeof(*ctx->stack));
if(!ctx->stack)
return FALSE;
ctx->stack_size = 4;
}else if(ctx->stack_top == ctx->stack_size) {
jsdisp_t **new_stack;
new_stack = realloc(ctx->stack, ctx->stack_size*2*sizeof(*ctx->stack));
if(!new_stack)
return FALSE;
ctx->stack = new_stack;
ctx->stack_size *= 2;
}
ctx->stack[ctx->stack_top++] = obj;
return TRUE;
}
static void stringify_pop_obj(stringify_ctx_t *ctx)
{
ctx->stack_top--;
}
static BOOL is_on_stack(stringify_ctx_t *ctx, jsdisp_t *obj)
{
size_t i = ctx->stack_top;
while(i--) {
if(ctx->stack[i] == obj)
return TRUE;
}
return FALSE;
}
static BOOL append_string_len(stringify_ctx_t *ctx, const WCHAR *str, size_t len)
{
if(!ctx->buf_size) {
ctx->buf = malloc(len*2*sizeof(WCHAR));
if(!ctx->buf)
return FALSE;
ctx->buf_size = len*2;
}else if(ctx->buf_len + len > ctx->buf_size) {
WCHAR *new_buf;
size_t new_size;
new_size = ctx->buf_size * 2 + len;
new_buf = realloc(ctx->buf, new_size*sizeof(WCHAR));
if(!new_buf)
return FALSE;
ctx->buf = new_buf;
ctx->buf_size = new_size;
}
if(len)
memcpy(ctx->buf + ctx->buf_len, str, len*sizeof(WCHAR));
ctx->buf_len += len;
return TRUE;
}
static inline BOOL append_string(stringify_ctx_t *ctx, const WCHAR *str)
{
return append_string_len(ctx, str, lstrlenW(str));
}
static inline BOOL append_char(stringify_ctx_t *ctx, WCHAR c)
{
return append_string_len(ctx, &c, 1);
}
static inline BOOL append_simple_quote(stringify_ctx_t *ctx, WCHAR c)
{
WCHAR str[] = {'\\',c};
return append_string_len(ctx, str, 2);
}
static HRESULT maybe_to_primitive(script_ctx_t *ctx, jsval_t val, jsval_t *r)
{
jsdisp_t *obj;
HRESULT hres;
if(!is_object_instance(val) || !(obj = iface_to_jsdisp(get_object(val))))
return jsval_copy(val, r);
if(is_class(obj, JSCLASS_NUMBER)) {
double n;
hres = to_number(ctx, val, &n);
jsdisp_release(obj);
if(SUCCEEDED(hres))
*r = jsval_number(n);
return hres;
}
if(is_class(obj, JSCLASS_STRING)) {
jsstr_t *str;
hres = to_string(ctx, val, &str);
jsdisp_release(obj);
if(SUCCEEDED(hres))
*r = jsval_string(str);
return hres;
}
if(is_class(obj, JSCLASS_BOOLEAN)) {
*r = jsval_bool(bool_obj_value(obj));
jsdisp_release(obj);
return S_OK;
}
*r = jsval_obj(obj);
return S_OK;
}
/* ECMA-262 5.1 Edition 15.12.3 (abstract operation Quote) */
static HRESULT json_quote(stringify_ctx_t *ctx, const WCHAR *ptr, size_t len)
{
if(!ptr || !append_char(ctx, '"'))
return E_OUTOFMEMORY;
while(len--) {
switch(*ptr) {
case '"':
case '\\':
if(!append_simple_quote(ctx, *ptr))
return E_OUTOFMEMORY;
break;
case '\b':
if(!append_simple_quote(ctx, 'b'))
return E_OUTOFMEMORY;
break;
case '\f':
if(!append_simple_quote(ctx, 'f'))
return E_OUTOFMEMORY;
break;
case '\n':
if(!append_simple_quote(ctx, 'n'))
return E_OUTOFMEMORY;
break;
case '\r':
if(!append_simple_quote(ctx, 'r'))
return E_OUTOFMEMORY;
break;
case '\t':
if(!append_simple_quote(ctx, 't'))
return E_OUTOFMEMORY;
break;
default:
if(*ptr < ' ') {
WCHAR buf[7];
swprintf(buf, ARRAY_SIZE(buf), L"\\u%04x", *ptr);
if(!append_string(ctx, buf))
return E_OUTOFMEMORY;
}else {
if(!append_char(ctx, *ptr))
return E_OUTOFMEMORY;
}
}
ptr++;
}
return append_char(ctx, '"') ? S_OK : E_OUTOFMEMORY;
}
static inline BOOL is_callable(jsdisp_t *obj)
{
return is_class(obj, JSCLASS_FUNCTION);
}
static HRESULT stringify(stringify_ctx_t *ctx, jsdisp_t *object, const WCHAR *name);
/* ECMA-262 5.1 Edition 15.12.3 (abstract operation JA) */
static HRESULT stringify_array(stringify_ctx_t *ctx, jsdisp_t *obj)
{
unsigned length, i, j;
WCHAR name[16];
HRESULT hres;
if(is_on_stack(ctx, obj)) {
FIXME("Found a cycle\n");
return E_FAIL;
}
if(!stringify_push_obj(ctx, obj))
return E_OUTOFMEMORY;
if(!append_char(ctx, '['))
return E_OUTOFMEMORY;
length = array_get_length(obj);
for(i=0; i < length; i++) {
if(i && !append_char(ctx, ','))
return E_OUTOFMEMORY;
if(*ctx->gap) {
if(!append_char(ctx, '\n'))
return E_OUTOFMEMORY;
for(j=0; j < ctx->stack_top; j++) {
if(!append_string(ctx, ctx->gap))
return E_OUTOFMEMORY;
}
}
_itow_s(i, name, ARRAY_SIZE(name), 10);
hres = stringify(ctx, obj, name);
if(FAILED(hres))
return hres;
if(hres == S_FALSE && !append_string(ctx, L"null"))
return E_OUTOFMEMORY;
}
if((length && *ctx->gap && !append_char(ctx, '\n')) || !append_char(ctx, ']'))
return E_OUTOFMEMORY;
stringify_pop_obj(ctx);
return S_OK;
}
/* ECMA-262 5.1 Edition 15.12.3 (abstract operation JO) */
static HRESULT stringify_object(stringify_ctx_t *ctx, jsdisp_t *obj)
{
DISPID dispid = DISPID_STARTENUM;
unsigned prop_cnt = 0, i;
size_t stepback;
BSTR prop_name;
HRESULT hres;
if(is_on_stack(ctx, obj)) {
FIXME("Found a cycle\n");
return E_FAIL;
}
if(!stringify_push_obj(ctx, obj))
return E_OUTOFMEMORY;
if(!append_char(ctx, '{'))
return E_OUTOFMEMORY;
while((hres = IDispatchEx_GetNextDispID(to_dispex(obj), fdexEnumDefault, dispid, &dispid)) == S_OK) {
stepback = ctx->buf_len;
if(prop_cnt && !append_char(ctx, ',')) {
hres = E_OUTOFMEMORY;
break;
}
if(*ctx->gap) {
if(!append_char(ctx, '\n')) {
hres = E_OUTOFMEMORY;
break;
}
for(i=0; i < ctx->stack_top; i++) {
if(!append_string(ctx, ctx->gap)) {
hres = E_OUTOFMEMORY;
break;
}
}
}
hres = IDispatchEx_GetMemberName(to_dispex(obj), dispid, &prop_name);
if(FAILED(hres))
return hres;
hres = json_quote(ctx, prop_name, SysStringLen(prop_name));
if(FAILED(hres)) {
SysFreeString(prop_name);
return hres;
}
if(!append_char(ctx, ':') || (*ctx->gap && !append_char(ctx, ' '))) {
SysFreeString(prop_name);
return E_OUTOFMEMORY;
}
hres = stringify(ctx, obj, prop_name);
SysFreeString(prop_name);
if(FAILED(hres))
return hres;
if(hres == S_FALSE) {
ctx->buf_len = stepback;
continue;
}
prop_cnt++;
}
if(prop_cnt && *ctx->gap) {
if(!append_char(ctx, '\n'))
return E_OUTOFMEMORY;
for(i=1; i < ctx->stack_top; i++) {
if(!append_string(ctx, ctx->gap)) {
hres = E_OUTOFMEMORY;
break;
}
}
}
if(!append_char(ctx, '}'))
return E_OUTOFMEMORY;
stringify_pop_obj(ctx);
return S_OK;
}
/* ECMA-262 5.1 Edition 15.12.3 (abstract operation Str) */
static HRESULT stringify(stringify_ctx_t *ctx, jsdisp_t *object, const WCHAR *name)
{
jsval_t value, v;
HRESULT hres;
hres = jsdisp_propget_name(object, name, &value);
if(FAILED(hres))
return hres == DISP_E_UNKNOWNNAME ? S_FALSE : hres;
if(is_object_instance(value)) {
jsdisp_t *obj;
DISPID id;
obj = to_jsdisp(get_object(value));
if(!obj) {
jsval_release(value);
return S_FALSE;
}
hres = jsdisp_get_id(obj, L"toJSON", 0, &id);
if(hres == S_OK)
FIXME("Use toJSON.\n");
}
if(ctx->replacer) {
jsstr_t *name_str;
jsval_t args[2];
if(!(name_str = jsstr_alloc(name))) {
jsval_release(value);
return E_OUTOFMEMORY;
}
args[0] = jsval_string(name_str);
args[1] = value;
hres = jsdisp_call_value(ctx->replacer, jsval_obj(object), DISPATCH_METHOD, ARRAY_SIZE(args), args, &v);
jsstr_release(name_str);
jsval_release(value);
if(FAILED(hres))
return hres;
value = v;
}
v = value;
hres = maybe_to_primitive(ctx->ctx, v, &value);
jsval_release(v);
if(FAILED(hres))
return hres;
switch(jsval_type(value)) {
case JSV_NULL:
if(is_null_disp(value))
hres = S_FALSE;
else if(!append_string(ctx, L"null"))
hres = E_OUTOFMEMORY;
break;
case JSV_BOOL:
if(!append_string(ctx, get_bool(value) ? L"true" : L"false"))
hres = E_OUTOFMEMORY;
break;
case JSV_STRING: {
jsstr_t *str = get_string(value);
const WCHAR *ptr = jsstr_flatten(str);
if(ptr)
hres = json_quote(ctx, ptr, jsstr_length(str));
else
hres = E_OUTOFMEMORY;
break;
}
case JSV_NUMBER: {
double n = get_number(value);
if(isfinite(n)) {
const WCHAR *ptr;
jsstr_t *str;
/* FIXME: Optimize. There is no need for jsstr_t here. */
hres = double_to_string(n, &str);
if(FAILED(hres))
break;
ptr = jsstr_flatten(str);
assert(ptr != NULL);
hres = ptr && !append_string_len(ctx, ptr, jsstr_length(str)) ? E_OUTOFMEMORY : S_OK;
jsstr_release(str);
}else {
if(!append_string(ctx, L"null"))
hres = E_OUTOFMEMORY;
}
break;
}
case JSV_OBJECT: {
jsdisp_t *obj;
obj = iface_to_jsdisp(get_object(value));
if(!obj) {
hres = S_FALSE;
break;
}
if(!is_callable(obj))
hres = is_class(obj, JSCLASS_ARRAY) ? stringify_array(ctx, obj) : stringify_object(ctx, obj);
else
hres = S_FALSE;
jsdisp_release(obj);
break;
}
case JSV_UNDEFINED:
hres = S_FALSE;
break;
case JSV_VARIANT:
FIXME("VARIANT\n");
hres = E_NOTIMPL;
break;
}
jsval_release(value);
return hres;
}
/* ECMA-262 5.1 Edition 15.12.3 */
static HRESULT JSON_stringify(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned argc, jsval_t *argv, jsval_t *r)
{
stringify_ctx_t stringify_ctx = { ctx };
jsdisp_t *obj = NULL, *replacer;
HRESULT hres;
TRACE("\n");
if(!argc) {
if(r)
*r = jsval_undefined();
return S_OK;
}
if(argc >= 2 && is_object_instance(argv[1]) && (replacer = to_jsdisp(get_object(argv[1])))) {
if(is_callable(replacer)) {
stringify_ctx.replacer = jsdisp_addref(replacer);
}else if(is_class(replacer, JSCLASS_ARRAY)) {
FIXME("Array replacer not yet supported\n");
return E_NOTIMPL;
}
}
if(argc >= 3) {
jsval_t space_val;
hres = maybe_to_primitive(ctx, argv[2], &space_val);
if(FAILED(hres))
goto fail;
if(is_number(space_val)) {
double n = get_number(space_val);
if(n >= 1) {
int i, len;
if(n > 10)
n = 10;
len = floor(n);
for(i=0; i < len; i++)
stringify_ctx.gap[i] = ' ';
stringify_ctx.gap[len] = 0;
}
}else if(is_string(space_val)) {
jsstr_t *space_str = get_string(space_val);
size_t len = jsstr_length(space_str);
if(len > 10)
len = 10;
jsstr_extract(space_str, 0, len, stringify_ctx.gap);
}
jsval_release(space_val);
}
if(FAILED(hres = create_object(ctx, NULL, &obj)))
goto fail;
if(FAILED(hres = jsdisp_propput_name(obj, L"", argv[0])))
goto fail;
hres = stringify(&stringify_ctx, obj, L"");
if(SUCCEEDED(hres) && r) {
assert(!stringify_ctx.stack_top);
if(hres == S_OK) {
jsstr_t *ret = jsstr_alloc_len(stringify_ctx.buf, stringify_ctx.buf_len);
if(ret)
*r = jsval_string(ret);
else
hres = E_OUTOFMEMORY;
}else {
*r = jsval_undefined();
}
}
fail:
if(obj)
jsdisp_release(obj);
if(stringify_ctx.replacer)
jsdisp_release(stringify_ctx.replacer);
free(stringify_ctx.buf);
free(stringify_ctx.stack);
return hres;
}
static const builtin_prop_t JSON_props[] = {
{L"parse", JSON_parse, PROPF_METHOD|2},
{L"stringify", JSON_stringify, PROPF_METHOD|3}
};
static const builtin_info_t JSON_info = {
.class = JSCLASS_JSON,
.props_cnt = ARRAY_SIZE(JSON_props),
.props = JSON_props,
};
HRESULT create_json(script_ctx_t *ctx, jsdisp_t **ret)
{
jsdisp_t *json;
HRESULT hres;
json = calloc(1, sizeof(*json));
if(!json)
return E_OUTOFMEMORY;
hres = init_dispex_from_constr(json, ctx, &JSON_info, ctx->object_constr);
if(FAILED(hres)) {
free(json);
return hres;
}
*ret = json;
return S_OK;
}