diff --git a/dlls/jscript/json.c b/dlls/jscript/json.c index 6e33a88ee53..01c780e5575 100644 --- a/dlls/jscript/json.c +++ b/dlls/jscript/json.c @@ -19,6 +19,7 @@ #include #include "jscript.h" +#include "parser.h" #include "wine/debug.h" #include "wine/unicode.h" @@ -28,10 +29,297 @@ WINE_DEFAULT_DEBUG_CHANNEL(jscript); static const WCHAR parseW[] = {'p','a','r','s','e',0}; static const WCHAR stringifyW[] = {'s','t','r','i','n','g','i','f','y',0}; +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}; + +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; +} + +/* 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 = heap_alloc((len+1)*sizeof(WCHAR)); + if(!buf) + return E_OUTOFMEMORY; + if(len) + memcpy(buf, ptr, len*sizeof(WCHAR)); + buf[len] = 0; + + if(!unescape(buf)) { + FIXME("unescape failed\n"); + heap_free(buf); + return E_FAIL; + } + + 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, nullW)) + break; + *r = jsval_null(); + return S_OK; + + /* JSONBooleanLiteral */ + case 't': + if(!is_keyword(ctx, trueW)) + break; + *r = jsval_bool(TRUE); + return S_OK; + case 'f': + if(!is_keyword(ctx, falseW)) + 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"); + heap_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); + } + heap_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); + heap_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(!isdigitW(*ctx->ptr)) + break; + + if(*ctx->ptr == '0') { + ctx->ptr++; + n = 0; + if(is_identifier_char(*ctx->ptr)) + break; + }else { + hres = parse_decimal(&ctx->ptr, ctx->end, &n); + if(FAILED(hres)) + return hres; + } + + *r = jsval_number(sign*n); + return S_OK; + } + } + + FIXME("Syntax error at %s\n", debugstr_w(ctx->ptr)); + return E_FAIL; +} + +/* ECMA-262 5.1 Edition 15.12.2 */ static HRESULT JSON_parse(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags, unsigned argc, jsval_t *argv, jsval_t *r) { - FIXME("\n"); - return E_NOTIMPL; + json_parse_ctx_t parse_ctx; + const WCHAR *buf; + jsstr_t *str; + jsval_t ret; + HRESULT hres; + + if(argc != 1) { + FIXME("Unsupported args\n"); + return E_INVALIDARG; + } + + 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); + jsstr_release(str); + if(FAILED(hres)) + return hres; + + if(skip_spaces(&parse_ctx)) { + FIXME("syntax error\n"); + jsval_release(ret); + return E_FAIL; + } + + if(r) + *r = ret; + else + jsval_release(ret); + return S_OK; } static HRESULT JSON_stringify(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags, unsigned argc, jsval_t *argv, jsval_t *r) diff --git a/dlls/jscript/lex.c b/dlls/jscript/lex.c index dd788f02df5..65b16a6280c 100644 --- a/dlls/jscript/lex.c +++ b/dlls/jscript/lex.c @@ -109,7 +109,7 @@ static int lex_error(parser_ctx_t *ctx, HRESULT hres) } /* ECMA-262 3rd Edition 7.6 */ -static BOOL is_identifier_char(WCHAR c) +BOOL is_identifier_char(WCHAR c) { return isalnumW(c) || c == '$' || c == '_' || c == '\\'; } @@ -249,7 +249,7 @@ static BOOL skip_spaces(parser_ctx_t *ctx) return ctx->ptr != ctx->end; } -static BOOL unescape(WCHAR *str) +BOOL unescape(WCHAR *str) { WCHAR *pd, *p, c; int i; @@ -406,7 +406,7 @@ literal_t *new_boolean_literal(parser_ctx_t *ctx, BOOL bval) return ret; } -static HRESULT parse_decimal(const WCHAR **iter, const WCHAR *end, double *ret) +HRESULT parse_decimal(const WCHAR **iter, const WCHAR *end, double *ret) { const WCHAR *ptr = *iter; LONGLONG d = 0, hlp; diff --git a/dlls/jscript/parser.h b/dlls/jscript/parser.h index f8d404f8d6d..c3dcb82f071 100644 --- a/dlls/jscript/parser.h +++ b/dlls/jscript/parser.h @@ -62,6 +62,10 @@ static inline void *parser_alloc_tmp(parser_ctx_t *ctx, DWORD size) return heap_pool_alloc(&ctx->script->tmp_heap, size); } +BOOL is_identifier_char(WCHAR) DECLSPEC_HIDDEN; +BOOL unescape(WCHAR*) DECLSPEC_HIDDEN; +HRESULT parse_decimal(const WCHAR**,const WCHAR*,double*) DECLSPEC_HIDDEN; + typedef enum { LT_DOUBLE, LT_STRING,