From 95bdd084a18132d15c85a8eb141b4307801dd950 Mon Sep 17 00:00:00 2001 From: Jacek Caban Date: Wed, 27 Jan 2016 20:43:55 +0100 Subject: [PATCH] jscript: Added JSON.stringify implementation. Signed-off-by: Jacek Caban Signed-off-by: Alexandre Julliard --- dlls/jscript/array.c | 7 + dlls/jscript/bool.c | 8 + dlls/jscript/jscript.h | 3 + dlls/jscript/json.c | 495 ++++++++++++++++++++++++++++++++++++++++- 4 files changed, 511 insertions(+), 2 deletions(-) diff --git a/dlls/jscript/array.c b/dlls/jscript/array.c index a0053090221..fb4982fb05f 100644 --- a/dlls/jscript/array.c +++ b/dlls/jscript/array.c @@ -20,6 +20,7 @@ #include "wine/port.h" #include +#include #include "jscript.h" @@ -64,6 +65,12 @@ static inline ArrayInstance *array_this(vdisp_t *jsthis) return is_vclass(jsthis, JSCLASS_ARRAY) ? array_from_vdisp(jsthis) : NULL; } +unsigned array_get_length(jsdisp_t *array) +{ + assert(is_class(array, JSCLASS_ARRAY)); + return array_from_jsdisp(array)->length; +} + static HRESULT get_length(script_ctx_t *ctx, vdisp_t *vdisp, jsdisp_t **jsthis, DWORD *ret) { ArrayInstance *array; diff --git a/dlls/jscript/bool.c b/dlls/jscript/bool.c index 9449ef872dc..7cd0c21f640 100644 --- a/dlls/jscript/bool.c +++ b/dlls/jscript/bool.c @@ -17,6 +17,8 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ +#include + #include "jscript.h" #include "wine/debug.h" @@ -37,6 +39,12 @@ static inline BoolInstance *bool_this(vdisp_t *jsthis) return is_vclass(jsthis, JSCLASS_BOOLEAN) ? (BoolInstance*)jsthis->u.jsdisp : NULL; } +BOOL bool_obj_value(jsdisp_t *obj) +{ + assert(is_class(obj, JSCLASS_BOOLEAN)); + return ((BoolInstance*)obj)->val; +} + /* ECMA-262 3rd Edition 15.6.4.2 */ static HRESULT Bool_toString(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags, unsigned argc, jsval_t *argv, jsval_t *r) { diff --git a/dlls/jscript/jscript.h b/dlls/jscript/jscript.h index 98cd5b8b44e..87f2e945be9 100644 --- a/dlls/jscript/jscript.h +++ b/dlls/jscript/jscript.h @@ -461,6 +461,9 @@ HRESULT regexp_match_next(script_ctx_t*,jsdisp_t*,DWORD,jsstr_t*,struct match_st HRESULT parse_regexp_flags(const WCHAR*,DWORD,DWORD*) DECLSPEC_HIDDEN; HRESULT regexp_string_match(script_ctx_t*,jsdisp_t*,jsstr_t*,jsval_t*) DECLSPEC_HIDDEN; +BOOL bool_obj_value(jsdisp_t*) DECLSPEC_HIDDEN; +unsigned array_get_length(jsdisp_t*) DECLSPEC_HIDDEN; + static inline BOOL is_class(jsdisp_t *jsdisp, jsclass_t class) { return jsdisp->builtin_info->class == class; diff --git a/dlls/jscript/json.c b/dlls/jscript/json.c index 01c780e5575..25f620e8e42 100644 --- a/dlls/jscript/json.c +++ b/dlls/jscript/json.c @@ -17,6 +17,7 @@ */ #include +#include #include "jscript.h" #include "parser.h" @@ -33,6 +34,8 @@ static const WCHAR nullW[] = {'n','u','l','l',0}; static const WCHAR trueW[] = {'t','r','u','e',0}; static const WCHAR falseW[] = {'f','a','l','s','e',0}; +static const WCHAR toJSONW[] = {'t','o','J','S','O','N',0}; + typedef struct { const WCHAR *ptr; const WCHAR *end; @@ -322,10 +325,498 @@ static HRESULT JSON_parse(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags, unsign 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 */ +} stringify_ctx_t; + +static BOOL stringify_push_obj(stringify_ctx_t *ctx, jsdisp_t *obj) +{ + if(!ctx->stack_size) { + ctx->stack = heap_alloc(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 = heap_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 = heap_alloc(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 = heap_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, strlenW(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) || !get_object(val) || !(obj = iface_to_jsdisp((IUnknown*)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 < ' ') { + const WCHAR formatW[] = {'\\','u','%','0','4','x',0}; + WCHAR buf[7]; + sprintfW(buf, formatW, *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, jsval_t val); + +/* 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; + jsval_t val; + 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; + } + } + + hres = jsdisp_get_idx(obj, i, &val); + if(FAILED(hres)) + return hres; + + hres = stringify(ctx, val); + if(FAILED(hres)) + return hres; + + if(hres == S_FALSE && !append_string(ctx, nullW)) + 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; + jsval_t val = jsval_undefined(); + 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(&obj->IDispatchEx_iface, fdexEnumDefault, dispid, &dispid)) == S_OK) { + jsval_release(val); + hres = jsdisp_propget(obj, dispid, &val); + if(FAILED(hres)) + return hres; + + if(is_undefined(val)) + continue; + + 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(&obj->IDispatchEx_iface, dispid, &prop_name); + if(FAILED(hres)) + break; + + hres = json_quote(ctx, prop_name, SysStringLen(prop_name)); + SysFreeString(prop_name); + if(FAILED(hres)) + break; + + if(!append_char(ctx, ':') || (*ctx->gap && !append_char(ctx, ' '))) { + hres = E_OUTOFMEMORY; + break; + } + + hres = stringify(ctx, val); + if(FAILED(hres)) + break; + + if(hres == S_FALSE) { + ctx->buf_len = stepback; + continue; + } + + prop_cnt++; + } + jsval_release(val); + if(FAILED(hres)) + return hres; + + 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, jsval_t val) +{ + jsval_t value; + HRESULT hres; + + if(is_object_instance(val) && get_object(val)) { + jsdisp_t *obj; + DISPID id; + + obj = iface_to_jsdisp((IUnknown*)get_object(val)); + if(!obj) + return S_FALSE; + + hres = jsdisp_get_id(obj, toJSONW, 0, &id); + jsdisp_release(obj); + if(hres == S_OK) + FIXME("Use toJSON.\n"); + } + + /* FIXME: Support replacer replacer. */ + + hres = maybe_to_primitive(ctx->ctx, val, &value); + if(FAILED(hres)) + return hres; + + switch(jsval_type(value)) { + case JSV_NULL: + if(!append_string(ctx, nullW)) + hres = E_OUTOFMEMORY; + break; + case JSV_BOOL: + if(!append_string(ctx, get_bool(value) ? trueW : falseW)) + 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(is_finite(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, nullW)) + hres = E_OUTOFMEMORY; + } + break; + } + case JSV_OBJECT: { + jsdisp_t *obj; + + obj = iface_to_jsdisp((IUnknown*)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, vdisp_t *jsthis, WORD flags, unsigned argc, jsval_t *argv, jsval_t *r) { - FIXME("\n"); - return E_NOTIMPL; + stringify_ctx_t stringify_ctx = {ctx, NULL,0,0, NULL,0,0, {0}}; + HRESULT hres; + + TRACE("\n"); + + if(argc >= 2 && is_object_instance(argv[1])) { + FIXME("Replacer %s not yet supported\n", debugstr_jsval(argv[1])); + return E_NOTIMPL; + } + + if(argc >= 3) { + jsval_t space_val; + + hres = maybe_to_primitive(ctx, argv[2], &space_val); + if(FAILED(hres)) + return hres; + + 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); + } + + hres = stringify(&stringify_ctx, argv[0]); + 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(); + } + } + + heap_free(stringify_ctx.buf); + heap_free(stringify_ctx.stack); + return hres; } static const builtin_prop_t JSON_props[] = {