eBookReaderSwitch/mupdf/thirdparty/mujs/jscompile.c

1451 lines
32 KiB
C
Raw Normal View History

#include "jsi.h"
#include "jslex.h"
#include "jsparse.h"
#include "jscompile.h"
#include "jsvalue.h" /* for jsV_numbertostring */
#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);
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)
{
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);
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 addnumber(JF, double value)
{
int i;
for (i = 0; i < F->numlen; ++i)
if (F->numtab[i] == value)
return i;
if (F->numlen >= F->numcap) {
F->numcap = F->numcap ? F->numcap * 2 : 16;
F->numtab = js_realloc(J, F->numtab, F->numcap * sizeof *F->numtab);
}
F->numtab[F->numlen] = value;
return F->numlen++;
}
static int addstring(JF, const char *value)
{
int i;
for (i = 0; i < F->strlen; ++i)
if (!strcmp(F->strtab[i], value))
return i;
if (F->strlen >= F->strcap) {
F->strcap = F->strcap ? F->strcap * 2 : 16;
F->strtab = js_realloc(J, F->strtab, F->strcap * sizeof *F->strtab);
}
F->strtab[F->strlen] = value;
return F->strlen++;
}
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 {
double nv = num + 32768;
js_Instruction iv = nv;
if (nv == iv) {
emit(J, F, OP_INTEGER);
emitarg(J, F, iv);
} else {
emit(J, F, OP_NUMBER);
emitarg(J, F, addnumber(J, F, num));
}
}
}
static void emitstring(JF, int opcode, const char *str)
{
emit(J, F, opcode);
emitarg(J, F, addstring(J, F, str));
}
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)
{
int i = 0;
while (list) {
if (list->a->type != EXP_UNDEF) {
emitline(J, F, list->a);
emitnumber(J, F, i++);
cexp(J, F, list->a);
emitline(J, F, list->a);
emit(J, F, OP_INITPROP);
} else {
++i;
}
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));
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));
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;
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_UNDEF:
emitline(J, F, exp);
emit(J, F, OP_UNDEF);
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);
emit(J, F, OP_NEWREGEXP);
emitarg(J, F, addstring(J, F, 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));
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: (%s)", jsP_aststring(exp->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_JumpList *jump, int baddr, int caddr)
{
while (jump) {
if (jump->type == STM_BREAK)
labelto(J, F, jump->inst, baddr);
if (jump->type == STM_CONTINUE)
labelto(J, F, jump->inst, caddr);
jump = jump->next;
}
}
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->jumps, 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->jumps, 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->jumps, 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->jumps, here(J,F), loop);
break;
case STM_SWITCH:
cswitch(J, F, stm->a, stm->b);
labeljumps(J, F, stm->jumps, 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->jumps, 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));
emitline(J, F, stm);
emit(J, F, OP_SETLOCAL);
emitarg(J, F, addlocal(J, F, stm->a, 0));
emit(J, F, OP_POP);
}
list = list->b;
}
}
static void cfunbody(JF, js_Ast *name, js_Ast *params, js_Ast *body)
{
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 (findlocal(J, F, name->string) < 0) {
emit(J, F, OP_CURRENT);
emit(J, F, OP_SETLOCAL);
emitarg(J, F, addlocal(J, F, name, 0));
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);
}
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);
}