Mercurial > hgrepos > Python2 > PyMuPDF
diff mupdf-source/thirdparty/mujs/jscompile.c @ 2:b50eed0cc0ef upstream
ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4.
The directory name has changed: no version number in the expanded directory now.
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Mon, 15 Sep 2025 11:43:07 +0200 |
| parents | |
| children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mupdf-source/thirdparty/mujs/jscompile.c Mon Sep 15 11:43:07 2025 +0200 @@ -0,0 +1,1428 @@ +#include "jsi.h" + +#define cexp jsC_cexp /* collision with math.h */ + +#define JF js_State *J, js_Function *F + +JS_NORETURN void jsC_error(js_State *J, js_Ast *node, const char *fmt, ...) JS_PRINTFLIKE(3,4); + +static void cfunbody(JF, js_Ast *name, js_Ast *params, js_Ast *body, int is_fun_exp); +static void cexp(JF, js_Ast *exp); +static void cstmlist(JF, js_Ast *list); +static void cstm(JF, js_Ast *stm); + +void jsC_error(js_State *J, js_Ast *node, const char *fmt, ...) +{ + va_list ap; + char buf[512]; + char msgbuf[256]; + + va_start(ap, fmt); + vsnprintf(msgbuf, 256, fmt, ap); + va_end(ap); + + snprintf(buf, 256, "%s:%d: ", J->filename, node->line); + strcat(buf, msgbuf); + + js_newsyntaxerror(J, buf); + js_throw(J); +} + +static const char *futurewords[] = { + "class", "const", "enum", "export", "extends", "import", "super", +}; + +static const char *strictfuturewords[] = { + "implements", "interface", "let", "package", "private", "protected", + "public", "static", "yield", +}; + +static void checkfutureword(JF, js_Ast *exp) +{ + if (jsY_findword(exp->string, futurewords, nelem(futurewords)) >= 0) + jsC_error(J, exp, "'%s' is a future reserved word", exp->string); + if (F->strict) { + if (jsY_findword(exp->string, strictfuturewords, nelem(strictfuturewords)) >= 0) + jsC_error(J, exp, "'%s' is a strict mode future reserved word", exp->string); + } +} + +static js_Function *newfun(js_State *J, int line, js_Ast *name, js_Ast *params, js_Ast *body, int script, int default_strict, int is_fun_exp) +{ + js_Function *F = js_malloc(J, sizeof *F); + memset(F, 0, sizeof *F); + F->gcmark = 0; + F->gcnext = J->gcfun; + J->gcfun = F; + ++J->gccounter; + + F->filename = js_intern(J, J->filename); + F->line = line; + F->script = script; + F->strict = default_strict; + F->name = name ? name->string : ""; + + cfunbody(J, F, name, params, body, is_fun_exp); + + return F; +} + +/* Emit opcodes, constants and jumps */ + +static void emitraw(JF, int value) +{ + if (value != (js_Instruction)value) + js_syntaxerror(J, "integer overflow in instruction coding"); + if (F->codelen >= F->codecap) { + F->codecap = F->codecap ? F->codecap * 2 : 64; + F->code = js_realloc(J, F->code, F->codecap * sizeof *F->code); + } + F->code[F->codelen++] = value; +} + +static void emit(JF, int value) +{ + emitraw(J, F, F->lastline); + emitraw(J, F, value); +} + +static void emitarg(JF, int value) +{ + emitraw(J, F, value); +} + +static void emitline(JF, js_Ast *node) +{ + F->lastline = node->line; +} + +static int addfunction(JF, js_Function *value) +{ + if (F->funlen >= F->funcap) { + F->funcap = F->funcap ? F->funcap * 2 : 16; + F->funtab = js_realloc(J, F->funtab, F->funcap * sizeof *F->funtab); + } + F->funtab[F->funlen] = value; + return F->funlen++; +} + +static int addlocal(JF, js_Ast *ident, int reuse) +{ + const char *name = ident->string; + if (F->strict) { + if (!strcmp(name, "arguments")) + jsC_error(J, ident, "redefining 'arguments' is not allowed in strict mode"); + if (!strcmp(name, "eval")) + jsC_error(J, ident, "redefining 'eval' is not allowed in strict mode"); + } else { + if (!strcmp(name, "eval")) + js_evalerror(J, "%s:%d: invalid use of 'eval'", J->filename, ident->line); + } + if (reuse || F->strict) { + int i; + for (i = 0; i < F->varlen; ++i) { + if (!strcmp(F->vartab[i], name)) { + if (reuse) + return i+1; + if (F->strict) + jsC_error(J, ident, "duplicate formal parameter '%s'", name); + } + } + } + if (F->varlen >= F->varcap) { + F->varcap = F->varcap ? F->varcap * 2 : 16; + F->vartab = js_realloc(J, F->vartab, F->varcap * sizeof *F->vartab); + } + F->vartab[F->varlen] = name; + return ++F->varlen; +} + +static int findlocal(JF, const char *name) +{ + int i; + for (i = F->varlen; i > 0; --i) + if (!strcmp(F->vartab[i-1], name)) + return i; + return -1; +} + +static void emitfunction(JF, js_Function *fun) +{ + F->lightweight = 0; + emit(J, F, OP_CLOSURE); + emitarg(J, F, addfunction(J, F, fun)); +} + +static void emitnumber(JF, double num) +{ + if (num == 0) { + emit(J, F, OP_INTEGER); + emitarg(J, F, 32768); + if (signbit(num)) + emit(J, F, OP_NEG); + } else if (num >= SHRT_MIN && num <= SHRT_MAX && num == (int)num) { + emit(J, F, OP_INTEGER); + emitarg(J, F, num + 32768); + } else { +#define N (sizeof(num) / sizeof(js_Instruction)) + js_Instruction x[N]; + size_t i; + emit(J, F, OP_NUMBER); + memcpy(x, &num, sizeof(num)); + for (i = 0; i < N; ++i) + emitarg(J, F, x[i]); +#undef N + } +} + +static void emitstring(JF, int opcode, const char *str) +{ +#define N (sizeof(str) / sizeof(js_Instruction)) + js_Instruction x[N]; + size_t i; + emit(J, F, opcode); + memcpy(x, &str, sizeof(str)); + for (i = 0; i < N; ++i) + emitarg(J, F, x[i]); +#undef N +} + +static void emitlocal(JF, int oploc, int opvar, js_Ast *ident) +{ + int is_arguments = !strcmp(ident->string, "arguments"); + int is_eval = !strcmp(ident->string, "eval"); + int i; + + if (is_arguments) { + F->lightweight = 0; + F->arguments = 1; + } + + checkfutureword(J, F, ident); + if (F->strict && oploc == OP_SETLOCAL) { + if (is_arguments) + jsC_error(J, ident, "'arguments' is read-only in strict mode"); + if (is_eval) + jsC_error(J, ident, "'eval' is read-only in strict mode"); + } + if (is_eval) + js_evalerror(J, "%s:%d: invalid use of 'eval'", J->filename, ident->line); + + i = findlocal(J, F, ident->string); + if (i < 0) { + emitstring(J, F, opvar, ident->string); + } else { + emit(J, F, oploc); + emitarg(J, F, i); + } +} + +static int here(JF) +{ + return F->codelen; +} + +static int emitjump(JF, int opcode) +{ + int inst; + emit(J, F, opcode); + inst = F->codelen; + emitarg(J, F, 0); + return inst; +} + +static void emitjumpto(JF, int opcode, int dest) +{ + emit(J, F, opcode); + if (dest != (js_Instruction)dest) + js_syntaxerror(J, "jump address integer overflow"); + emitarg(J, F, dest); +} + +static void labelto(JF, int inst, int addr) +{ + if (addr != (js_Instruction)addr) + js_syntaxerror(J, "jump address integer overflow"); + F->code[inst] = addr; +} + +static void label(JF, int inst) +{ + labelto(J, F, inst, F->codelen); +} + +/* Expressions */ + +static void ctypeof(JF, js_Ast *exp) +{ + if (exp->a->type == EXP_IDENTIFIER) { + emitline(J, F, exp->a); + emitlocal(J, F, OP_GETLOCAL, OP_HASVAR, exp->a); + } else { + cexp(J, F, exp->a); + } + emitline(J, F, exp); + emit(J, F, OP_TYPEOF); +} + +static void cunary(JF, js_Ast *exp, int opcode) +{ + cexp(J, F, exp->a); + emitline(J, F, exp); + emit(J, F, opcode); +} + +static void cbinary(JF, js_Ast *exp, int opcode) +{ + cexp(J, F, exp->a); + cexp(J, F, exp->b); + emitline(J, F, exp); + emit(J, F, opcode); +} + +static void carray(JF, js_Ast *list) +{ + while (list) { + emitline(J, F, list->a); + if (list->a->type == EXP_ELISION) { + emit(J, F, OP_SKIPARRAY); + } else { + cexp(J, F, list->a); + emit(J, F, OP_INITARRAY); + } + list = list->b; + } +} + +static void checkdup(JF, js_Ast *list, js_Ast *end) +{ + char nbuf[32], sbuf[32]; + const char *needle, *straw; + + if (end->a->type == EXP_NUMBER) + needle = jsV_numbertostring(J, nbuf, end->a->number); + else + needle = end->a->string; + + while (list->a != end) { + if (list->a->type == end->type) { + js_Ast *prop = list->a->a; + if (prop->type == EXP_NUMBER) + straw = jsV_numbertostring(J, sbuf, prop->number); + else + straw = prop->string; + if (!strcmp(needle, straw)) + jsC_error(J, list, "duplicate property '%s' in object literal", needle); + } + list = list->b; + } +} + +static void cobject(JF, js_Ast *list) +{ + js_Ast *head = list; + + while (list) { + js_Ast *kv = list->a; + js_Ast *prop = kv->a; + + if (prop->type == AST_IDENTIFIER || prop->type == EXP_STRING) { + emitline(J, F, prop); + emitstring(J, F, OP_STRING, prop->string); + } else if (prop->type == EXP_NUMBER) { + emitline(J, F, prop); + emitnumber(J, F, prop->number); + } else { + jsC_error(J, prop, "invalid property name in object initializer"); + } + + if (F->strict) + checkdup(J, F, head, kv); + + switch (kv->type) { + default: /* impossible */ break; + case EXP_PROP_VAL: + cexp(J, F, kv->b); + emitline(J, F, kv); + emit(J, F, OP_INITPROP); + break; + case EXP_PROP_GET: + emitfunction(J, F, newfun(J, prop->line, NULL, NULL, kv->c, 0, F->strict, 1)); + emitline(J, F, kv); + emit(J, F, OP_INITGETTER); + break; + case EXP_PROP_SET: + emitfunction(J, F, newfun(J, prop->line, NULL, kv->b, kv->c, 0, F->strict, 1)); + emitline(J, F, kv); + emit(J, F, OP_INITSETTER); + break; + } + + list = list->b; + } +} + +static int cargs(JF, js_Ast *list) +{ + int n = 0; + while (list) { + cexp(J, F, list->a); + list = list->b; + ++n; + } + return n; +} + +static void cassign(JF, js_Ast *exp) +{ + js_Ast *lhs = exp->a; + js_Ast *rhs = exp->b; + switch (lhs->type) { + case EXP_IDENTIFIER: + cexp(J, F, rhs); + emitline(J, F, exp); + emitlocal(J, F, OP_SETLOCAL, OP_SETVAR, lhs); + break; + case EXP_INDEX: + cexp(J, F, lhs->a); + cexp(J, F, lhs->b); + cexp(J, F, rhs); + emitline(J, F, exp); + emit(J, F, OP_SETPROP); + break; + case EXP_MEMBER: + cexp(J, F, lhs->a); + cexp(J, F, rhs); + emitline(J, F, exp); + emitstring(J, F, OP_SETPROP_S, lhs->b->string); + break; + default: + jsC_error(J, lhs, "invalid l-value in assignment"); + } +} + +static void cassignforin(JF, js_Ast *stm) +{ + js_Ast *lhs = stm->a; + + if (stm->type == STM_FOR_IN_VAR) { + if (lhs->b) + jsC_error(J, lhs->b, "more than one loop variable in for-in statement"); + emitline(J, F, lhs->a); + emitlocal(J, F, OP_SETLOCAL, OP_SETVAR, lhs->a->a); /* list(var-init(ident)) */ + emit(J, F, OP_POP); + return; + } + + switch (lhs->type) { + case EXP_IDENTIFIER: + emitline(J, F, lhs); + emitlocal(J, F, OP_SETLOCAL, OP_SETVAR, lhs); + emit(J, F, OP_POP); + break; + case EXP_INDEX: + cexp(J, F, lhs->a); + cexp(J, F, lhs->b); + emitline(J, F, lhs); + emit(J, F, OP_ROT3); + emit(J, F, OP_SETPROP); + emit(J, F, OP_POP); + break; + case EXP_MEMBER: + cexp(J, F, lhs->a); + emitline(J, F, lhs); + emit(J, F, OP_ROT2); + emitstring(J, F, OP_SETPROP_S, lhs->b->string); + emit(J, F, OP_POP); + break; + default: + jsC_error(J, lhs, "invalid l-value in for-in loop assignment"); + } +} + +static void cassignop1(JF, js_Ast *lhs) +{ + switch (lhs->type) { + case EXP_IDENTIFIER: + emitline(J, F, lhs); + emitlocal(J, F, OP_GETLOCAL, OP_GETVAR, lhs); + break; + case EXP_INDEX: + cexp(J, F, lhs->a); + cexp(J, F, lhs->b); + emitline(J, F, lhs); + emit(J, F, OP_DUP2); + emit(J, F, OP_GETPROP); + break; + case EXP_MEMBER: + cexp(J, F, lhs->a); + emitline(J, F, lhs); + emit(J, F, OP_DUP); + emitstring(J, F, OP_GETPROP_S, lhs->b->string); + break; + default: + jsC_error(J, lhs, "invalid l-value in assignment"); + } +} + +static void cassignop2(JF, js_Ast *lhs, int postfix) +{ + switch (lhs->type) { + case EXP_IDENTIFIER: + emitline(J, F, lhs); + if (postfix) emit(J, F, OP_ROT2); + emitlocal(J, F, OP_SETLOCAL, OP_SETVAR, lhs); + break; + case EXP_INDEX: + emitline(J, F, lhs); + if (postfix) emit(J, F, OP_ROT4); + emit(J, F, OP_SETPROP); + break; + case EXP_MEMBER: + emitline(J, F, lhs); + if (postfix) emit(J, F, OP_ROT3); + emitstring(J, F, OP_SETPROP_S, lhs->b->string); + break; + default: + jsC_error(J, lhs, "invalid l-value in assignment"); + } +} + +static void cassignop(JF, js_Ast *exp, int opcode) +{ + js_Ast *lhs = exp->a; + js_Ast *rhs = exp->b; + cassignop1(J, F, lhs); + cexp(J, F, rhs); + emitline(J, F, exp); + emit(J, F, opcode); + cassignop2(J, F, lhs, 0); +} + +static void cdelete(JF, js_Ast *exp) +{ + js_Ast *arg = exp->a; + switch (arg->type) { + case EXP_IDENTIFIER: + if (F->strict) + jsC_error(J, exp, "delete on an unqualified name is not allowed in strict mode"); + emitline(J, F, exp); + emitlocal(J, F, OP_DELLOCAL, OP_DELVAR, arg); + break; + case EXP_INDEX: + cexp(J, F, arg->a); + cexp(J, F, arg->b); + emitline(J, F, exp); + emit(J, F, OP_DELPROP); + break; + case EXP_MEMBER: + cexp(J, F, arg->a); + emitline(J, F, exp); + emitstring(J, F, OP_DELPROP_S, arg->b->string); + break; + default: + jsC_error(J, exp, "invalid l-value in delete expression"); + } +} + +static void ceval(JF, js_Ast *fun, js_Ast *args) +{ + int n = cargs(J, F, args); + F->lightweight = 0; + F->arguments = 1; + if (n == 0) + emit(J, F, OP_UNDEF); + else while (n-- > 1) + emit(J, F, OP_POP); + emit(J, F, OP_EVAL); +} + +static void ccall(JF, js_Ast *fun, js_Ast *args) +{ + int n; + switch (fun->type) { + case EXP_INDEX: + cexp(J, F, fun->a); + emit(J, F, OP_DUP); + cexp(J, F, fun->b); + emit(J, F, OP_GETPROP); + emit(J, F, OP_ROT2); + break; + case EXP_MEMBER: + cexp(J, F, fun->a); + emit(J, F, OP_DUP); + emitstring(J, F, OP_GETPROP_S, fun->b->string); + emit(J, F, OP_ROT2); + break; + case EXP_IDENTIFIER: + if (!strcmp(fun->string, "eval")) { + ceval(J, F, fun, args); + return; + } + /* fallthrough */ + default: + cexp(J, F, fun); + emit(J, F, OP_UNDEF); + break; + } + n = cargs(J, F, args); + emit(J, F, OP_CALL); + emitarg(J, F, n); +} + +static void cexp(JF, js_Ast *exp) +{ + int then, end; + int n; + + switch (exp->type) { + case EXP_STRING: + emitline(J, F, exp); + emitstring(J, F, OP_STRING, exp->string); + break; + case EXP_NUMBER: + emitline(J, F, exp); + emitnumber(J, F, exp->number); + break; + case EXP_ELISION: + break; + case EXP_NULL: + emitline(J, F, exp); + emit(J, F, OP_NULL); + break; + case EXP_TRUE: + emitline(J, F, exp); + emit(J, F, OP_TRUE); + break; + case EXP_FALSE: + emitline(J, F, exp); + emit(J, F, OP_FALSE); + break; + case EXP_THIS: + emitline(J, F, exp); + emit(J, F, OP_THIS); + break; + + case EXP_REGEXP: + emitline(J, F, exp); + emitstring(J, F, OP_NEWREGEXP, exp->string); + emitarg(J, F, exp->number); + break; + + case EXP_OBJECT: + emitline(J, F, exp); + emit(J, F, OP_NEWOBJECT); + cobject(J, F, exp->a); + break; + + case EXP_ARRAY: + emitline(J, F, exp); + emit(J, F, OP_NEWARRAY); + carray(J, F, exp->a); + break; + + case EXP_FUN: + emitline(J, F, exp); + emitfunction(J, F, newfun(J, exp->line, exp->a, exp->b, exp->c, 0, F->strict, 1)); + break; + + case EXP_IDENTIFIER: + emitline(J, F, exp); + emitlocal(J, F, OP_GETLOCAL, OP_GETVAR, exp); + break; + + case EXP_INDEX: + cexp(J, F, exp->a); + cexp(J, F, exp->b); + emitline(J, F, exp); + emit(J, F, OP_GETPROP); + break; + + case EXP_MEMBER: + cexp(J, F, exp->a); + emitline(J, F, exp); + emitstring(J, F, OP_GETPROP_S, exp->b->string); + break; + + case EXP_CALL: + ccall(J, F, exp->a, exp->b); + break; + + case EXP_NEW: + cexp(J, F, exp->a); + n = cargs(J, F, exp->b); + emitline(J, F, exp); + emit(J, F, OP_NEW); + emitarg(J, F, n); + break; + + case EXP_DELETE: + cdelete(J, F, exp); + break; + + case EXP_PREINC: + cassignop1(J, F, exp->a); + emitline(J, F, exp); + emit(J, F, OP_INC); + cassignop2(J, F, exp->a, 0); + break; + + case EXP_PREDEC: + cassignop1(J, F, exp->a); + emitline(J, F, exp); + emit(J, F, OP_DEC); + cassignop2(J, F, exp->a, 0); + break; + + case EXP_POSTINC: + cassignop1(J, F, exp->a); + emitline(J, F, exp); + emit(J, F, OP_POSTINC); + cassignop2(J, F, exp->a, 1); + emit(J, F, OP_POP); + break; + + case EXP_POSTDEC: + cassignop1(J, F, exp->a); + emitline(J, F, exp); + emit(J, F, OP_POSTDEC); + cassignop2(J, F, exp->a, 1); + emit(J, F, OP_POP); + break; + + case EXP_VOID: + cexp(J, F, exp->a); + emitline(J, F, exp); + emit(J, F, OP_POP); + emit(J, F, OP_UNDEF); + break; + + case EXP_TYPEOF: ctypeof(J, F, exp); break; + case EXP_POS: cunary(J, F, exp, OP_POS); break; + case EXP_NEG: cunary(J, F, exp, OP_NEG); break; + case EXP_BITNOT: cunary(J, F, exp, OP_BITNOT); break; + case EXP_LOGNOT: cunary(J, F, exp, OP_LOGNOT); break; + + case EXP_BITOR: cbinary(J, F, exp, OP_BITOR); break; + case EXP_BITXOR: cbinary(J, F, exp, OP_BITXOR); break; + case EXP_BITAND: cbinary(J, F, exp, OP_BITAND); break; + case EXP_EQ: cbinary(J, F, exp, OP_EQ); break; + case EXP_NE: cbinary(J, F, exp, OP_NE); break; + case EXP_STRICTEQ: cbinary(J, F, exp, OP_STRICTEQ); break; + case EXP_STRICTNE: cbinary(J, F, exp, OP_STRICTNE); break; + case EXP_LT: cbinary(J, F, exp, OP_LT); break; + case EXP_GT: cbinary(J, F, exp, OP_GT); break; + case EXP_LE: cbinary(J, F, exp, OP_LE); break; + case EXP_GE: cbinary(J, F, exp, OP_GE); break; + case EXP_INSTANCEOF: cbinary(J, F, exp, OP_INSTANCEOF); break; + case EXP_IN: cbinary(J, F, exp, OP_IN); break; + case EXP_SHL: cbinary(J, F, exp, OP_SHL); break; + case EXP_SHR: cbinary(J, F, exp, OP_SHR); break; + case EXP_USHR: cbinary(J, F, exp, OP_USHR); break; + case EXP_ADD: cbinary(J, F, exp, OP_ADD); break; + case EXP_SUB: cbinary(J, F, exp, OP_SUB); break; + case EXP_MUL: cbinary(J, F, exp, OP_MUL); break; + case EXP_DIV: cbinary(J, F, exp, OP_DIV); break; + case EXP_MOD: cbinary(J, F, exp, OP_MOD); break; + + case EXP_ASS: cassign(J, F, exp); break; + case EXP_ASS_MUL: cassignop(J, F, exp, OP_MUL); break; + case EXP_ASS_DIV: cassignop(J, F, exp, OP_DIV); break; + case EXP_ASS_MOD: cassignop(J, F, exp, OP_MOD); break; + case EXP_ASS_ADD: cassignop(J, F, exp, OP_ADD); break; + case EXP_ASS_SUB: cassignop(J, F, exp, OP_SUB); break; + case EXP_ASS_SHL: cassignop(J, F, exp, OP_SHL); break; + case EXP_ASS_SHR: cassignop(J, F, exp, OP_SHR); break; + case EXP_ASS_USHR: cassignop(J, F, exp, OP_USHR); break; + case EXP_ASS_BITAND: cassignop(J, F, exp, OP_BITAND); break; + case EXP_ASS_BITXOR: cassignop(J, F, exp, OP_BITXOR); break; + case EXP_ASS_BITOR: cassignop(J, F, exp, OP_BITOR); break; + + case EXP_COMMA: + cexp(J, F, exp->a); + emitline(J, F, exp); + emit(J, F, OP_POP); + cexp(J, F, exp->b); + break; + + case EXP_LOGOR: + cexp(J, F, exp->a); + emitline(J, F, exp); + emit(J, F, OP_DUP); + end = emitjump(J, F, OP_JTRUE); + emit(J, F, OP_POP); + cexp(J, F, exp->b); + label(J, F, end); + break; + + case EXP_LOGAND: + cexp(J, F, exp->a); + emitline(J, F, exp); + emit(J, F, OP_DUP); + end = emitjump(J, F, OP_JFALSE); + emit(J, F, OP_POP); + cexp(J, F, exp->b); + label(J, F, end); + break; + + case EXP_COND: + cexp(J, F, exp->a); + emitline(J, F, exp); + then = emitjump(J, F, OP_JTRUE); + cexp(J, F, exp->c); + end = emitjump(J, F, OP_JUMP); + label(J, F, then); + cexp(J, F, exp->b); + label(J, F, end); + break; + + default: + jsC_error(J, exp, "unknown expression type"); + } +} + +/* Patch break and continue statements */ + +static void addjump(JF, enum js_AstType type, js_Ast *target, int inst) +{ + js_JumpList *jump = js_malloc(J, sizeof *jump); + jump->type = type; + jump->inst = inst; + jump->next = target->jumps; + target->jumps = jump; +} + +static void labeljumps(JF, js_Ast *stm, int baddr, int caddr) +{ + js_JumpList *jump = stm->jumps; + while (jump) { + js_JumpList *next = jump->next; + if (jump->type == STM_BREAK) + labelto(J, F, jump->inst, baddr); + if (jump->type == STM_CONTINUE) + labelto(J, F, jump->inst, caddr); + js_free(J, jump); + jump = next; + } + stm->jumps = NULL; +} + +static int isloop(enum js_AstType T) +{ + return T == STM_DO || T == STM_WHILE || + T == STM_FOR || T == STM_FOR_VAR || + T == STM_FOR_IN || T == STM_FOR_IN_VAR; +} + +static int isfun(enum js_AstType T) +{ + return T == AST_FUNDEC || T == EXP_FUN || T == EXP_PROP_GET || T == EXP_PROP_SET; +} + +static int matchlabel(js_Ast *node, const char *label) +{ + while (node && node->type == STM_LABEL) { + if (!strcmp(node->a->string, label)) + return 1; + node = node->parent; + } + return 0; +} + +static js_Ast *breaktarget(JF, js_Ast *node, const char *label) +{ + while (node) { + if (isfun(node->type)) + break; + if (!label) { + if (isloop(node->type) || node->type == STM_SWITCH) + return node; + } else { + if (matchlabel(node->parent, label)) + return node; + } + node = node->parent; + } + return NULL; +} + +static js_Ast *continuetarget(JF, js_Ast *node, const char *label) +{ + while (node) { + if (isfun(node->type)) + break; + if (isloop(node->type)) { + if (!label) + return node; + else if (matchlabel(node->parent, label)) + return node; + } + node = node->parent; + } + return NULL; +} + +static js_Ast *returntarget(JF, js_Ast *node) +{ + while (node) { + if (isfun(node->type)) + return node; + node = node->parent; + } + return NULL; +} + +/* Emit code to rebalance stack and scopes during an abrupt exit */ + +static void cexit(JF, enum js_AstType T, js_Ast *node, js_Ast *target) +{ + js_Ast *prev; + do { + prev = node, node = node->parent; + switch (node->type) { + default: + /* impossible */ + break; + case STM_WITH: + emitline(J, F, node); + emit(J, F, OP_ENDWITH); + break; + case STM_FOR_IN: + case STM_FOR_IN_VAR: + emitline(J, F, node); + /* pop the iterator if leaving the loop */ + if (F->script) { + if (T == STM_RETURN || T == STM_BREAK || (T == STM_CONTINUE && target != node)) { + /* pop the iterator, save the return or exp value */ + emit(J, F, OP_ROT2); + emit(J, F, OP_POP); + } + if (T == STM_CONTINUE) + emit(J, F, OP_ROT2); /* put the iterator back on top */ + } else { + if (T == STM_RETURN) { + /* pop the iterator, save the return value */ + emit(J, F, OP_ROT2); + emit(J, F, OP_POP); + } + if (T == STM_BREAK || (T == STM_CONTINUE && target != node)) + emit(J, F, OP_POP); /* pop the iterator */ + } + break; + case STM_TRY: + emitline(J, F, node); + /* came from try block */ + if (prev == node->a) { + emit(J, F, OP_ENDTRY); + if (node->d) cstm(J, F, node->d); /* finally */ + } + /* came from catch block */ + if (prev == node->c) { + /* ... with finally */ + if (node->d) { + emit(J, F, OP_ENDCATCH); + emit(J, F, OP_ENDTRY); + cstm(J, F, node->d); /* finally */ + } else { + emit(J, F, OP_ENDCATCH); + } + } + break; + } + } while (node != target); +} + +/* Try/catch/finally */ + +static void ctryfinally(JF, js_Ast *trystm, js_Ast *finallystm) +{ + int L1; + L1 = emitjump(J, F, OP_TRY); + { + /* if we get here, we have caught an exception in the try block */ + cstm(J, F, finallystm); /* inline finally block */ + emit(J, F, OP_THROW); /* rethrow exception */ + } + label(J, F, L1); + cstm(J, F, trystm); + emit(J, F, OP_ENDTRY); + cstm(J, F, finallystm); +} + +static void ctrycatch(JF, js_Ast *trystm, js_Ast *catchvar, js_Ast *catchstm) +{ + int L1, L2; + L1 = emitjump(J, F, OP_TRY); + { + /* if we get here, we have caught an exception in the try block */ + checkfutureword(J, F, catchvar); + if (F->strict) { + if (!strcmp(catchvar->string, "arguments")) + jsC_error(J, catchvar, "redefining 'arguments' is not allowed in strict mode"); + if (!strcmp(catchvar->string, "eval")) + jsC_error(J, catchvar, "redefining 'eval' is not allowed in strict mode"); + } + emitline(J, F, catchvar); + emitstring(J, F, OP_CATCH, catchvar->string); + cstm(J, F, catchstm); + emit(J, F, OP_ENDCATCH); + L2 = emitjump(J, F, OP_JUMP); /* skip past the try block */ + } + label(J, F, L1); + cstm(J, F, trystm); + emit(J, F, OP_ENDTRY); + label(J, F, L2); +} + +static void ctrycatchfinally(JF, js_Ast *trystm, js_Ast *catchvar, js_Ast *catchstm, js_Ast *finallystm) +{ + int L1, L2, L3; + L1 = emitjump(J, F, OP_TRY); + { + /* if we get here, we have caught an exception in the try block */ + L2 = emitjump(J, F, OP_TRY); + { + /* if we get here, we have caught an exception in the catch block */ + cstm(J, F, finallystm); /* inline finally block */ + emit(J, F, OP_THROW); /* rethrow exception */ + } + label(J, F, L2); + if (F->strict) { + checkfutureword(J, F, catchvar); + if (!strcmp(catchvar->string, "arguments")) + jsC_error(J, catchvar, "redefining 'arguments' is not allowed in strict mode"); + if (!strcmp(catchvar->string, "eval")) + jsC_error(J, catchvar, "redefining 'eval' is not allowed in strict mode"); + } + emitline(J, F, catchvar); + emitstring(J, F, OP_CATCH, catchvar->string); + cstm(J, F, catchstm); + emit(J, F, OP_ENDCATCH); + emit(J, F, OP_ENDTRY); + L3 = emitjump(J, F, OP_JUMP); /* skip past the try block to the finally block */ + } + label(J, F, L1); + cstm(J, F, trystm); + emit(J, F, OP_ENDTRY); + label(J, F, L3); + cstm(J, F, finallystm); +} + +/* Switch */ + +static void cswitch(JF, js_Ast *ref, js_Ast *head) +{ + js_Ast *node, *clause, *def = NULL; + int end; + + cexp(J, F, ref); + + /* emit an if-else chain of tests for the case clause expressions */ + for (node = head; node; node = node->b) { + clause = node->a; + if (clause->type == STM_DEFAULT) { + if (def) + jsC_error(J, clause, "more than one default label in switch"); + def = clause; + } else { + cexp(J, F, clause->a); + emitline(J, F, clause); + clause->casejump = emitjump(J, F, OP_JCASE); + } + } + emit(J, F, OP_POP); + if (def) { + emitline(J, F, def); + def->casejump = emitjump(J, F, OP_JUMP); + end = 0; + } else { + end = emitjump(J, F, OP_JUMP); + } + + /* emit the case clause bodies */ + for (node = head; node; node = node->b) { + clause = node->a; + label(J, F, clause->casejump); + if (clause->type == STM_DEFAULT) + cstmlist(J, F, clause->a); + else + cstmlist(J, F, clause->b); + } + + if (end) + label(J, F, end); +} + +/* Statements */ + +static void cvarinit(JF, js_Ast *list) +{ + while (list) { + js_Ast *var = list->a; + if (var->b) { + cexp(J, F, var->b); + emitline(J, F, var); + emitlocal(J, F, OP_SETLOCAL, OP_SETVAR, var->a); + emit(J, F, OP_POP); + } + list = list->b; + } +} + +static void cstm(JF, js_Ast *stm) +{ + js_Ast *target; + int loop, cont, then, end; + + emitline(J, F, stm); + + switch (stm->type) { + case AST_FUNDEC: + break; + + case STM_BLOCK: + cstmlist(J, F, stm->a); + break; + + case STM_EMPTY: + if (F->script) { + emitline(J, F, stm); + emit(J, F, OP_POP); + emit(J, F, OP_UNDEF); + } + break; + + case STM_VAR: + cvarinit(J, F, stm->a); + break; + + case STM_IF: + if (stm->c) { + cexp(J, F, stm->a); + emitline(J, F, stm); + then = emitjump(J, F, OP_JTRUE); + cstm(J, F, stm->c); + emitline(J, F, stm); + end = emitjump(J, F, OP_JUMP); + label(J, F, then); + cstm(J, F, stm->b); + label(J, F, end); + } else { + cexp(J, F, stm->a); + emitline(J, F, stm); + end = emitjump(J, F, OP_JFALSE); + cstm(J, F, stm->b); + label(J, F, end); + } + break; + + case STM_DO: + loop = here(J, F); + cstm(J, F, stm->a); + cont = here(J, F); + cexp(J, F, stm->b); + emitline(J, F, stm); + emitjumpto(J, F, OP_JTRUE, loop); + labeljumps(J, F, stm, here(J,F), cont); + break; + + case STM_WHILE: + loop = here(J, F); + cexp(J, F, stm->a); + emitline(J, F, stm); + end = emitjump(J, F, OP_JFALSE); + cstm(J, F, stm->b); + emitline(J, F, stm); + emitjumpto(J, F, OP_JUMP, loop); + label(J, F, end); + labeljumps(J, F, stm, here(J,F), loop); + break; + + case STM_FOR: + case STM_FOR_VAR: + if (stm->type == STM_FOR_VAR) { + cvarinit(J, F, stm->a); + } else { + if (stm->a) { + cexp(J, F, stm->a); + emit(J, F, OP_POP); + } + } + loop = here(J, F); + if (stm->b) { + cexp(J, F, stm->b); + emitline(J, F, stm); + end = emitjump(J, F, OP_JFALSE); + } else { + end = 0; + } + cstm(J, F, stm->d); + cont = here(J, F); + if (stm->c) { + cexp(J, F, stm->c); + emit(J, F, OP_POP); + } + emitline(J, F, stm); + emitjumpto(J, F, OP_JUMP, loop); + if (end) + label(J, F, end); + labeljumps(J, F, stm, here(J,F), cont); + break; + + case STM_FOR_IN: + case STM_FOR_IN_VAR: + cexp(J, F, stm->b); + emitline(J, F, stm); + emit(J, F, OP_ITERATOR); + loop = here(J, F); + { + emitline(J, F, stm); + emit(J, F, OP_NEXTITER); + end = emitjump(J, F, OP_JFALSE); + cassignforin(J, F, stm); + if (F->script) { + emit(J, F, OP_ROT2); + cstm(J, F, stm->c); + emit(J, F, OP_ROT2); + } else { + cstm(J, F, stm->c); + } + emitline(J, F, stm); + emitjumpto(J, F, OP_JUMP, loop); + } + label(J, F, end); + labeljumps(J, F, stm, here(J,F), loop); + break; + + case STM_SWITCH: + cswitch(J, F, stm->a, stm->b); + labeljumps(J, F, stm, here(J,F), 0); + break; + + case STM_LABEL: + cstm(J, F, stm->b); + /* skip consecutive labels */ + while (stm->type == STM_LABEL) + stm = stm->b; + /* loops and switches have already been labelled */ + if (!isloop(stm->type) && stm->type != STM_SWITCH) + labeljumps(J, F, stm, here(J,F), 0); + break; + + case STM_BREAK: + if (stm->a) { + checkfutureword(J, F, stm->a); + target = breaktarget(J, F, stm->parent, stm->a->string); + if (!target) + jsC_error(J, stm, "break label '%s' not found", stm->a->string); + } else { + target = breaktarget(J, F, stm->parent, NULL); + if (!target) + jsC_error(J, stm, "unlabelled break must be inside loop or switch"); + } + cexit(J, F, STM_BREAK, stm, target); + emitline(J, F, stm); + addjump(J, F, STM_BREAK, target, emitjump(J, F, OP_JUMP)); + break; + + case STM_CONTINUE: + if (stm->a) { + checkfutureword(J, F, stm->a); + target = continuetarget(J, F, stm->parent, stm->a->string); + if (!target) + jsC_error(J, stm, "continue label '%s' not found", stm->a->string); + } else { + target = continuetarget(J, F, stm->parent, NULL); + if (!target) + jsC_error(J, stm, "continue must be inside loop"); + } + cexit(J, F, STM_CONTINUE, stm, target); + emitline(J, F, stm); + addjump(J, F, STM_CONTINUE, target, emitjump(J, F, OP_JUMP)); + break; + + case STM_RETURN: + if (stm->a) + cexp(J, F, stm->a); + else + emit(J, F, OP_UNDEF); + target = returntarget(J, F, stm->parent); + if (!target) + jsC_error(J, stm, "return not in function"); + cexit(J, F, STM_RETURN, stm, target); + emitline(J, F, stm); + emit(J, F, OP_RETURN); + break; + + case STM_THROW: + cexp(J, F, stm->a); + emitline(J, F, stm); + emit(J, F, OP_THROW); + break; + + case STM_WITH: + F->lightweight = 0; + if (F->strict) + jsC_error(J, stm->a, "'with' statements are not allowed in strict mode"); + cexp(J, F, stm->a); + emitline(J, F, stm); + emit(J, F, OP_WITH); + cstm(J, F, stm->b); + emitline(J, F, stm); + emit(J, F, OP_ENDWITH); + break; + + case STM_TRY: + emitline(J, F, stm); + if (stm->b && stm->c) { + F->lightweight = 0; + if (stm->d) + ctrycatchfinally(J, F, stm->a, stm->b, stm->c, stm->d); + else + ctrycatch(J, F, stm->a, stm->b, stm->c); + } else { + ctryfinally(J, F, stm->a, stm->d); + } + break; + + case STM_DEBUGGER: + emitline(J, F, stm); + emit(J, F, OP_DEBUGGER); + break; + + default: + if (F->script) { + emitline(J, F, stm); + emit(J, F, OP_POP); + cexp(J, F, stm); + } else { + cexp(J, F, stm); + emitline(J, F, stm); + emit(J, F, OP_POP); + } + break; + } +} + +static void cstmlist(JF, js_Ast *list) +{ + while (list) { + cstm(J, F, list->a); + list = list->b; + } +} + +/* Declarations and programs */ + +static int listlength(js_Ast *list) +{ + int n = 0; + while (list) ++n, list = list->b; + return n; +} + +static void cparams(JF, js_Ast *list, js_Ast *fname) +{ + F->numparams = listlength(list); + while (list) { + checkfutureword(J, F, list->a); + addlocal(J, F, list->a, 0); + list = list->b; + } +} + +static void cvardecs(JF, js_Ast *node) +{ + if (node->type == AST_LIST) { + while (node) { + cvardecs(J, F, node->a); + node = node->b; + } + return; + } + + if (isfun(node->type)) + return; /* stop at inner functions */ + + if (node->type == EXP_VAR) { + checkfutureword(J, F, node->a); + addlocal(J, F, node->a, 1); + } + + if (node->a) cvardecs(J, F, node->a); + if (node->b) cvardecs(J, F, node->b); + if (node->c) cvardecs(J, F, node->c); + if (node->d) cvardecs(J, F, node->d); +} + +static void cfundecs(JF, js_Ast *list) +{ + while (list) { + js_Ast *stm = list->a; + if (stm->type == AST_FUNDEC) { + emitline(J, F, stm); + emitfunction(J, F, newfun(J, stm->line, stm->a, stm->b, stm->c, 0, F->strict, 0)); + emitline(J, F, stm); + emit(J, F, OP_SETLOCAL); + emitarg(J, F, addlocal(J, F, stm->a, 1)); + emit(J, F, OP_POP); + } + list = list->b; + } +} + +static void cfunbody(JF, js_Ast *name, js_Ast *params, js_Ast *body, int is_fun_exp) +{ + F->lightweight = 1; + F->arguments = 0; + + if (F->script) + F->lightweight = 0; + + /* Check if first statement is 'use strict': */ + if (body && body->type == AST_LIST && body->a && body->a->type == EXP_STRING) + if (!strcmp(body->a->string, "use strict")) + F->strict = 1; + + F->lastline = F->line; + + cparams(J, F, params, name); + + if (body) { + cvardecs(J, F, body); + cfundecs(J, F, body); + } + + if (name) { + checkfutureword(J, F, name); + if (is_fun_exp) { + if (findlocal(J, F, name->string) < 0) { + /* TODO: make this binding immutable! */ + emit(J, F, OP_CURRENT); + emit(J, F, OP_SETLOCAL); + emitarg(J, F, addlocal(J, F, name, 1)); + emit(J, F, OP_POP); + } + } + } + + if (F->script) { + emit(J, F, OP_UNDEF); + cstmlist(J, F, body); + emit(J, F, OP_RETURN); + } else { + cstmlist(J, F, body); + emit(J, F, OP_UNDEF); + emit(J, F, OP_RETURN); + } +} + +js_Function *jsC_compilefunction(js_State *J, js_Ast *prog) +{ + return newfun(J, prog->line, prog->a, prog->b, prog->c, 0, J->default_strict, 1); +} + +js_Function *jsC_compilescript(js_State *J, js_Ast *prog, int default_strict) +{ + return newfun(J, prog ? prog->line : 0, NULL, NULL, prog, 1, default_strict, 0); +}
