| /* | |
| * Copyright 2011 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 "engine.h" | |
| #include "parser.h" | |
| #include "wine/rbtree.h" | |
| #include "wine/debug.h" | |
| WINE_DEFAULT_DEBUG_CHANNEL(jscript); | |
| WINE_DECLARE_DEBUG_CHANNEL(jscript_disas); | |
| typedef struct _statement_ctx_t { | |
| unsigned stack_use; | |
| BOOL using_scope; | |
| BOOL using_except; | |
| unsigned break_label; | |
| unsigned continue_label; | |
| const labelled_statement_t *labelled_stat; | |
| struct _statement_ctx_t *next; | |
| } statement_ctx_t; | |
| typedef struct { | |
| struct wine_rb_entry entry; | |
| BSTR name; | |
| int ref; | |
| } function_local_t; | |
| typedef struct { | |
| parser_ctx_t *parser; | |
| bytecode_t *code; | |
| BOOL from_eval; | |
| unsigned code_off; | |
| unsigned code_size; | |
| unsigned *labels; | |
| unsigned labels_size; | |
| unsigned labels_cnt; | |
| struct wine_rb_tree locals; | |
| unsigned locals_cnt; | |
| statement_ctx_t *stat_ctx; | |
| function_code_t *func; | |
| function_expression_t *func_head; | |
| function_expression_t *func_tail; | |
| heap_pool_t heap; | |
| } compiler_ctx_t; | |
| static const struct { | |
| const char *op_str; | |
| instr_arg_type_t arg1_type; | |
| instr_arg_type_t arg2_type; | |
| } instr_info[] = { | |
| #define X(n,a,b,c) {#n,b,c}, | |
| OP_LIST | |
| #undef X | |
| }; | |
| static void dump_instr_arg(instr_arg_type_t type, instr_arg_t *arg) | |
| { | |
| switch(type) { | |
| case ARG_STR: | |
| TRACE_(jscript_disas)("\t%s", debugstr_jsstr(arg->str)); | |
| break; | |
| case ARG_BSTR: | |
| TRACE_(jscript_disas)("\t%s", debugstr_wn(arg->bstr, SysStringLen(arg->bstr))); | |
| break; | |
| case ARG_INT: | |
| TRACE_(jscript_disas)("\t%d", arg->uint); | |
| break; | |
| case ARG_UINT: | |
| case ARG_ADDR: | |
| TRACE_(jscript_disas)("\t%u", arg->uint); | |
| break; | |
| case ARG_FUNC: | |
| case ARG_NONE: | |
| break; | |
| DEFAULT_UNREACHABLE; | |
| } | |
| } | |
| static void dump_code(compiler_ctx_t *ctx, unsigned off) | |
| { | |
| instr_t *instr; | |
| for(instr = ctx->code->instrs+off; instr < ctx->code->instrs+ctx->code_off; instr++) { | |
| TRACE_(jscript_disas)("%d:\t%s", (int)(instr-ctx->code->instrs), instr_info[instr->op].op_str); | |
| if(instr_info[instr->op].arg1_type == ARG_DBL) { | |
| TRACE_(jscript_disas)("\t%lf", instr->u.dbl); | |
| }else { | |
| dump_instr_arg(instr_info[instr->op].arg1_type, instr->u.arg); | |
| dump_instr_arg(instr_info[instr->op].arg2_type, instr->u.arg+1); | |
| } | |
| TRACE_(jscript_disas)("\n"); | |
| } | |
| } | |
| static HRESULT compile_expression(compiler_ctx_t*,expression_t*,BOOL); | |
| static HRESULT compile_statement(compiler_ctx_t*,statement_ctx_t*,statement_t*); | |
| static inline void *compiler_alloc(bytecode_t *code, size_t size) | |
| { | |
| return heap_pool_alloc(&code->heap, size); | |
| } | |
| static jsstr_t *compiler_alloc_string_len(compiler_ctx_t *ctx, const WCHAR *str, unsigned len) | |
| { | |
| jsstr_t *new_str; | |
| if(!ctx->code->str_pool_size) { | |
| ctx->code->str_pool = heap_alloc(8 * sizeof(jsstr_t*)); | |
| if(!ctx->code->str_pool) | |
| return NULL; | |
| ctx->code->str_pool_size = 8; | |
| }else if(ctx->code->str_pool_size == ctx->code->str_cnt) { | |
| jsstr_t **new_pool; | |
| new_pool = heap_realloc(ctx->code->str_pool, ctx->code->str_pool_size*2*sizeof(jsstr_t*)); | |
| if(!new_pool) | |
| return NULL; | |
| ctx->code->str_pool = new_pool; | |
| ctx->code->str_pool_size *= 2; | |
| } | |
| new_str = jsstr_alloc_len(str, len); | |
| if(!new_str) | |
| return NULL; | |
| ctx->code->str_pool[ctx->code->str_cnt++] = new_str; | |
| return new_str; | |
| } | |
| static jsstr_t *compiler_alloc_string(compiler_ctx_t *ctx, const WCHAR *str) | |
| { | |
| return compiler_alloc_string_len(ctx, str, strlenW(str)); | |
| } | |
| static BOOL ensure_bstr_slot(compiler_ctx_t *ctx) | |
| { | |
| if(!ctx->code->bstr_pool_size) { | |
| ctx->code->bstr_pool = heap_alloc(8 * sizeof(BSTR)); | |
| if(!ctx->code->bstr_pool) | |
| return FALSE; | |
| ctx->code->bstr_pool_size = 8; | |
| }else if(ctx->code->bstr_pool_size == ctx->code->bstr_cnt) { | |
| BSTR *new_pool; | |
| new_pool = heap_realloc(ctx->code->bstr_pool, ctx->code->bstr_pool_size*2*sizeof(BSTR)); | |
| if(!new_pool) | |
| return FALSE; | |
| ctx->code->bstr_pool = new_pool; | |
| ctx->code->bstr_pool_size *= 2; | |
| } | |
| return TRUE; | |
| } | |
| static BSTR compiler_alloc_bstr(compiler_ctx_t *ctx, const WCHAR *str) | |
| { | |
| if(!ensure_bstr_slot(ctx)) | |
| return NULL; | |
| ctx->code->bstr_pool[ctx->code->bstr_cnt] = SysAllocString(str); | |
| if(!ctx->code->bstr_pool[ctx->code->bstr_cnt]) | |
| return NULL; | |
| return ctx->code->bstr_pool[ctx->code->bstr_cnt++]; | |
| } | |
| static BSTR compiler_alloc_bstr_len(compiler_ctx_t *ctx, const WCHAR *str, size_t len) | |
| { | |
| if(!ensure_bstr_slot(ctx)) | |
| return NULL; | |
| ctx->code->bstr_pool[ctx->code->bstr_cnt] = SysAllocStringLen(str, len); | |
| if(!ctx->code->bstr_pool[ctx->code->bstr_cnt]) | |
| return NULL; | |
| return ctx->code->bstr_pool[ctx->code->bstr_cnt++]; | |
| } | |
| static unsigned push_instr(compiler_ctx_t *ctx, jsop_t op) | |
| { | |
| assert(ctx->code_size >= ctx->code_off); | |
| if(ctx->code_size == ctx->code_off) { | |
| instr_t *new_instrs; | |
| new_instrs = heap_realloc(ctx->code->instrs, ctx->code_size*2*sizeof(instr_t)); | |
| if(!new_instrs) | |
| return 0; | |
| ctx->code->instrs = new_instrs; | |
| ctx->code_size *= 2; | |
| } | |
| ctx->code->instrs[ctx->code_off].op = op; | |
| return ctx->code_off++; | |
| } | |
| static inline instr_t *instr_ptr(compiler_ctx_t *ctx, unsigned off) | |
| { | |
| assert(off < ctx->code_off); | |
| return ctx->code->instrs + off; | |
| } | |
| static HRESULT push_instr_int(compiler_ctx_t *ctx, jsop_t op, LONG arg) | |
| { | |
| unsigned instr; | |
| instr = push_instr(ctx, op); | |
| if(!instr) | |
| return E_OUTOFMEMORY; | |
| instr_ptr(ctx, instr)->u.arg->lng = arg; | |
| return S_OK; | |
| } | |
| static HRESULT push_instr_str(compiler_ctx_t *ctx, jsop_t op, const WCHAR *arg) | |
| { | |
| unsigned instr; | |
| jsstr_t *str; | |
| str = compiler_alloc_string(ctx, arg); | |
| if(!str) | |
| return E_OUTOFMEMORY; | |
| instr = push_instr(ctx, op); | |
| if(!instr) | |
| return E_OUTOFMEMORY; | |
| instr_ptr(ctx, instr)->u.arg->str = str; | |
| return S_OK; | |
| } | |
| static HRESULT push_instr_bstr(compiler_ctx_t *ctx, jsop_t op, const WCHAR *arg) | |
| { | |
| unsigned instr; | |
| WCHAR *str; | |
| str = compiler_alloc_bstr(ctx, arg); | |
| if(!str) | |
| return E_OUTOFMEMORY; | |
| instr = push_instr(ctx, op); | |
| if(!instr) | |
| return E_OUTOFMEMORY; | |
| instr_ptr(ctx, instr)->u.arg->bstr = str; | |
| return S_OK; | |
| } | |
| static HRESULT push_instr_bstr_uint(compiler_ctx_t *ctx, jsop_t op, const WCHAR *arg1, unsigned arg2) | |
| { | |
| unsigned instr; | |
| WCHAR *str; | |
| str = compiler_alloc_bstr(ctx, arg1); | |
| if(!str) | |
| return E_OUTOFMEMORY; | |
| instr = push_instr(ctx, op); | |
| if(!instr) | |
| return E_OUTOFMEMORY; | |
| instr_ptr(ctx, instr)->u.arg[0].bstr = str; | |
| instr_ptr(ctx, instr)->u.arg[1].uint = arg2; | |
| return S_OK; | |
| } | |
| static HRESULT push_instr_uint_str(compiler_ctx_t *ctx, jsop_t op, unsigned arg1, const WCHAR *arg2) | |
| { | |
| unsigned instr; | |
| jsstr_t *str; | |
| str = compiler_alloc_string(ctx, arg2); | |
| if(!str) | |
| return E_OUTOFMEMORY; | |
| instr = push_instr(ctx, op); | |
| if(!instr) | |
| return E_OUTOFMEMORY; | |
| instr_ptr(ctx, instr)->u.arg[0].uint = arg1; | |
| instr_ptr(ctx, instr)->u.arg[1].str = str; | |
| return S_OK; | |
| } | |
| static HRESULT push_instr_double(compiler_ctx_t *ctx, jsop_t op, double arg) | |
| { | |
| unsigned instr; | |
| instr = push_instr(ctx, op); | |
| if(!instr) | |
| return E_OUTOFMEMORY; | |
| instr_ptr(ctx, instr)->u.dbl = arg; | |
| return S_OK; | |
| } | |
| static inline void set_arg_uint(compiler_ctx_t *ctx, unsigned instr, unsigned arg) | |
| { | |
| instr_ptr(ctx, instr)->u.arg->uint = arg; | |
| } | |
| static HRESULT push_instr_uint(compiler_ctx_t *ctx, jsop_t op, unsigned arg) | |
| { | |
| unsigned instr; | |
| instr = push_instr(ctx, op); | |
| if(!instr) | |
| return E_OUTOFMEMORY; | |
| set_arg_uint(ctx, instr, arg); | |
| return S_OK; | |
| } | |
| static HRESULT compile_binary_expression(compiler_ctx_t *ctx, binary_expression_t *expr, jsop_t op) | |
| { | |
| HRESULT hres; | |
| hres = compile_expression(ctx, expr->expression1, TRUE); | |
| if(FAILED(hres)) | |
| return hres; | |
| hres = compile_expression(ctx, expr->expression2, TRUE); | |
| if(FAILED(hres)) | |
| return hres; | |
| return push_instr(ctx, op) ? S_OK : E_OUTOFMEMORY; | |
| } | |
| static HRESULT compile_unary_expression(compiler_ctx_t *ctx, unary_expression_t *expr, jsop_t op) | |
| { | |
| HRESULT hres; | |
| hres = compile_expression(ctx, expr->expression, TRUE); | |
| if(FAILED(hres)) | |
| return hres; | |
| return push_instr(ctx, op) ? S_OK : E_OUTOFMEMORY; | |
| } | |
| /* ECMA-262 3rd Edition 11.2.1 */ | |
| static HRESULT compile_member_expression(compiler_ctx_t *ctx, member_expression_t *expr) | |
| { | |
| HRESULT hres; | |
| hres = compile_expression(ctx, expr->expression, TRUE); | |
| if(FAILED(hres)) | |
| return hres; | |
| return push_instr_bstr(ctx, OP_member, expr->identifier); | |
| } | |
| #define LABEL_FLAG 0x80000000 | |
| static unsigned alloc_label(compiler_ctx_t *ctx) | |
| { | |
| if(!ctx->labels_size) { | |
| ctx->labels = heap_alloc(8 * sizeof(*ctx->labels)); | |
| if(!ctx->labels) | |
| return 0; | |
| ctx->labels_size = 8; | |
| }else if(ctx->labels_size == ctx->labels_cnt) { | |
| unsigned *new_labels; | |
| new_labels = heap_realloc(ctx->labels, 2*ctx->labels_size*sizeof(*ctx->labels)); | |
| if(!new_labels) | |
| return 0; | |
| ctx->labels = new_labels; | |
| ctx->labels_size *= 2; | |
| } | |
| return ctx->labels_cnt++ | LABEL_FLAG; | |
| } | |
| static void label_set_addr(compiler_ctx_t *ctx, unsigned label) | |
| { | |
| assert(label & LABEL_FLAG); | |
| ctx->labels[label & ~LABEL_FLAG] = ctx->code_off; | |
| } | |
| static inline BOOL is_memberid_expr(expression_type_t type) | |
| { | |
| return type == EXPR_IDENT || type == EXPR_MEMBER || type == EXPR_ARRAY; | |
| } | |
| static BOOL bind_local(compiler_ctx_t *ctx, const WCHAR *identifier, int *ret_ref) | |
| { | |
| statement_ctx_t *iter; | |
| local_ref_t *ref; | |
| for(iter = ctx->stat_ctx; iter; iter = iter->next) { | |
| if(iter->using_scope) | |
| return FALSE; | |
| } | |
| ref = lookup_local(ctx->func, identifier); | |
| if(!ref) | |
| return FALSE; | |
| *ret_ref = ref->ref; | |
| return TRUE; | |
| } | |
| static HRESULT emit_identifier_ref(compiler_ctx_t *ctx, const WCHAR *identifier, unsigned flags) | |
| { | |
| int local_ref; | |
| if(bind_local(ctx, identifier, &local_ref)) | |
| return push_instr_int(ctx, OP_local_ref, local_ref); | |
| return push_instr_bstr_uint(ctx, OP_identid, identifier, flags); | |
| } | |
| static HRESULT emit_identifier(compiler_ctx_t *ctx, const WCHAR *identifier) | |
| { | |
| int local_ref; | |
| if(bind_local(ctx, identifier, &local_ref)) | |
| return push_instr_int(ctx, OP_local, local_ref); | |
| return push_instr_bstr(ctx, OP_ident, identifier); | |
| } | |
| static HRESULT compile_memberid_expression(compiler_ctx_t *ctx, expression_t *expr, unsigned flags) | |
| { | |
| HRESULT hres = S_OK; | |
| switch(expr->type) { | |
| case EXPR_IDENT: { | |
| identifier_expression_t *ident_expr = (identifier_expression_t*)expr; | |
| hres = emit_identifier_ref(ctx, ident_expr->identifier, flags); | |
| break; | |
| } | |
| case EXPR_ARRAY: { | |
| binary_expression_t *array_expr = (binary_expression_t*)expr; | |
| hres = compile_expression(ctx, array_expr->expression1, TRUE); | |
| if(FAILED(hres)) | |
| return hres; | |
| hres = compile_expression(ctx, array_expr->expression2, TRUE); | |
| if(FAILED(hres)) | |
| return hres; | |
| hres = push_instr_uint(ctx, OP_memberid, flags); | |
| break; | |
| } | |
| case EXPR_MEMBER: { | |
| member_expression_t *member_expr = (member_expression_t*)expr; | |
| hres = compile_expression(ctx, member_expr->expression, TRUE); | |
| if(FAILED(hres)) | |
| return hres; | |
| /* FIXME: Potential optimization */ | |
| hres = push_instr_str(ctx, OP_str, member_expr->identifier); | |
| if(FAILED(hres)) | |
| return hres; | |
| hres = push_instr_uint(ctx, OP_memberid, flags); | |
| break; | |
| } | |
| DEFAULT_UNREACHABLE; | |
| } | |
| return hres; | |
| } | |
| static HRESULT compile_increment_expression(compiler_ctx_t *ctx, unary_expression_t *expr, jsop_t op, int n) | |
| { | |
| HRESULT hres; | |
| if(!is_memberid_expr(expr->expression->type)) { | |
| hres = compile_expression(ctx, expr->expression, TRUE); | |
| if(FAILED(hres)) | |
| return hres; | |
| return push_instr_uint(ctx, OP_throw_ref, JS_E_ILLEGAL_ASSIGN); | |
| } | |
| hres = compile_memberid_expression(ctx, expr->expression, fdexNameEnsure); | |
| if(FAILED(hres)) | |
| return hres; | |
| return push_instr_int(ctx, op, n); | |
| } | |
| /* ECMA-262 3rd Edition 11.14 */ | |
| static HRESULT compile_comma_expression(compiler_ctx_t *ctx, binary_expression_t *expr, BOOL emit_ret) | |
| { | |
| HRESULT hres; | |
| hres = compile_expression(ctx, expr->expression1, FALSE); | |
| if(FAILED(hres)) | |
| return hres; | |
| return compile_expression(ctx, expr->expression2, emit_ret); | |
| } | |
| /* ECMA-262 3rd Edition 11.11 */ | |
| static HRESULT compile_logical_expression(compiler_ctx_t *ctx, binary_expression_t *expr, jsop_t op) | |
| { | |
| unsigned instr; | |
| HRESULT hres; | |
| hres = compile_expression(ctx, expr->expression1, TRUE); | |
| if(FAILED(hres)) | |
| return hres; | |
| instr = push_instr(ctx, op); | |
| if(!instr) | |
| return E_OUTOFMEMORY; | |
| hres = compile_expression(ctx, expr->expression2, TRUE); | |
| if(FAILED(hres)) | |
| return hres; | |
| set_arg_uint(ctx, instr, ctx->code_off); | |
| return S_OK; | |
| } | |
| /* ECMA-262 3rd Edition 11.12 */ | |
| static HRESULT compile_conditional_expression(compiler_ctx_t *ctx, conditional_expression_t *expr) | |
| { | |
| unsigned jmp_false, jmp_end; | |
| HRESULT hres; | |
| hres = compile_expression(ctx, expr->expression, TRUE); | |
| if(FAILED(hres)) | |
| return hres; | |
| jmp_false = push_instr(ctx, OP_cnd_z); | |
| if(!jmp_false) | |
| return E_OUTOFMEMORY; | |
| hres = compile_expression(ctx, expr->true_expression, TRUE); | |
| if(FAILED(hres)) | |
| return hres; | |
| jmp_end = push_instr(ctx, OP_jmp); | |
| if(!jmp_end) | |
| return E_OUTOFMEMORY; | |
| set_arg_uint(ctx, jmp_false, ctx->code_off); | |
| hres = push_instr_uint(ctx, OP_pop, 1); | |
| if(FAILED(hres)) | |
| return hres; | |
| hres = compile_expression(ctx, expr->false_expression, TRUE); | |
| if(FAILED(hres)) | |
| return hres; | |
| set_arg_uint(ctx, jmp_end, ctx->code_off); | |
| return S_OK; | |
| } | |
| static HRESULT compile_new_expression(compiler_ctx_t *ctx, call_expression_t *expr) | |
| { | |
| unsigned arg_cnt = 0; | |
| argument_t *arg; | |
| HRESULT hres; | |
| hres = compile_expression(ctx, expr->expression, TRUE); | |
| if(FAILED(hres)) | |
| return hres; | |
| for(arg = expr->argument_list; arg; arg = arg->next) { | |
| hres = compile_expression(ctx, arg->expr, TRUE); | |
| if(FAILED(hres)) | |
| return hres; | |
| arg_cnt++; | |
| } | |
| hres = push_instr_uint(ctx, OP_new, arg_cnt); | |
| if(FAILED(hres)) | |
| return hres; | |
| hres = push_instr_uint(ctx, OP_pop, arg_cnt+1); | |
| if(FAILED(hres)) | |
| return hres; | |
| return push_instr(ctx, OP_push_ret) ? S_OK : E_OUTOFMEMORY; | |
| } | |
| static HRESULT compile_call_expression(compiler_ctx_t *ctx, call_expression_t *expr, BOOL emit_ret) | |
| { | |
| unsigned arg_cnt = 0, extra_args; | |
| argument_t *arg; | |
| unsigned instr; | |
| jsop_t op; | |
| HRESULT hres; | |
| if(is_memberid_expr(expr->expression->type)) { | |
| op = OP_call_member; | |
| extra_args = 2; | |
| hres = compile_memberid_expression(ctx, expr->expression, 0); | |
| }else { | |
| op = OP_call; | |
| extra_args = 1; | |
| hres = compile_expression(ctx, expr->expression, TRUE); | |
| } | |
| if(FAILED(hres)) | |
| return hres; | |
| for(arg = expr->argument_list; arg; arg = arg->next) { | |
| hres = compile_expression(ctx, arg->expr, TRUE); | |
| if(FAILED(hres)) | |
| return hres; | |
| arg_cnt++; | |
| } | |
| instr = push_instr(ctx, op); | |
| if(!instr) | |
| return E_OUTOFMEMORY; | |
| instr_ptr(ctx, instr)->u.arg[0].uint = arg_cnt; | |
| instr_ptr(ctx, instr)->u.arg[1].lng = emit_ret; | |
| hres = push_instr_uint(ctx, OP_pop, arg_cnt + extra_args); | |
| if(FAILED(hres)) | |
| return hres; | |
| return !emit_ret || push_instr(ctx, OP_push_ret) ? S_OK : E_OUTOFMEMORY; | |
| } | |
| static HRESULT compile_delete_expression(compiler_ctx_t *ctx, unary_expression_t *expr) | |
| { | |
| HRESULT hres; | |
| switch(expr->expression->type) { | |
| case EXPR_ARRAY: { | |
| binary_expression_t *array_expr = (binary_expression_t*)expr->expression; | |
| hres = compile_expression(ctx, array_expr->expression1, TRUE); | |
| if(FAILED(hres)) | |
| return hres; | |
| hres = compile_expression(ctx, array_expr->expression2, TRUE); | |
| if(FAILED(hres)) | |
| return hres; | |
| if(!push_instr(ctx, OP_delete)) | |
| return E_OUTOFMEMORY; | |
| break; | |
| } | |
| case EXPR_MEMBER: { | |
| member_expression_t *member_expr = (member_expression_t*)expr->expression; | |
| hres = compile_expression(ctx, member_expr->expression, TRUE); | |
| if(FAILED(hres)) | |
| return hres; | |
| /* FIXME: Potential optimization */ | |
| hres = push_instr_str(ctx, OP_str, member_expr->identifier); | |
| if(FAILED(hres)) | |
| return hres; | |
| if(!push_instr(ctx, OP_delete)) | |
| return E_OUTOFMEMORY; | |
| break; | |
| } | |
| case EXPR_IDENT: | |
| return push_instr_bstr(ctx, OP_delete_ident, ((identifier_expression_t*)expr->expression)->identifier); | |
| default: { | |
| const WCHAR fixmeW[] = {'F','I','X','M','E',0}; | |
| WARN("invalid delete, unimplemented exception message\n"); | |
| hres = compile_expression(ctx, expr->expression, TRUE); | |
| if(FAILED(hres)) | |
| return hres; | |
| return push_instr_uint_str(ctx, OP_throw_type, JS_E_INVALID_DELETE, fixmeW); | |
| } | |
| } | |
| return S_OK; | |
| } | |
| static HRESULT compile_assign_expression(compiler_ctx_t *ctx, binary_expression_t *expr, jsop_t op) | |
| { | |
| BOOL use_throw_path = FALSE; | |
| unsigned arg_cnt = 0; | |
| HRESULT hres; | |
| if(expr->expression1->type == EXPR_CALL) { | |
| call_expression_t *call_expr = (call_expression_t*)expr->expression1; | |
| argument_t *arg; | |
| if(op != OP_LAST) { | |
| FIXME("op %d not supported on parametrized assign expressions\n", op); | |
| return E_NOTIMPL; | |
| } | |
| if(is_memberid_expr(call_expr->expression->type) && call_expr->argument_list) { | |
| hres = compile_memberid_expression(ctx, call_expr->expression, fdexNameEnsure); | |
| if(FAILED(hres)) | |
| return hres; | |
| for(arg = call_expr->argument_list; arg; arg = arg->next) { | |
| hres = compile_expression(ctx, arg->expr, TRUE); | |
| if(FAILED(hres)) | |
| return hres; | |
| arg_cnt++; | |
| } | |
| }else { | |
| use_throw_path = TRUE; | |
| } | |
| }else if(is_memberid_expr(expr->expression1->type)) { | |
| hres = compile_memberid_expression(ctx, expr->expression1, fdexNameEnsure); | |
| if(FAILED(hres)) | |
| return hres; | |
| }else { | |
| use_throw_path = TRUE; | |
| } | |
| if(use_throw_path) { | |
| /* Illegal assignment: evaluate and throw */ | |
| hres = compile_expression(ctx, expr->expression1, TRUE); | |
| if(FAILED(hres)) | |
| return hres; | |
| hres = compile_expression(ctx, expr->expression2, TRUE); | |
| if(FAILED(hres)) | |
| return hres; | |
| if(op != OP_LAST && !push_instr(ctx, op)) | |
| return E_OUTOFMEMORY; | |
| return push_instr_uint(ctx, OP_throw_ref, JS_E_ILLEGAL_ASSIGN); | |
| } | |
| if(op != OP_LAST && !push_instr(ctx, OP_refval)) | |
| return E_OUTOFMEMORY; | |
| hres = compile_expression(ctx, expr->expression2, TRUE); | |
| if(FAILED(hres)) | |
| return hres; | |
| if(op != OP_LAST && !push_instr(ctx, op)) | |
| return E_OUTOFMEMORY; | |
| if(arg_cnt) | |
| return push_instr_uint(ctx, OP_assign_call, arg_cnt); | |
| if(!push_instr(ctx, OP_assign)) | |
| return E_OUTOFMEMORY; | |
| return S_OK; | |
| } | |
| static HRESULT compile_typeof_expression(compiler_ctx_t *ctx, unary_expression_t *expr) | |
| { | |
| jsop_t op; | |
| HRESULT hres; | |
| if(is_memberid_expr(expr->expression->type)) { | |
| if(expr->expression->type == EXPR_IDENT) | |
| return push_instr_bstr(ctx, OP_typeofident, ((identifier_expression_t*)expr->expression)->identifier); | |
| op = OP_typeofid; | |
| hres = compile_memberid_expression(ctx, expr->expression, 0); | |
| }else { | |
| op = OP_typeof; | |
| hres = compile_expression(ctx, expr->expression, TRUE); | |
| } | |
| if(FAILED(hres)) | |
| return hres; | |
| return push_instr(ctx, op) ? S_OK : E_OUTOFMEMORY; | |
| } | |
| static HRESULT compile_literal(compiler_ctx_t *ctx, literal_t *literal) | |
| { | |
| switch(literal->type) { | |
| case LT_BOOL: | |
| return push_instr_int(ctx, OP_bool, literal->u.bval); | |
| case LT_DOUBLE: | |
| return push_instr_double(ctx, OP_double, literal->u.dval); | |
| case LT_NULL: | |
| return push_instr(ctx, OP_null) ? S_OK : E_OUTOFMEMORY; | |
| case LT_STRING: | |
| return push_instr_str(ctx, OP_str, literal->u.wstr); | |
| case LT_REGEXP: { | |
| unsigned instr; | |
| jsstr_t *str; | |
| str = compiler_alloc_string_len(ctx, literal->u.regexp.str, literal->u.regexp.str_len); | |
| if(!str) | |
| return E_OUTOFMEMORY; | |
| instr = push_instr(ctx, OP_regexp); | |
| if(!instr) | |
| return E_OUTOFMEMORY; | |
| instr_ptr(ctx, instr)->u.arg[0].str = str; | |
| instr_ptr(ctx, instr)->u.arg[1].uint = literal->u.regexp.flags; | |
| return S_OK; | |
| } | |
| DEFAULT_UNREACHABLE; | |
| } | |
| return E_FAIL; | |
| } | |
| static HRESULT literal_as_bstr(compiler_ctx_t *ctx, literal_t *literal, BSTR *str) | |
| { | |
| switch(literal->type) { | |
| case LT_STRING: | |
| *str = compiler_alloc_bstr(ctx, literal->u.wstr); | |
| break; | |
| case LT_DOUBLE: { | |
| jsstr_t *jsstr; | |
| HRESULT hres; | |
| hres = double_to_string(literal->u.dval, &jsstr); | |
| if(FAILED(hres)) | |
| return hres; | |
| *str = compiler_alloc_bstr_len(ctx, NULL, jsstr_length(jsstr)); | |
| if(*str) | |
| jsstr_flush(jsstr, *str); | |
| jsstr_release(jsstr); | |
| break; | |
| } | |
| DEFAULT_UNREACHABLE; | |
| } | |
| return *str ? S_OK : E_OUTOFMEMORY; | |
| } | |
| static HRESULT compile_array_literal(compiler_ctx_t *ctx, array_literal_expression_t *expr) | |
| { | |
| unsigned i, elem_cnt = expr->length; | |
| array_element_t *iter; | |
| HRESULT hres; | |
| for(iter = expr->element_list; iter; iter = iter->next) { | |
| elem_cnt += iter->elision+1; | |
| for(i=0; i < iter->elision; i++) { | |
| if(!push_instr(ctx, OP_undefined)) | |
| return E_OUTOFMEMORY; | |
| } | |
| hres = compile_expression(ctx, iter->expr, TRUE); | |
| if(FAILED(hres)) | |
| return hres; | |
| } | |
| for(i=0; i < expr->length; i++) { | |
| if(!push_instr(ctx, OP_undefined)) | |
| return E_OUTOFMEMORY; | |
| } | |
| return push_instr_uint(ctx, OP_carray, elem_cnt); | |
| } | |
| static HRESULT compile_object_literal(compiler_ctx_t *ctx, property_value_expression_t *expr) | |
| { | |
| prop_val_t *iter; | |
| unsigned instr; | |
| BSTR name; | |
| HRESULT hres; | |
| if(!push_instr(ctx, OP_new_obj)) | |
| return E_OUTOFMEMORY; | |
| for(iter = expr->property_list; iter; iter = iter->next) { | |
| hres = literal_as_bstr(ctx, iter->name, &name); | |
| if(FAILED(hres)) | |
| return hres; | |
| hres = compile_expression(ctx, iter->value, TRUE); | |
| if(FAILED(hres)) | |
| return hres; | |
| instr = push_instr(ctx, OP_obj_prop); | |
| if(!instr) | |
| return E_OUTOFMEMORY; | |
| instr_ptr(ctx, instr)->u.arg->bstr = name; | |
| } | |
| return S_OK; | |
| } | |
| static HRESULT compile_function_expression(compiler_ctx_t *ctx, function_expression_t *expr, BOOL emit_ret) | |
| { | |
| return emit_ret ? push_instr_uint(ctx, OP_func, expr->func_id) : S_OK; | |
| } | |
| static HRESULT compile_expression(compiler_ctx_t *ctx, expression_t *expr, BOOL emit_ret) | |
| { | |
| HRESULT hres; | |
| switch(expr->type) { | |
| case EXPR_ADD: | |
| hres = compile_binary_expression(ctx, (binary_expression_t*)expr, OP_add); | |
| break; | |
| case EXPR_AND: | |
| hres = compile_logical_expression(ctx, (binary_expression_t*)expr, OP_cnd_z); | |
| break; | |
| case EXPR_ARRAY: | |
| hres = compile_binary_expression(ctx, (binary_expression_t*)expr, OP_array); | |
| break; | |
| case EXPR_ARRAYLIT: | |
| hres = compile_array_literal(ctx, (array_literal_expression_t*)expr); | |
| break; | |
| case EXPR_ASSIGN: | |
| hres = compile_assign_expression(ctx, (binary_expression_t*)expr, OP_LAST); | |
| break; | |
| case EXPR_ASSIGNADD: | |
| hres = compile_assign_expression(ctx, (binary_expression_t*)expr, OP_add); | |
| break; | |
| case EXPR_ASSIGNAND: | |
| hres = compile_assign_expression(ctx, (binary_expression_t*)expr, OP_and); | |
| break; | |
| case EXPR_ASSIGNSUB: | |
| hres = compile_assign_expression(ctx, (binary_expression_t*)expr, OP_sub); | |
| break; | |
| case EXPR_ASSIGNMUL: | |
| hres = compile_assign_expression(ctx, (binary_expression_t*)expr, OP_mul); | |
| break; | |
| case EXPR_ASSIGNDIV: | |
| hres = compile_assign_expression(ctx, (binary_expression_t*)expr, OP_div); | |
| break; | |
| case EXPR_ASSIGNMOD: | |
| hres = compile_assign_expression(ctx, (binary_expression_t*)expr, OP_mod); | |
| break; | |
| case EXPR_ASSIGNOR: | |
| hres = compile_assign_expression(ctx, (binary_expression_t*)expr, OP_or); | |
| break; | |
| case EXPR_ASSIGNLSHIFT: | |
| hres = compile_assign_expression(ctx, (binary_expression_t*)expr, OP_lshift); | |
| break; | |
| case EXPR_ASSIGNRSHIFT: | |
| hres = compile_assign_expression(ctx, (binary_expression_t*)expr, OP_rshift); | |
| break; | |
| case EXPR_ASSIGNRRSHIFT: | |
| hres = compile_assign_expression(ctx, (binary_expression_t*)expr, OP_rshift2); | |
| break; | |
| case EXPR_ASSIGNXOR: | |
| hres = compile_assign_expression(ctx, (binary_expression_t*)expr, OP_xor); | |
| break; | |
| case EXPR_BAND: | |
| hres = compile_binary_expression(ctx, (binary_expression_t*)expr, OP_and); | |
| break; | |
| case EXPR_BITNEG: | |
| hres = compile_unary_expression(ctx, (unary_expression_t*)expr, OP_bneg); | |
| break; | |
| case EXPR_BOR: | |
| hres = compile_binary_expression(ctx, (binary_expression_t*)expr, OP_or); | |
| break; | |
| case EXPR_CALL: | |
| return compile_call_expression(ctx, (call_expression_t*)expr, emit_ret); | |
| case EXPR_COMMA: | |
| return compile_comma_expression(ctx, (binary_expression_t*)expr, emit_ret); | |
| case EXPR_COND: | |
| hres = compile_conditional_expression(ctx, (conditional_expression_t*)expr); | |
| break; | |
| case EXPR_DELETE: | |
| hres = compile_delete_expression(ctx, (unary_expression_t*)expr); | |
| break; | |
| case EXPR_DIV: | |
| hres = compile_binary_expression(ctx, (binary_expression_t*)expr, OP_div); | |
| break; | |
| case EXPR_EQ: | |
| hres = compile_binary_expression(ctx, (binary_expression_t*)expr, OP_eq); | |
| break; | |
| case EXPR_EQEQ: | |
| hres = compile_binary_expression(ctx, (binary_expression_t*)expr, OP_eq2); | |
| break; | |
| case EXPR_FUNC: | |
| return compile_function_expression(ctx, (function_expression_t*)expr, emit_ret); | |
| case EXPR_GREATER: | |
| hres = compile_binary_expression(ctx, (binary_expression_t*)expr, OP_gt); | |
| break; | |
| case EXPR_GREATEREQ: | |
| hres = compile_binary_expression(ctx, (binary_expression_t*)expr, OP_gteq); | |
| break; | |
| case EXPR_IDENT: | |
| hres = emit_identifier(ctx, ((identifier_expression_t*)expr)->identifier); | |
| break; | |
| case EXPR_IN: | |
| hres = compile_binary_expression(ctx, (binary_expression_t*)expr, OP_in); | |
| break; | |
| case EXPR_INSTANCEOF: | |
| hres = compile_binary_expression(ctx, (binary_expression_t*)expr, OP_instanceof); | |
| break; | |
| case EXPR_LESS: | |
| hres = compile_binary_expression(ctx, (binary_expression_t*)expr, OP_lt); | |
| break; | |
| case EXPR_LESSEQ: | |
| hres = compile_binary_expression(ctx, (binary_expression_t*)expr, OP_lteq); | |
| break; | |
| case EXPR_LITERAL: | |
| hres = compile_literal(ctx, ((literal_expression_t*)expr)->literal); | |
| break; | |
| case EXPR_LOGNEG: | |
| hres = compile_unary_expression(ctx, (unary_expression_t*)expr, OP_neg); | |
| break; | |
| case EXPR_LSHIFT: | |
| hres = compile_binary_expression(ctx, (binary_expression_t*)expr, OP_lshift); | |
| break; | |
| case EXPR_MEMBER: | |
| hres = compile_member_expression(ctx, (member_expression_t*)expr); | |
| break; | |
| case EXPR_MINUS: | |
| hres = compile_unary_expression(ctx, (unary_expression_t*)expr, OP_minus); | |
| break; | |
| case EXPR_MOD: | |
| hres = compile_binary_expression(ctx, (binary_expression_t*)expr, OP_mod); | |
| break; | |
| case EXPR_MUL: | |
| hres = compile_binary_expression(ctx, (binary_expression_t*)expr, OP_mul); | |
| break; | |
| case EXPR_NEW: | |
| hres = compile_new_expression(ctx, (call_expression_t*)expr); | |
| break; | |
| case EXPR_NOTEQ: | |
| hres = compile_binary_expression(ctx, (binary_expression_t*)expr, OP_neq); | |
| break; | |
| case EXPR_NOTEQEQ: | |
| hres = compile_binary_expression(ctx, (binary_expression_t*)expr, OP_neq2); | |
| break; | |
| case EXPR_OR: | |
| hres = compile_logical_expression(ctx, (binary_expression_t*)expr, OP_cnd_nz); | |
| break; | |
| case EXPR_PLUS: | |
| hres = compile_unary_expression(ctx, (unary_expression_t*)expr, OP_tonum); | |
| break; | |
| case EXPR_POSTDEC: | |
| hres = compile_increment_expression(ctx, (unary_expression_t*)expr, OP_postinc, -1); | |
| break; | |
| case EXPR_POSTINC: | |
| hres = compile_increment_expression(ctx, (unary_expression_t*)expr, OP_postinc, 1); | |
| break; | |
| case EXPR_PREDEC: | |
| hres = compile_increment_expression(ctx, (unary_expression_t*)expr, OP_preinc, -1); | |
| break; | |
| case EXPR_PREINC: | |
| hres = compile_increment_expression(ctx, (unary_expression_t*)expr, OP_preinc, 1); | |
| break; | |
| case EXPR_PROPVAL: | |
| hres = compile_object_literal(ctx, (property_value_expression_t*)expr); | |
| break; | |
| case EXPR_RSHIFT: | |
| hres = compile_binary_expression(ctx, (binary_expression_t*)expr, OP_rshift); | |
| break; | |
| case EXPR_RRSHIFT: | |
| hres = compile_binary_expression(ctx, (binary_expression_t*)expr, OP_rshift2); | |
| break; | |
| case EXPR_SUB: | |
| hres = compile_binary_expression(ctx, (binary_expression_t*)expr, OP_sub); | |
| break; | |
| case EXPR_THIS: | |
| return !emit_ret || push_instr(ctx, OP_this) ? S_OK : E_OUTOFMEMORY; | |
| case EXPR_TYPEOF: | |
| hres = compile_typeof_expression(ctx, (unary_expression_t*)expr); | |
| break; | |
| case EXPR_VOID: | |
| hres = compile_unary_expression(ctx, (unary_expression_t*)expr, OP_void); | |
| break; | |
| case EXPR_BXOR: | |
| hres = compile_binary_expression(ctx, (binary_expression_t*)expr, OP_xor); | |
| break; | |
| DEFAULT_UNREACHABLE; | |
| } | |
| if(FAILED(hres)) | |
| return hres; | |
| return emit_ret ? S_OK : push_instr_uint(ctx, OP_pop, 1); | |
| } | |
| static inline BOOL is_loop_statement(statement_type_t type) | |
| { | |
| return type == STAT_FOR || type == STAT_FORIN || type == STAT_WHILE; | |
| } | |
| /* ECMA-262 3rd Edition 12.1 */ | |
| static HRESULT compile_block_statement(compiler_ctx_t *ctx, statement_t *iter) | |
| { | |
| HRESULT hres; | |
| while(iter) { | |
| hres = compile_statement(ctx, NULL, iter); | |
| if(FAILED(hres)) | |
| return hres; | |
| iter = iter->next; | |
| } | |
| return S_OK; | |
| } | |
| /* ECMA-262 3rd Edition 12.2 */ | |
| static HRESULT compile_variable_list(compiler_ctx_t *ctx, variable_declaration_t *list) | |
| { | |
| variable_declaration_t *iter; | |
| HRESULT hres; | |
| assert(list != NULL); | |
| for(iter = list; iter; iter = iter->next) { | |
| if(!iter->expr) | |
| continue; | |
| hres = emit_identifier_ref(ctx, iter->identifier, 0); | |
| if(FAILED(hres)) | |
| return hres; | |
| hres = compile_expression(ctx, iter->expr, TRUE); | |
| if(FAILED(hres)) | |
| return hres; | |
| if(!push_instr(ctx, OP_assign)) | |
| return E_OUTOFMEMORY; | |
| hres = push_instr_uint(ctx, OP_pop, 1); | |
| if(FAILED(hres)) | |
| return hres; | |
| } | |
| return S_OK; | |
| } | |
| /* ECMA-262 3rd Edition 12.2 */ | |
| static HRESULT compile_var_statement(compiler_ctx_t *ctx, var_statement_t *stat) | |
| { | |
| return compile_variable_list(ctx, stat->variable_list); | |
| } | |
| /* ECMA-262 3rd Edition 12.4 */ | |
| static HRESULT compile_expression_statement(compiler_ctx_t *ctx, expression_statement_t *stat) | |
| { | |
| HRESULT hres; | |
| hres = compile_expression(ctx, stat->expr, ctx->from_eval); | |
| if(FAILED(hres)) | |
| return hres; | |
| return !ctx->from_eval || push_instr(ctx, OP_setret) ? S_OK : E_OUTOFMEMORY; | |
| } | |
| /* ECMA-262 3rd Edition 12.5 */ | |
| static HRESULT compile_if_statement(compiler_ctx_t *ctx, if_statement_t *stat) | |
| { | |
| unsigned jmp_else; | |
| HRESULT hres; | |
| hres = compile_expression(ctx, stat->expr, TRUE); | |
| if(FAILED(hres)) | |
| return hres; | |
| jmp_else = push_instr(ctx, OP_jmp_z); | |
| if(!jmp_else) | |
| return E_OUTOFMEMORY; | |
| hres = compile_statement(ctx, NULL, stat->if_stat); | |
| if(FAILED(hres)) | |
| return hres; | |
| if(stat->else_stat) { | |
| unsigned jmp_end; | |
| jmp_end = push_instr(ctx, OP_jmp); | |
| if(!jmp_end) | |
| return E_OUTOFMEMORY; | |
| set_arg_uint(ctx, jmp_else, ctx->code_off); | |
| hres = compile_statement(ctx, NULL, stat->else_stat); | |
| if(FAILED(hres)) | |
| return hres; | |
| set_arg_uint(ctx, jmp_end, ctx->code_off); | |
| }else { | |
| set_arg_uint(ctx, jmp_else, ctx->code_off); | |
| } | |
| return S_OK; | |
| } | |
| /* ECMA-262 3rd Edition 12.6.2 */ | |
| static HRESULT compile_while_statement(compiler_ctx_t *ctx, while_statement_t *stat) | |
| { | |
| statement_ctx_t stat_ctx = {0, FALSE, FALSE}; | |
| unsigned jmp_off; | |
| HRESULT hres; | |
| stat_ctx.break_label = alloc_label(ctx); | |
| if(!stat_ctx.break_label) | |
| return E_OUTOFMEMORY; | |
| stat_ctx.continue_label = alloc_label(ctx); | |
| if(!stat_ctx.continue_label) | |
| return E_OUTOFMEMORY; | |
| jmp_off = ctx->code_off; | |
| if(!stat->do_while) { | |
| label_set_addr(ctx, stat_ctx.continue_label); | |
| hres = compile_expression(ctx, stat->expr, TRUE); | |
| if(FAILED(hres)) | |
| return hres; | |
| hres = push_instr_uint(ctx, OP_jmp_z, stat_ctx.break_label); | |
| if(FAILED(hres)) | |
| return hres; | |
| } | |
| hres = compile_statement(ctx, &stat_ctx, stat->statement); | |
| if(FAILED(hres)) | |
| return hres; | |
| if(stat->do_while) { | |
| label_set_addr(ctx, stat_ctx.continue_label); | |
| hres = compile_expression(ctx, stat->expr, TRUE); | |
| if(FAILED(hres)) | |
| return hres; | |
| hres = push_instr_uint(ctx, OP_jmp_z, stat_ctx.break_label); | |
| if(FAILED(hres)) | |
| return hres; | |
| } | |
| hres = push_instr_uint(ctx, OP_jmp, jmp_off); | |
| if(FAILED(hres)) | |
| return hres; | |
| label_set_addr(ctx, stat_ctx.break_label); | |
| return S_OK; | |
| } | |
| /* ECMA-262 3rd Edition 12.6.3 */ | |
| static HRESULT compile_for_statement(compiler_ctx_t *ctx, for_statement_t *stat) | |
| { | |
| statement_ctx_t stat_ctx = {0, FALSE, FALSE}; | |
| unsigned expr_off; | |
| HRESULT hres; | |
| if(stat->variable_list) { | |
| hres = compile_variable_list(ctx, stat->variable_list); | |
| if(FAILED(hres)) | |
| return hres; | |
| }else if(stat->begin_expr) { | |
| hres = compile_expression(ctx, stat->begin_expr, FALSE); | |
| if(FAILED(hres)) | |
| return hres; | |
| } | |
| stat_ctx.break_label = alloc_label(ctx); | |
| if(!stat_ctx.break_label) | |
| return E_OUTOFMEMORY; | |
| stat_ctx.continue_label = alloc_label(ctx); | |
| if(!stat_ctx.continue_label) | |
| return E_OUTOFMEMORY; | |
| expr_off = ctx->code_off; | |
| if(stat->expr) { | |
| hres = compile_expression(ctx, stat->expr, TRUE); | |
| if(FAILED(hres)) | |
| return hres; | |
| hres = push_instr_uint(ctx, OP_jmp_z, stat_ctx.break_label); | |
| if(FAILED(hres)) | |
| return hres; | |
| } | |
| hres = compile_statement(ctx, &stat_ctx, stat->statement); | |
| if(FAILED(hres)) | |
| return hres; | |
| label_set_addr(ctx, stat_ctx.continue_label); | |
| if(stat->end_expr) { | |
| hres = compile_expression(ctx, stat->end_expr, FALSE); | |
| if(FAILED(hres)) | |
| return hres; | |
| } | |
| hres = push_instr_uint(ctx, OP_jmp, expr_off); | |
| if(FAILED(hres)) | |
| return hres; | |
| label_set_addr(ctx, stat_ctx.break_label); | |
| return S_OK; | |
| } | |
| /* ECMA-262 3rd Edition 12.6.4 */ | |
| static HRESULT compile_forin_statement(compiler_ctx_t *ctx, forin_statement_t *stat) | |
| { | |
| statement_ctx_t stat_ctx = {4, FALSE, FALSE}; | |
| HRESULT hres; | |
| if(stat->variable) { | |
| hres = compile_variable_list(ctx, stat->variable); | |
| if(FAILED(hres)) | |
| return hres; | |
| } | |
| stat_ctx.break_label = alloc_label(ctx); | |
| if(!stat_ctx.break_label) | |
| return E_OUTOFMEMORY; | |
| stat_ctx.continue_label = alloc_label(ctx); | |
| if(!stat_ctx.continue_label) | |
| return E_OUTOFMEMORY; | |
| hres = compile_expression(ctx, stat->in_expr, TRUE); | |
| if(FAILED(hres)) | |
| return hres; | |
| if(stat->variable) { | |
| hres = emit_identifier_ref(ctx, stat->variable->identifier, fdexNameEnsure); | |
| if(FAILED(hres)) | |
| return hres; | |
| }else if(is_memberid_expr(stat->expr->type)) { | |
| hres = compile_memberid_expression(ctx, stat->expr, fdexNameEnsure); | |
| if(FAILED(hres)) | |
| return hres; | |
| }else { | |
| hres = push_instr_uint(ctx, OP_throw_ref, JS_E_ILLEGAL_ASSIGN); | |
| if(FAILED(hres)) | |
| return hres; | |
| /* FIXME: compile statement anyways when we depend on compiler to check errors */ | |
| return S_OK; | |
| } | |
| hres = push_instr_int(ctx, OP_int, DISPID_STARTENUM); | |
| if(FAILED(hres)) | |
| return hres; | |
| label_set_addr(ctx, stat_ctx.continue_label); | |
| hres = push_instr_uint(ctx, OP_forin, stat_ctx.break_label); | |
| if(FAILED(hres)) | |
| return E_OUTOFMEMORY; | |
| hres = compile_statement(ctx, &stat_ctx, stat->statement); | |
| if(FAILED(hres)) | |
| return hres; | |
| hres = push_instr_uint(ctx, OP_jmp, stat_ctx.continue_label); | |
| if(FAILED(hres)) | |
| return hres; | |
| label_set_addr(ctx, stat_ctx.break_label); | |
| return S_OK; | |
| } | |
| static HRESULT pop_to_stat(compiler_ctx_t *ctx, statement_ctx_t *stat_ctx) | |
| { | |
| unsigned stack_pop = 0; | |
| statement_ctx_t *iter; | |
| HRESULT hres; | |
| for(iter = ctx->stat_ctx; iter != stat_ctx; iter = iter->next) { | |
| if(iter->using_scope && !push_instr(ctx, OP_pop_scope)) | |
| return E_OUTOFMEMORY; | |
| if(iter->using_except) { | |
| if(stack_pop) { | |
| hres = push_instr_uint(ctx, OP_pop, stack_pop); | |
| if(FAILED(hres)) | |
| return hres; | |
| stack_pop = 0; | |
| } | |
| hres = push_instr_uint(ctx, OP_pop_except, ctx->code_off+1); | |
| if(FAILED(hres)) | |
| return hres; | |
| } | |
| stack_pop += iter->stack_use; | |
| } | |
| if(stack_pop) { | |
| hres = push_instr_uint(ctx, OP_pop, stack_pop); | |
| if(FAILED(hres)) | |
| return hres; | |
| } | |
| return S_OK; | |
| } | |
| /* ECMA-262 3rd Edition 12.7 */ | |
| static HRESULT compile_continue_statement(compiler_ctx_t *ctx, branch_statement_t *stat) | |
| { | |
| statement_ctx_t *pop_ctx; | |
| HRESULT hres; | |
| if(stat->identifier) { | |
| statement_t *label_stat; | |
| statement_ctx_t *iter; | |
| pop_ctx = NULL; | |
| for(iter = ctx->stat_ctx; iter; iter = iter->next) { | |
| if(iter->continue_label) | |
| pop_ctx = iter; | |
| if(iter->labelled_stat && !strcmpW(iter->labelled_stat->identifier, stat->identifier)) | |
| break; | |
| } | |
| if(!iter) { | |
| WARN("Label not found\n"); | |
| return JS_E_LABEL_NOT_FOUND; | |
| } | |
| /* Labelled continue are allowed only on loops */ | |
| for(label_stat = iter->labelled_stat->statement; | |
| label_stat->type == STAT_LABEL; | |
| label_stat = ((labelled_statement_t*)label_stat)->statement); | |
| if(!is_loop_statement(label_stat->type)) { | |
| WARN("Label is not a loop\n"); | |
| return JS_E_INVALID_CONTINUE; | |
| } | |
| assert(pop_ctx != NULL); | |
| }else { | |
| for(pop_ctx = ctx->stat_ctx; pop_ctx; pop_ctx = pop_ctx->next) { | |
| if(pop_ctx->continue_label) | |
| break; | |
| } | |
| if(!pop_ctx) { | |
| WARN("continue outside loop\n"); | |
| return JS_E_INVALID_CONTINUE; | |
| } | |
| } | |
| hres = pop_to_stat(ctx, pop_ctx); | |
| if(FAILED(hres)) | |
| return hres; | |
| return push_instr_uint(ctx, OP_jmp, pop_ctx->continue_label); | |
| } | |
| /* ECMA-262 3rd Edition 12.8 */ | |
| static HRESULT compile_break_statement(compiler_ctx_t *ctx, branch_statement_t *stat) | |
| { | |
| statement_ctx_t *pop_ctx; | |
| HRESULT hres; | |
| if(stat->identifier) { | |
| for(pop_ctx = ctx->stat_ctx; pop_ctx; pop_ctx = pop_ctx->next) { | |
| if(pop_ctx->labelled_stat && !strcmpW(pop_ctx->labelled_stat->identifier, stat->identifier)) { | |
| assert(pop_ctx->break_label); | |
| break; | |
| } | |
| } | |
| if(!pop_ctx) { | |
| WARN("Label not found\n"); | |
| return JS_E_LABEL_NOT_FOUND; | |
| } | |
| }else { | |
| for(pop_ctx = ctx->stat_ctx; pop_ctx; pop_ctx = pop_ctx->next) { | |
| if(pop_ctx->break_label && !pop_ctx->labelled_stat) | |
| break; | |
| } | |
| if(!pop_ctx) { | |
| WARN("Break outside loop\n"); | |
| return JS_E_INVALID_BREAK; | |
| } | |
| } | |
| hres = pop_to_stat(ctx, pop_ctx->next); | |
| if(FAILED(hres)) | |
| return hres; | |
| return push_instr_uint(ctx, OP_jmp, pop_ctx->break_label); | |
| } | |
| /* ECMA-262 3rd Edition 12.9 */ | |
| static HRESULT compile_return_statement(compiler_ctx_t *ctx, expression_statement_t *stat) | |
| { | |
| HRESULT hres; | |
| if(ctx->from_eval) { | |
| WARN("misplaced return statement\n"); | |
| return JS_E_MISPLACED_RETURN; | |
| } | |
| if(stat->expr) { | |
| hres = compile_expression(ctx, stat->expr, TRUE); | |
| if(FAILED(hres)) | |
| return hres; | |
| if(!push_instr(ctx, OP_setret)) | |
| return E_OUTOFMEMORY; | |
| } | |
| hres = pop_to_stat(ctx, NULL); | |
| if(FAILED(hres)) | |
| return hres; | |
| return push_instr_uint(ctx, OP_ret, !stat->expr); | |
| } | |
| /* ECMA-262 3rd Edition 12.10 */ | |
| static HRESULT compile_with_statement(compiler_ctx_t *ctx, with_statement_t *stat) | |
| { | |
| statement_ctx_t stat_ctx = {0, TRUE, FALSE}; | |
| HRESULT hres; | |
| hres = compile_expression(ctx, stat->expr, TRUE); | |
| if(FAILED(hres)) | |
| return hres; | |
| if(!push_instr(ctx, OP_push_scope)) | |
| return E_OUTOFMEMORY; | |
| hres = compile_statement(ctx, &stat_ctx, stat->statement); | |
| if(FAILED(hres)) | |
| return hres; | |
| if(!push_instr(ctx, OP_pop_scope)) | |
| return E_OUTOFMEMORY; | |
| return S_OK; | |
| } | |
| /* ECMA-262 3rd Edition 12.10 */ | |
| static HRESULT compile_labelled_statement(compiler_ctx_t *ctx, labelled_statement_t *stat) | |
| { | |
| statement_ctx_t stat_ctx = {0, FALSE, FALSE, 0, 0, stat}, *iter; | |
| HRESULT hres; | |
| for(iter = ctx->stat_ctx; iter; iter = iter->next) { | |
| if(iter->labelled_stat && !strcmpW(iter->labelled_stat->identifier, stat->identifier)) { | |
| WARN("Label %s redefined\n", debugstr_w(stat->identifier)); | |
| return JS_E_LABEL_REDEFINED; | |
| } | |
| } | |
| /* Labelled breaks are allowed for any labelled statements, not only loops (violating spec) */ | |
| stat_ctx.break_label = alloc_label(ctx); | |
| if(!stat_ctx.break_label) | |
| return E_OUTOFMEMORY; | |
| hres = compile_statement(ctx, &stat_ctx, stat->statement); | |
| if(FAILED(hres)) | |
| return hres; | |
| label_set_addr(ctx, stat_ctx.break_label); | |
| return S_OK; | |
| } | |
| /* ECMA-262 3rd Edition 12.13 */ | |
| static HRESULT compile_switch_statement(compiler_ctx_t *ctx, switch_statement_t *stat) | |
| { | |
| statement_ctx_t stat_ctx = {0, FALSE, FALSE}; | |
| unsigned case_cnt = 0, *case_jmps, i, default_jmp; | |
| BOOL have_default = FALSE; | |
| statement_t *stat_iter; | |
| case_clausule_t *iter; | |
| HRESULT hres; | |
| hres = compile_expression(ctx, stat->expr, TRUE); | |
| if(FAILED(hres)) | |
| return hres; | |
| stat_ctx.break_label = alloc_label(ctx); | |
| if(!stat_ctx.break_label) | |
| return E_OUTOFMEMORY; | |
| for(iter = stat->case_list; iter; iter = iter->next) { | |
| if(iter->expr) | |
| case_cnt++; | |
| } | |
| case_jmps = heap_alloc(case_cnt * sizeof(*case_jmps)); | |
| if(!case_jmps) | |
| return E_OUTOFMEMORY; | |
| i = 0; | |
| for(iter = stat->case_list; iter; iter = iter->next) { | |
| if(!iter->expr) { | |
| have_default = TRUE; | |
| continue; | |
| } | |
| hres = compile_expression(ctx, iter->expr, TRUE); | |
| if(FAILED(hres)) | |
| break; | |
| case_jmps[i] = push_instr(ctx, OP_case); | |
| if(!case_jmps[i]) { | |
| hres = E_OUTOFMEMORY; | |
| break; | |
| } | |
| i++; | |
| } | |
| if(SUCCEEDED(hres)) { | |
| hres = push_instr_uint(ctx, OP_pop, 1); | |
| if(SUCCEEDED(hres)) { | |
| default_jmp = push_instr(ctx, OP_jmp); | |
| if(!default_jmp) | |
| hres = E_OUTOFMEMORY; | |
| } | |
| } | |
| if(FAILED(hres)) { | |
| heap_free(case_jmps); | |
| return hres; | |
| } | |
| i = 0; | |
| for(iter = stat->case_list; iter; iter = iter->next) { | |
| while(iter->next && iter->next->stat == iter->stat) { | |
| set_arg_uint(ctx, iter->expr ? case_jmps[i++] : default_jmp, ctx->code_off); | |
| iter = iter->next; | |
| } | |
| set_arg_uint(ctx, iter->expr ? case_jmps[i++] : default_jmp, ctx->code_off); | |
| for(stat_iter = iter->stat; stat_iter && (!iter->next || iter->next->stat != stat_iter); | |
| stat_iter = stat_iter->next) { | |
| hres = compile_statement(ctx, &stat_ctx, stat_iter); | |
| if(FAILED(hres)) | |
| break; | |
| } | |
| if(FAILED(hres)) | |
| break; | |
| } | |
| heap_free(case_jmps); | |
| if(FAILED(hres)) | |
| return hres; | |
| assert(i == case_cnt); | |
| if(!have_default) { | |
| hres = push_instr_uint(ctx, OP_jmp, stat_ctx.break_label); | |
| if(FAILED(hres)) | |
| return hres; | |
| set_arg_uint(ctx, default_jmp, ctx->code_off); | |
| } | |
| label_set_addr(ctx, stat_ctx.break_label); | |
| return S_OK; | |
| } | |
| /* ECMA-262 3rd Edition 12.13 */ | |
| static HRESULT compile_throw_statement(compiler_ctx_t *ctx, expression_statement_t *stat) | |
| { | |
| HRESULT hres; | |
| hres = compile_expression(ctx, stat->expr, TRUE); | |
| if(FAILED(hres)) | |
| return hres; | |
| return push_instr(ctx, OP_throw) ? S_OK : E_OUTOFMEMORY; | |
| } | |
| /* ECMA-262 3rd Edition 12.14 */ | |
| static HRESULT compile_try_statement(compiler_ctx_t *ctx, try_statement_t *stat) | |
| { | |
| statement_ctx_t try_ctx = {0, FALSE, TRUE}, finally_ctx = {2, FALSE, FALSE}; | |
| unsigned push_except, finally_off = 0, catch_off = 0, pop_except, catch_pop_except = 0; | |
| BSTR ident; | |
| HRESULT hres; | |
| push_except = push_instr(ctx, OP_push_except); | |
| if(!push_except) | |
| return E_OUTOFMEMORY; | |
| if(stat->catch_block) { | |
| ident = compiler_alloc_bstr(ctx, stat->catch_block->identifier); | |
| if(!ident) | |
| return E_OUTOFMEMORY; | |
| }else { | |
| ident = NULL; | |
| } | |
| hres = compile_statement(ctx, &try_ctx, stat->try_statement); | |
| if(FAILED(hres)) | |
| return hres; | |
| pop_except = push_instr(ctx, OP_pop_except); | |
| if(!pop_except) | |
| return E_OUTOFMEMORY; | |
| if(stat->catch_block) { | |
| statement_ctx_t catch_ctx = {0, TRUE, stat->finally_statement != NULL}; | |
| if(stat->finally_statement) | |
| catch_ctx.using_except = TRUE; | |
| catch_off = ctx->code_off; | |
| hres = push_instr_bstr(ctx, OP_enter_catch, ident); | |
| if(FAILED(hres)) | |
| return hres; | |
| hres = compile_statement(ctx, &catch_ctx, stat->catch_block->statement); | |
| if(FAILED(hres)) | |
| return hres; | |
| if(!push_instr(ctx, OP_pop_scope)) | |
| return E_OUTOFMEMORY; | |
| if(stat->finally_statement) { | |
| catch_pop_except = push_instr(ctx, OP_pop_except); | |
| if(!catch_pop_except) | |
| return E_OUTOFMEMORY; | |
| } | |
| } | |
| if(stat->finally_statement) { | |
| /* | |
| * finally block expects two elements on the stack, which may be: | |
| * - (true, return_addr) set by OP_pop_except, OP_end_finally jumps back to passed address | |
| * - (false, exception_value) set when unwinding an exception, which OP_end_finally rethrows | |
| */ | |
| finally_off = ctx->code_off; | |
| hres = compile_statement(ctx, &finally_ctx, stat->finally_statement); | |
| if(FAILED(hres)) | |
| return hres; | |
| if(!push_instr(ctx, OP_end_finally)) | |
| return E_OUTOFMEMORY; | |
| } | |
| instr_ptr(ctx, pop_except)->u.arg[0].uint = ctx->code_off; | |
| if(catch_pop_except) | |
| instr_ptr(ctx, catch_pop_except)->u.arg[0].uint = ctx->code_off; | |
| instr_ptr(ctx, push_except)->u.arg[0].uint = catch_off; | |
| instr_ptr(ctx, push_except)->u.arg[1].uint = finally_off; | |
| return S_OK; | |
| } | |
| static HRESULT compile_statement(compiler_ctx_t *ctx, statement_ctx_t *stat_ctx, statement_t *stat) | |
| { | |
| HRESULT hres; | |
| if(stat_ctx) { | |
| stat_ctx->next = ctx->stat_ctx; | |
| ctx->stat_ctx = stat_ctx; | |
| } | |
| switch(stat->type) { | |
| case STAT_BLOCK: | |
| hres = compile_block_statement(ctx, ((block_statement_t*)stat)->stat_list); | |
| break; | |
| case STAT_BREAK: | |
| hres = compile_break_statement(ctx, (branch_statement_t*)stat); | |
| break; | |
| case STAT_CONTINUE: | |
| hres = compile_continue_statement(ctx, (branch_statement_t*)stat); | |
| break; | |
| case STAT_EMPTY: | |
| /* nothing to do */ | |
| hres = S_OK; | |
| break; | |
| case STAT_EXPR: | |
| hres = compile_expression_statement(ctx, (expression_statement_t*)stat); | |
| break; | |
| case STAT_FOR: | |
| hres = compile_for_statement(ctx, (for_statement_t*)stat); | |
| break; | |
| case STAT_FORIN: | |
| hres = compile_forin_statement(ctx, (forin_statement_t*)stat); | |
| break; | |
| case STAT_IF: | |
| hres = compile_if_statement(ctx, (if_statement_t*)stat); | |
| break; | |
| case STAT_LABEL: | |
| hres = compile_labelled_statement(ctx, (labelled_statement_t*)stat); | |
| break; | |
| case STAT_RETURN: | |
| hres = compile_return_statement(ctx, (expression_statement_t*)stat); | |
| break; | |
| case STAT_SWITCH: | |
| hres = compile_switch_statement(ctx, (switch_statement_t*)stat); | |
| break; | |
| case STAT_THROW: | |
| hres = compile_throw_statement(ctx, (expression_statement_t*)stat); | |
| break; | |
| case STAT_TRY: | |
| hres = compile_try_statement(ctx, (try_statement_t*)stat); | |
| break; | |
| case STAT_VAR: | |
| hres = compile_var_statement(ctx, (var_statement_t*)stat); | |
| break; | |
| case STAT_WHILE: | |
| hres = compile_while_statement(ctx, (while_statement_t*)stat); | |
| break; | |
| case STAT_WITH: | |
| hres = compile_with_statement(ctx, (with_statement_t*)stat); | |
| break; | |
| DEFAULT_UNREACHABLE; | |
| } | |
| if(stat_ctx) { | |
| assert(ctx->stat_ctx == stat_ctx); | |
| ctx->stat_ctx = stat_ctx->next; | |
| } | |
| return hres; | |
| } | |
| static int function_local_cmp(const void *key, const struct wine_rb_entry *entry) | |
| { | |
| function_local_t *local = WINE_RB_ENTRY_VALUE(entry, function_local_t, entry); | |
| return strcmpW(key, local->name); | |
| } | |
| static inline function_local_t *find_local(compiler_ctx_t *ctx, const WCHAR *name) | |
| { | |
| struct wine_rb_entry *entry = wine_rb_get(&ctx->locals, name); | |
| return entry ? WINE_RB_ENTRY_VALUE(entry, function_local_t, entry) : NULL; | |
| } | |
| static BOOL alloc_local(compiler_ctx_t *ctx, BSTR name, int ref) | |
| { | |
| function_local_t *local; | |
| local = heap_pool_alloc(&ctx->heap, sizeof(*local)); | |
| if(!local) | |
| return FALSE; | |
| local->name = name; | |
| local->ref = ref; | |
| wine_rb_put(&ctx->locals, name, &local->entry); | |
| ctx->locals_cnt++; | |
| return TRUE; | |
| } | |
| static BOOL alloc_variable(compiler_ctx_t *ctx, const WCHAR *name) | |
| { | |
| BSTR ident; | |
| if(find_local(ctx, name)) | |
| return TRUE; | |
| ident = compiler_alloc_bstr(ctx, name); | |
| if(!ident) | |
| return FALSE; | |
| return alloc_local(ctx, ident, ctx->func->var_cnt++); | |
| } | |
| static BOOL visit_function_expression(compiler_ctx_t *ctx, function_expression_t *expr) | |
| { | |
| expr->func_id = ctx->func->func_cnt++; | |
| ctx->func_tail = ctx->func_tail ? (ctx->func_tail->next = expr) : (ctx->func_head = expr); | |
| return !expr->identifier || expr->event_target || alloc_variable(ctx, expr->identifier); | |
| } | |
| static HRESULT visit_expression(compiler_ctx_t *ctx, expression_t *expr) | |
| { | |
| HRESULT hres = S_OK; | |
| switch(expr->type) { | |
| case EXPR_ADD: | |
| case EXPR_AND: | |
| case EXPR_ARRAY: | |
| case EXPR_ASSIGN: | |
| case EXPR_ASSIGNADD: | |
| case EXPR_ASSIGNAND: | |
| case EXPR_ASSIGNSUB: | |
| case EXPR_ASSIGNMUL: | |
| case EXPR_ASSIGNDIV: | |
| case EXPR_ASSIGNMOD: | |
| case EXPR_ASSIGNOR: | |
| case EXPR_ASSIGNLSHIFT: | |
| case EXPR_ASSIGNRSHIFT: | |
| case EXPR_ASSIGNRRSHIFT: | |
| case EXPR_ASSIGNXOR: | |
| case EXPR_BAND: | |
| case EXPR_BOR: | |
| case EXPR_COMMA: | |
| case EXPR_DIV: | |
| case EXPR_EQ: | |
| case EXPR_EQEQ: | |
| case EXPR_GREATER: | |
| case EXPR_GREATEREQ: | |
| case EXPR_IN: | |
| case EXPR_INSTANCEOF: | |
| case EXPR_LESS: | |
| case EXPR_LESSEQ: | |
| case EXPR_LSHIFT: | |
| case EXPR_MOD: | |
| case EXPR_MUL: | |
| case EXPR_NOTEQ: | |
| case EXPR_NOTEQEQ: | |
| case EXPR_OR: | |
| case EXPR_RSHIFT: | |
| case EXPR_RRSHIFT: | |
| case EXPR_SUB: | |
| case EXPR_BXOR: { | |
| binary_expression_t *binary_expr = (binary_expression_t*)expr; | |
| hres = visit_expression(ctx, binary_expr->expression1); | |
| if(FAILED(hres)) | |
| return hres; | |
| hres = visit_expression(ctx, binary_expr->expression2); | |
| break; | |
| } | |
| case EXPR_BITNEG: | |
| case EXPR_DELETE: | |
| case EXPR_LOGNEG: | |
| case EXPR_MINUS: | |
| case EXPR_PLUS: | |
| case EXPR_POSTDEC: | |
| case EXPR_POSTINC: | |
| case EXPR_PREDEC: | |
| case EXPR_PREINC: | |
| case EXPR_TYPEOF: | |
| case EXPR_VOID: | |
| hres = visit_expression(ctx, ((unary_expression_t*)expr)->expression); | |
| break; | |
| case EXPR_IDENT: | |
| case EXPR_LITERAL: | |
| case EXPR_THIS: | |
| break; | |
| case EXPR_ARRAYLIT: { | |
| array_literal_expression_t *array_expr = (array_literal_expression_t*)expr; | |
| array_element_t *iter; | |
| for(iter = array_expr->element_list; iter; iter = iter->next) { | |
| hres = visit_expression(ctx, iter->expr); | |
| if(FAILED(hres)) | |
| return hres; | |
| } | |
| break; | |
| } | |
| case EXPR_CALL: | |
| case EXPR_NEW: { | |
| call_expression_t *call_expr = (call_expression_t*)expr; | |
| argument_t *arg; | |
| hres = visit_expression(ctx, call_expr->expression); | |
| if(FAILED(hres)) | |
| return hres; | |
| for(arg = call_expr->argument_list; arg; arg = arg->next) { | |
| hres = visit_expression(ctx, arg->expr); | |
| if(FAILED(hres)) | |
| return hres; | |
| } | |
| break; | |
| } | |
| case EXPR_COND: { | |
| conditional_expression_t *cond_expr = (conditional_expression_t*)expr; | |
| hres = visit_expression(ctx, cond_expr->expression); | |
| if(FAILED(hres)) | |
| return hres; | |
| hres = visit_expression(ctx, cond_expr->true_expression); | |
| if(FAILED(hres)) | |
| return hres; | |
| hres = visit_expression(ctx, cond_expr->false_expression); | |
| break; | |
| } | |
| case EXPR_FUNC: | |
| visit_function_expression(ctx, (function_expression_t*)expr); | |
| break; | |
| case EXPR_MEMBER: | |
| hres = visit_expression(ctx, ((member_expression_t*)expr)->expression); | |
| break; | |
| case EXPR_PROPVAL: { | |
| prop_val_t *iter; | |
| for(iter = ((property_value_expression_t*)expr)->property_list; iter; iter = iter->next) { | |
| hres = visit_expression(ctx, iter->value); | |
| if(FAILED(hres)) | |
| return hres; | |
| } | |
| break; | |
| } | |
| DEFAULT_UNREACHABLE; | |
| } | |
| return hres; | |
| } | |
| static HRESULT visit_variable_list(compiler_ctx_t *ctx, variable_declaration_t *list) | |
| { | |
| variable_declaration_t *iter; | |
| HRESULT hres; | |
| for(iter = list; iter; iter = iter->next) { | |
| if(!alloc_variable(ctx, iter->identifier)) | |
| return E_OUTOFMEMORY; | |
| if(iter->expr) { | |
| hres = visit_expression(ctx, iter->expr); | |
| if(FAILED(hres)) | |
| return hres; | |
| } | |
| } | |
| return S_OK; | |
| } | |
| static HRESULT visit_statement(compiler_ctx_t*,statement_t*); | |
| static HRESULT visit_block_statement(compiler_ctx_t *ctx, statement_t *iter) | |
| { | |
| HRESULT hres; | |
| while(iter) { | |
| hres = visit_statement(ctx, iter); | |
| if(FAILED(hres)) | |
| return hres; | |
| iter = iter->next; | |
| } | |
| return S_OK; | |
| } | |
| static HRESULT visit_statement(compiler_ctx_t *ctx, statement_t *stat) | |
| { | |
| HRESULT hres = S_OK; | |
| switch(stat->type) { | |
| case STAT_BLOCK: | |
| hres = visit_block_statement(ctx, ((block_statement_t*)stat)->stat_list); | |
| break; | |
| case STAT_BREAK: | |
| case STAT_CONTINUE: | |
| case STAT_EMPTY: | |
| break; | |
| case STAT_EXPR: | |
| case STAT_RETURN: | |
| case STAT_THROW: { | |
| expression_statement_t *expr_stat = (expression_statement_t*)stat; | |
| if(expr_stat->expr) | |
| hres = visit_expression(ctx, expr_stat->expr); | |
| break; | |
| } | |
| case STAT_FOR: { | |
| for_statement_t *for_stat = (for_statement_t*)stat; | |
| if(for_stat->variable_list) | |
| hres = visit_variable_list(ctx, for_stat->variable_list); | |
| else if(for_stat->begin_expr) | |
| hres = visit_expression(ctx, for_stat->begin_expr); | |
| if(FAILED(hres)) | |
| break; | |
| if(for_stat->expr) { | |
| hres = visit_expression(ctx, for_stat->expr); | |
| if(FAILED(hres)) | |
| break; | |
| } | |
| hres = visit_statement(ctx, for_stat->statement); | |
| if(FAILED(hres)) | |
| break; | |
| if(for_stat->end_expr) | |
| hres = visit_expression(ctx, for_stat->end_expr); | |
| break; | |
| } | |
| case STAT_FORIN: { | |
| forin_statement_t *forin_stat = (forin_statement_t*)stat; | |
| if(forin_stat->variable) { | |
| hres = visit_variable_list(ctx, forin_stat->variable); | |
| if(FAILED(hres)) | |
| break; | |
| } | |
| hres = visit_expression(ctx, forin_stat->in_expr); | |
| if(FAILED(hres)) | |
| return hres; | |
| if(forin_stat->expr) { | |
| hres = visit_expression(ctx, forin_stat->expr); | |
| if(FAILED(hres)) | |
| return hres; | |
| } | |
| hres = visit_statement(ctx, forin_stat->statement); | |
| break; | |
| } | |
| case STAT_IF: { | |
| if_statement_t *if_stat = (if_statement_t*)stat; | |
| hres = visit_expression(ctx, if_stat->expr); | |
| if(FAILED(hres)) | |
| return hres; | |
| hres = visit_statement(ctx, if_stat->if_stat); | |
| if(FAILED(hres)) | |
| return hres; | |
| if(if_stat->else_stat) | |
| hres = visit_statement(ctx, if_stat->else_stat); | |
| break; | |
| } | |
| case STAT_LABEL: | |
| hres = visit_statement(ctx, ((labelled_statement_t*)stat)->statement); | |
| break; | |
| case STAT_SWITCH: { | |
| switch_statement_t *switch_stat = (switch_statement_t*)stat; | |
| statement_t *stat_iter; | |
| case_clausule_t *iter; | |
| hres = visit_expression(ctx, switch_stat->expr); | |
| if(FAILED(hres)) | |
| return hres; | |
| for(iter = switch_stat->case_list; iter; iter = iter->next) { | |
| if(!iter->expr) | |
| continue; | |
| hres = visit_expression(ctx, iter->expr); | |
| if(FAILED(hres)) | |
| return hres; | |
| } | |
| for(iter = switch_stat->case_list; iter; iter = iter->next) { | |
| while(iter->next && iter->next->stat == iter->stat) | |
| iter = iter->next; | |
| for(stat_iter = iter->stat; stat_iter && (!iter->next || iter->next->stat != stat_iter); | |
| stat_iter = stat_iter->next) { | |
| hres = visit_statement(ctx, stat_iter); | |
| if(FAILED(hres)) | |
| return hres; | |
| } | |
| } | |
| break; | |
| } | |
| case STAT_TRY: { | |
| try_statement_t *try_stat = (try_statement_t*)stat; | |
| hres = visit_statement(ctx, try_stat->try_statement); | |
| if(FAILED(hres)) | |
| return hres; | |
| if(try_stat->catch_block) { | |
| hres = visit_statement(ctx, try_stat->catch_block->statement); | |
| if(FAILED(hres)) | |
| return hres; | |
| } | |
| if(try_stat->finally_statement) | |
| hres = visit_statement(ctx, try_stat->finally_statement); | |
| break; | |
| } | |
| case STAT_VAR: | |
| hres = visit_variable_list(ctx, ((var_statement_t*)stat)->variable_list); | |
| break; | |
| case STAT_WHILE: { | |
| while_statement_t *while_stat = (while_statement_t*)stat; | |
| hres = visit_expression(ctx, while_stat->expr); | |
| if(FAILED(hres)) | |
| return hres; | |
| hres = visit_statement(ctx, while_stat->statement); | |
| break; | |
| } | |
| case STAT_WITH: { | |
| with_statement_t *with_stat = (with_statement_t*)stat; | |
| hres = visit_expression(ctx, with_stat->expr); | |
| if(FAILED(hres)) | |
| return hres; | |
| hres = visit_statement(ctx, with_stat->statement); | |
| break; | |
| } | |
| DEFAULT_UNREACHABLE; | |
| } | |
| return hres; | |
| } | |
| static void resolve_labels(compiler_ctx_t *ctx, unsigned off) | |
| { | |
| instr_t *instr; | |
| for(instr = ctx->code->instrs+off; instr < ctx->code->instrs+ctx->code_off; instr++) { | |
| if(instr_info[instr->op].arg1_type == ARG_ADDR && (instr->u.arg->uint & LABEL_FLAG)) { | |
| assert((instr->u.arg->uint & ~LABEL_FLAG) < ctx->labels_cnt); | |
| instr->u.arg->uint = ctx->labels[instr->u.arg->uint & ~LABEL_FLAG]; | |
| } | |
| assert(instr_info[instr->op].arg2_type != ARG_ADDR); | |
| } | |
| ctx->labels_cnt = 0; | |
| } | |
| void release_bytecode(bytecode_t *code) | |
| { | |
| unsigned i; | |
| if(--code->ref) | |
| return; | |
| for(i=0; i < code->bstr_cnt; i++) | |
| SysFreeString(code->bstr_pool[i]); | |
| for(i=0; i < code->str_cnt; i++) | |
| jsstr_release(code->str_pool[i]); | |
| heap_free(code->source); | |
| heap_pool_free(&code->heap); | |
| heap_free(code->bstr_pool); | |
| heap_free(code->str_pool); | |
| heap_free(code->instrs); | |
| heap_free(code); | |
| } | |
| static HRESULT init_code(compiler_ctx_t *compiler, const WCHAR *source) | |
| { | |
| compiler->code = heap_alloc_zero(sizeof(bytecode_t)); | |
| if(!compiler->code) | |
| return E_OUTOFMEMORY; | |
| compiler->code->ref = 1; | |
| heap_pool_init(&compiler->code->heap); | |
| compiler->code->source = heap_strdupW(source); | |
| if(!compiler->code->source) { | |
| release_bytecode(compiler->code); | |
| return E_OUTOFMEMORY; | |
| } | |
| compiler->code->instrs = heap_alloc(64 * sizeof(instr_t)); | |
| if(!compiler->code->instrs) { | |
| release_bytecode(compiler->code); | |
| return E_OUTOFMEMORY; | |
| } | |
| compiler->code_size = 64; | |
| compiler->code_off = 1; | |
| return S_OK; | |
| } | |
| static HRESULT compile_function(compiler_ctx_t *ctx, source_elements_t *source, function_expression_t *func_expr, | |
| BOOL from_eval, function_code_t *func) | |
| { | |
| function_expression_t *iter; | |
| function_local_t *local; | |
| unsigned off, i; | |
| HRESULT hres; | |
| TRACE("\n"); | |
| ctx->func_head = ctx->func_tail = NULL; | |
| ctx->from_eval = from_eval; | |
| ctx->func = func; | |
| ctx->locals_cnt = 0; | |
| wine_rb_init(&ctx->locals, function_local_cmp); | |
| if(func_expr) { | |
| parameter_t *param_iter; | |
| if(func_expr->identifier) { | |
| func->name = compiler_alloc_bstr(ctx, func_expr->identifier); | |
| if(!func->name) | |
| return E_OUTOFMEMORY; | |
| } | |
| if(func_expr->event_target) { | |
| func->event_target = compiler_alloc_bstr(ctx, func_expr->event_target); | |
| if(!func->event_target) | |
| return E_OUTOFMEMORY; | |
| } | |
| func->source = func_expr->src_str; | |
| func->source_len = func_expr->src_len; | |
| for(param_iter = func_expr->parameter_list; param_iter; param_iter = param_iter->next) | |
| func->param_cnt++; | |
| func->params = compiler_alloc(ctx->code, func->param_cnt * sizeof(*func->params)); | |
| if(!func->params) | |
| return E_OUTOFMEMORY; | |
| for(param_iter = func_expr->parameter_list, i=0; param_iter; param_iter = param_iter->next, i++) { | |
| func->params[i] = compiler_alloc_bstr(ctx, param_iter->identifier); | |
| if(!func->params[i]) | |
| return E_OUTOFMEMORY; | |
| } | |
| } | |
| for(i = 0; i < func->param_cnt; i++) { | |
| if(!find_local(ctx, func->params[i]) && !alloc_local(ctx, func->params[i], -i-1)) | |
| return E_OUTOFMEMORY; | |
| } | |
| hres = visit_block_statement(ctx, source->statement); | |
| if(FAILED(hres)) | |
| return hres; | |
| func->locals = compiler_alloc(ctx->code, ctx->locals_cnt * sizeof(*func->locals)); | |
| if(!func->locals) | |
| return E_OUTOFMEMORY; | |
| func->locals_cnt = ctx->locals_cnt; | |
| func->variables = compiler_alloc(ctx->code, func->var_cnt * sizeof(*func->variables)); | |
| if(!func->variables) | |
| return E_OUTOFMEMORY; | |
| i = 0; | |
| WINE_RB_FOR_EACH_ENTRY(local, &ctx->locals, function_local_t, entry) { | |
| func->locals[i].name = local->name; | |
| func->locals[i].ref = local->ref; | |
| if(local->ref >= 0) { | |
| func->variables[local->ref].name = local->name; | |
| func->variables[local->ref].func_id = -1; | |
| } | |
| i++; | |
| } | |
| assert(i == ctx->locals_cnt); | |
| func->funcs = compiler_alloc(ctx->code, func->func_cnt * sizeof(*func->funcs)); | |
| if(!func->funcs) | |
| return E_OUTOFMEMORY; | |
| memset(func->funcs, 0, func->func_cnt * sizeof(*func->funcs)); | |
| off = ctx->code_off; | |
| hres = compile_block_statement(ctx, source->statement); | |
| if(FAILED(hres)) | |
| return hres; | |
| resolve_labels(ctx, off); | |
| hres = push_instr_uint(ctx, OP_ret, !from_eval); | |
| if(FAILED(hres)) | |
| return hres; | |
| if(TRACE_ON(jscript_disas)) | |
| dump_code(ctx, off); | |
| func->instr_off = off; | |
| for(iter = ctx->func_head, i=0; iter; iter = iter->next, i++) { | |
| hres = compile_function(ctx, iter->source_elements, iter, FALSE, func->funcs+i); | |
| if(FAILED(hres)) | |
| return hres; | |
| TRACE("[%d] func %s\n", i, debugstr_w(func->funcs[i].name)); | |
| if(func->funcs[i].name && !func->funcs[i].event_target) { | |
| local_ref_t *local_ref = lookup_local(func, func->funcs[i].name); | |
| func->funcs[i].local_ref = local_ref->ref; | |
| TRACE("found ref %s %d for %s\n", debugstr_w(local_ref->name), local_ref->ref, debugstr_w(func->funcs[i].name)); | |
| if(local_ref->ref >= 0) | |
| func->variables[local_ref->ref].func_id = i; | |
| } | |
| } | |
| assert(i == func->func_cnt); | |
| return S_OK; | |
| } | |
| static HRESULT parse_arguments(compiler_ctx_t *ctx, const WCHAR *args, BSTR *arg_array, unsigned *args_size) | |
| { | |
| const WCHAR *ptr = args, *ptr2; | |
| unsigned arg_cnt = 0; | |
| while(isspaceW(*ptr)) | |
| ptr++; | |
| if(!*ptr) { | |
| if(args_size) | |
| *args_size = 0; | |
| return S_OK; | |
| } | |
| while(1) { | |
| if(!isalphaW(*ptr) && *ptr != '_') { | |
| FIXME("expected alpha or '_': %s\n", debugstr_w(ptr)); | |
| return E_FAIL; | |
| } | |
| ptr2 = ptr; | |
| while(isalnumW(*ptr) || *ptr == '_') | |
| ptr++; | |
| if(*ptr && *ptr != ',' && !isspaceW(*ptr)) { | |
| FIXME("unexpected har %s\n", debugstr_w(ptr)); | |
| return E_FAIL; | |
| } | |
| if(arg_array) { | |
| arg_array[arg_cnt] = compiler_alloc_bstr_len(ctx, ptr2, ptr-ptr2); | |
| if(!arg_array[arg_cnt]) | |
| return E_OUTOFMEMORY; | |
| } | |
| arg_cnt++; | |
| while(isspaceW(*ptr)) | |
| ptr++; | |
| if(!*ptr) | |
| break; | |
| if(*ptr != ',') { | |
| FIXME("expected ',': %s\n", debugstr_w(ptr)); | |
| return E_FAIL; | |
| } | |
| ptr++; | |
| while(isspaceW(*ptr)) | |
| ptr++; | |
| } | |
| if(args_size) | |
| *args_size = arg_cnt; | |
| return S_OK; | |
| } | |
| static HRESULT compile_arguments(compiler_ctx_t *ctx, const WCHAR *args) | |
| { | |
| HRESULT hres; | |
| hres = parse_arguments(ctx, args, NULL, &ctx->code->global_code.param_cnt); | |
| if(FAILED(hres)) | |
| return hres; | |
| ctx->code->global_code.params = compiler_alloc(ctx->code, | |
| ctx->code->global_code.param_cnt * sizeof(*ctx->code->global_code.params)); | |
| if(!ctx->code->global_code.params) | |
| return E_OUTOFMEMORY; | |
| return parse_arguments(ctx, args, ctx->code->global_code.params, NULL); | |
| } | |
| HRESULT compile_script(script_ctx_t *ctx, const WCHAR *code, const WCHAR *args, const WCHAR *delimiter, | |
| BOOL from_eval, BOOL use_decode, bytecode_t **ret) | |
| { | |
| compiler_ctx_t compiler = {0}; | |
| HRESULT hres; | |
| hres = init_code(&compiler, code); | |
| if(FAILED(hres)) | |
| return hres; | |
| if(args) { | |
| hres = compile_arguments(&compiler, args); | |
| if(FAILED(hres)) | |
| return hres; | |
| } | |
| if(use_decode) { | |
| hres = decode_source(compiler.code->source); | |
| if(FAILED(hres)) { | |
| WARN("Decoding failed\n"); | |
| return hres; | |
| } | |
| } | |
| hres = script_parse(ctx, compiler.code->source, delimiter, from_eval, &compiler.parser); | |
| if(FAILED(hres)) { | |
| release_bytecode(compiler.code); | |
| return hres; | |
| } | |
| heap_pool_init(&compiler.heap); | |
| hres = compile_function(&compiler, compiler.parser->source, NULL, from_eval, &compiler.code->global_code); | |
| heap_pool_free(&compiler.heap); | |
| parser_release(compiler.parser); | |
| if(FAILED(hres)) { | |
| release_bytecode(compiler.code); | |
| return hres; | |
| } | |
| *ret = compiler.code; | |
| return S_OK; | |
| } |