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);
+}