jscript: Added JSON.stringify implementation.
Signed-off-by: Jacek Caban <jacek@codeweavers.com> Signed-off-by: Alexandre Julliard <julliard@winehq.org>
This commit is contained in:
parent
f0be56e17c
commit
95bdd084a1
|
@ -20,6 +20,7 @@
|
|||
#include "wine/port.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <assert.h>
|
||||
|
||||
#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;
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
*/
|
||||
|
||||
#include <math.h>
|
||||
#include <assert.h>
|
||||
|
||||
#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");
|
||||
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[] = {
|
||||
|
|
Loading…
Reference in New Issue