diff mupdf-source/thirdparty/mujs/jsrun.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/jsrun.c	Mon Sep 15 11:43:07 2025 +0200
@@ -0,0 +1,2063 @@
+#include "jsi.h"
+#include "utf.h"
+
+#include <assert.h>
+
+static void jsR_run(js_State *J, js_Function *F);
+
+/* Push values on stack */
+
+#define STACK (J->stack)
+#define TOP (J->top)
+#define BOT (J->bot)
+
+static void js_trystackoverflow(js_State *J)
+{
+	STACK[TOP].t.type = JS_TLITSTR;
+	STACK[TOP].u.litstr = "exception stack overflow";
+	++TOP;
+	js_throw(J);
+}
+
+static void js_stackoverflow(js_State *J)
+{
+	STACK[TOP].t.type = JS_TLITSTR;
+	STACK[TOP].u.litstr = "stack overflow";
+	++TOP;
+	js_throw(J);
+}
+
+static void js_outofmemory(js_State *J)
+{
+	STACK[TOP].t.type = JS_TLITSTR;
+	STACK[TOP].u.litstr = "out of memory";
+	++TOP;
+	js_throw(J);
+}
+
+void *js_malloc(js_State *J, int size)
+{
+	void *ptr = J->alloc(J->actx, NULL, size);
+	if (!ptr)
+		js_outofmemory(J);
+	return ptr;
+}
+
+void *js_realloc(js_State *J, void *ptr, int size)
+{
+	ptr = J->alloc(J->actx, ptr, size);
+	if (!ptr)
+		js_outofmemory(J);
+	return ptr;
+}
+
+char *js_strdup(js_State *J, const char *s)
+{
+	int n = strlen(s) + 1;
+	char *p = js_malloc(J, n);
+	memcpy(p, s, n);
+	return p;
+}
+
+void js_free(js_State *J, void *ptr)
+{
+	J->alloc(J->actx, ptr, 0);
+}
+
+js_String *jsV_newmemstring(js_State *J, const char *s, int n)
+{
+	js_String *v = js_malloc(J, soffsetof(js_String, p) + n + 1);
+	memcpy(v->p, s, n);
+	v->p[n] = 0;
+	v->gcmark = 0;
+	v->gcnext = J->gcstr;
+	J->gcstr = v;
+	++J->gccounter;
+	return v;
+}
+
+#define CHECKSTACK(n) if (TOP + n >= JS_STACKSIZE) js_stackoverflow(J)
+
+void js_pushvalue(js_State *J, js_Value v)
+{
+	CHECKSTACK(1);
+	STACK[TOP] = v;
+	++TOP;
+}
+
+void js_pushundefined(js_State *J)
+{
+	CHECKSTACK(1);
+	STACK[TOP].t.type = JS_TUNDEFINED;
+	++TOP;
+}
+
+void js_pushnull(js_State *J)
+{
+	CHECKSTACK(1);
+	STACK[TOP].t.type = JS_TNULL;
+	++TOP;
+}
+
+void js_pushboolean(js_State *J, int v)
+{
+	CHECKSTACK(1);
+	STACK[TOP].t.type = JS_TBOOLEAN;
+	STACK[TOP].u.boolean = !!v;
+	++TOP;
+}
+
+void js_pushnumber(js_State *J, double v)
+{
+	CHECKSTACK(1);
+	STACK[TOP].t.type = JS_TNUMBER;
+	STACK[TOP].u.number = v;
+	++TOP;
+}
+
+void js_pushstring(js_State *J, const char *v)
+{
+	size_t n = strlen(v);
+	if (n > JS_STRLIMIT)
+		js_rangeerror(J, "invalid string length");
+	CHECKSTACK(1);
+	if (n <= soffsetof(js_Value, t.type)) {
+		char *s = STACK[TOP].u.shrstr;
+		while (n--) *s++ = *v++;
+		*s = 0;
+		STACK[TOP].t.type = JS_TSHRSTR;
+	} else {
+		STACK[TOP].t.type = JS_TMEMSTR;
+		STACK[TOP].u.memstr = jsV_newmemstring(J, v, n);
+	}
+	++TOP;
+}
+
+void js_pushlstring(js_State *J, const char *v, int n)
+{
+	if (n > JS_STRLIMIT)
+		js_rangeerror(J, "invalid string length");
+	CHECKSTACK(1);
+	if (n <= soffsetof(js_Value, t.type)) {
+		char *s = STACK[TOP].u.shrstr;
+		while (n--) *s++ = *v++;
+		*s = 0;
+		STACK[TOP].t.type = JS_TSHRSTR;
+	} else {
+		STACK[TOP].t.type = JS_TMEMSTR;
+		STACK[TOP].u.memstr = jsV_newmemstring(J, v, n);
+	}
+	++TOP;
+}
+
+void js_pushliteral(js_State *J, const char *v)
+{
+	CHECKSTACK(1);
+	STACK[TOP].t.type = JS_TLITSTR;
+	STACK[TOP].u.litstr = v;
+	++TOP;
+}
+
+void js_pushobject(js_State *J, js_Object *v)
+{
+	CHECKSTACK(1);
+	STACK[TOP].t.type = JS_TOBJECT;
+	STACK[TOP].u.object = v;
+	++TOP;
+}
+
+void js_pushglobal(js_State *J)
+{
+	js_pushobject(J, J->G);
+}
+
+void js_currentfunction(js_State *J)
+{
+	CHECKSTACK(1);
+	if (BOT > 0)
+		STACK[TOP] = STACK[BOT-1];
+	else
+		STACK[TOP].t.type = JS_TUNDEFINED;
+	++TOP;
+}
+
+void *js_currentfunctiondata(js_State *J)
+{
+	if (BOT > 0)
+		return STACK[BOT-1].u.object->u.c.data;
+	return NULL;
+}
+
+/* Read values from stack */
+
+static js_Value *stackidx(js_State *J, int idx)
+{
+	static js_Value undefined = { { {0}, JS_TUNDEFINED } };
+	idx = idx < 0 ? TOP + idx : BOT + idx;
+	if (idx < 0 || idx >= TOP)
+		return &undefined;
+	return STACK + idx;
+}
+
+js_Value *js_tovalue(js_State *J, int idx)
+{
+	return stackidx(J, idx);
+}
+
+int js_isdefined(js_State *J, int idx) { return stackidx(J, idx)->t.type != JS_TUNDEFINED; }
+int js_isundefined(js_State *J, int idx) { return stackidx(J, idx)->t.type == JS_TUNDEFINED; }
+int js_isnull(js_State *J, int idx) { return stackidx(J, idx)->t.type == JS_TNULL; }
+int js_isboolean(js_State *J, int idx) { return stackidx(J, idx)->t.type == JS_TBOOLEAN; }
+int js_isnumber(js_State *J, int idx) { return stackidx(J, idx)->t.type == JS_TNUMBER; }
+int js_isstring(js_State *J, int idx) { enum js_Type t = stackidx(J, idx)->t.type; return t == JS_TSHRSTR || t == JS_TLITSTR || t == JS_TMEMSTR; }
+int js_isprimitive(js_State *J, int idx) { return stackidx(J, idx)->t.type != JS_TOBJECT; }
+int js_isobject(js_State *J, int idx) { return stackidx(J, idx)->t.type == JS_TOBJECT; }
+int js_iscoercible(js_State *J, int idx) { js_Value *v = stackidx(J, idx); return v->t.type != JS_TUNDEFINED && v->t.type != JS_TNULL; }
+
+int js_iscallable(js_State *J, int idx)
+{
+	js_Value *v = stackidx(J, idx);
+	if (v->t.type == JS_TOBJECT)
+		return v->u.object->type == JS_CFUNCTION ||
+			v->u.object->type == JS_CSCRIPT ||
+			v->u.object->type == JS_CCFUNCTION;
+	return 0;
+}
+
+int js_isarray(js_State *J, int idx)
+{
+	js_Value *v = stackidx(J, idx);
+	return v->t.type == JS_TOBJECT && v->u.object->type == JS_CARRAY;
+}
+
+int js_isregexp(js_State *J, int idx)
+{
+	js_Value *v = stackidx(J, idx);
+	return v->t.type == JS_TOBJECT && v->u.object->type == JS_CREGEXP;
+}
+
+int js_isuserdata(js_State *J, int idx, const char *tag)
+{
+	js_Value *v = stackidx(J, idx);
+	if (v->t.type == JS_TOBJECT && v->u.object->type == JS_CUSERDATA)
+		return !strcmp(tag, v->u.object->u.user.tag);
+	return 0;
+}
+
+int js_iserror(js_State *J, int idx)
+{
+	js_Value *v = stackidx(J, idx);
+	return v->t.type == JS_TOBJECT && v->u.object->type == JS_CERROR;
+}
+
+const char *js_typeof(js_State *J, int idx)
+{
+	js_Value *v = stackidx(J, idx);
+	switch (v->t.type) {
+	default:
+	case JS_TSHRSTR: return "string";
+	case JS_TUNDEFINED: return "undefined";
+	case JS_TNULL: return "object";
+	case JS_TBOOLEAN: return "boolean";
+	case JS_TNUMBER: return "number";
+	case JS_TLITSTR: return "string";
+	case JS_TMEMSTR: return "string";
+	case JS_TOBJECT:
+		if (v->u.object->type == JS_CFUNCTION || v->u.object->type == JS_CCFUNCTION)
+			return "function";
+		return "object";
+	}
+}
+
+int js_type(js_State *J, int idx)
+{
+	js_Value *v = stackidx(J, idx);
+	switch (v->t.type) {
+	default:
+	case JS_TSHRSTR: return JS_ISSTRING;
+	case JS_TUNDEFINED: return JS_ISUNDEFINED;
+	case JS_TNULL: return JS_ISNULL;
+	case JS_TBOOLEAN: return JS_ISBOOLEAN;
+	case JS_TNUMBER: return JS_ISNUMBER;
+	case JS_TLITSTR: return JS_ISSTRING;
+	case JS_TMEMSTR: return JS_ISSTRING;
+	case JS_TOBJECT:
+		if (v->u.object->type == JS_CFUNCTION || v->u.object->type == JS_CCFUNCTION)
+			return JS_ISFUNCTION;
+		return JS_ISOBJECT;
+	}
+}
+
+int js_toboolean(js_State *J, int idx)
+{
+	return jsV_toboolean(J, stackidx(J, idx));
+}
+
+double js_tonumber(js_State *J, int idx)
+{
+	return jsV_tonumber(J, stackidx(J, idx));
+}
+
+int js_tointeger(js_State *J, int idx)
+{
+	return jsV_numbertointeger(jsV_tonumber(J, stackidx(J, idx)));
+}
+
+int js_toint32(js_State *J, int idx)
+{
+	return jsV_numbertoint32(jsV_tonumber(J, stackidx(J, idx)));
+}
+
+unsigned int js_touint32(js_State *J, int idx)
+{
+	return jsV_numbertouint32(jsV_tonumber(J, stackidx(J, idx)));
+}
+
+short js_toint16(js_State *J, int idx)
+{
+	return jsV_numbertoint16(jsV_tonumber(J, stackidx(J, idx)));
+}
+
+unsigned short js_touint16(js_State *J, int idx)
+{
+	return jsV_numbertouint16(jsV_tonumber(J, stackidx(J, idx)));
+}
+
+const char *js_tostring(js_State *J, int idx)
+{
+	return jsV_tostring(J, stackidx(J, idx));
+}
+
+js_Object *js_toobject(js_State *J, int idx)
+{
+	return jsV_toobject(J, stackidx(J, idx));
+}
+
+void js_toprimitive(js_State *J, int idx, int hint)
+{
+	jsV_toprimitive(J, stackidx(J, idx), hint);
+}
+
+js_Regexp *js_toregexp(js_State *J, int idx)
+{
+	js_Value *v = stackidx(J, idx);
+	if (v->t.type == JS_TOBJECT && v->u.object->type == JS_CREGEXP)
+		return &v->u.object->u.r;
+	js_typeerror(J, "not a regexp");
+}
+
+void *js_touserdata(js_State *J, int idx, const char *tag)
+{
+	js_Value *v = stackidx(J, idx);
+	if (v->t.type == JS_TOBJECT && v->u.object->type == JS_CUSERDATA)
+		if (!strcmp(tag, v->u.object->u.user.tag))
+			return v->u.object->u.user.data;
+	js_typeerror(J, "not a %s", tag);
+}
+
+static js_Object *jsR_tofunction(js_State *J, int idx)
+{
+	js_Value *v = stackidx(J, idx);
+	if (v->t.type == JS_TUNDEFINED || v->t.type == JS_TNULL)
+		return NULL;
+	if (v->t.type == JS_TOBJECT)
+		if (v->u.object->type == JS_CFUNCTION || v->u.object->type == JS_CCFUNCTION)
+			return v->u.object;
+	js_typeerror(J, "not a function");
+}
+
+/* Stack manipulation */
+
+int js_gettop(js_State *J)
+{
+	return TOP - BOT;
+}
+
+void js_pop(js_State *J, int n)
+{
+	TOP -= n;
+	if (TOP < BOT) {
+		TOP = BOT;
+		js_error(J, "stack underflow!");
+	}
+}
+
+void js_remove(js_State *J, int idx)
+{
+	idx = idx < 0 ? TOP + idx : BOT + idx;
+	if (idx < BOT || idx >= TOP)
+		js_error(J, "stack error!");
+	for (;idx < TOP - 1; ++idx)
+		STACK[idx] = STACK[idx+1];
+	--TOP;
+}
+
+void js_insert(js_State *J, int idx)
+{
+	js_error(J, "not implemented yet");
+}
+
+void js_replace(js_State* J, int idx)
+{
+	idx = idx < 0 ? TOP + idx : BOT + idx;
+	if (idx < BOT || idx >= TOP)
+		js_error(J, "stack error!");
+	STACK[idx] = STACK[--TOP];
+}
+
+void js_copy(js_State *J, int idx)
+{
+	CHECKSTACK(1);
+	STACK[TOP] = *stackidx(J, idx);
+	++TOP;
+}
+
+void js_dup(js_State *J)
+{
+	CHECKSTACK(1);
+	STACK[TOP] = STACK[TOP-1];
+	++TOP;
+}
+
+void js_dup2(js_State *J)
+{
+	CHECKSTACK(2);
+	STACK[TOP] = STACK[TOP-2];
+	STACK[TOP+1] = STACK[TOP-1];
+	TOP += 2;
+}
+
+void js_rot2(js_State *J)
+{
+	/* A B -> B A */
+	js_Value tmp = STACK[TOP-1];	/* A B (B) */
+	STACK[TOP-1] = STACK[TOP-2];	/* A A */
+	STACK[TOP-2] = tmp;		/* B A */
+}
+
+void js_rot3(js_State *J)
+{
+	/* A B C -> C A B */
+	js_Value tmp = STACK[TOP-1];	/* A B C (C) */
+	STACK[TOP-1] = STACK[TOP-2];	/* A B B */
+	STACK[TOP-2] = STACK[TOP-3];	/* A A B */
+	STACK[TOP-3] = tmp;		/* C A B */
+}
+
+void js_rot4(js_State *J)
+{
+	/* A B C D -> D A B C */
+	js_Value tmp = STACK[TOP-1];	/* A B C D (D) */
+	STACK[TOP-1] = STACK[TOP-2];	/* A B C C */
+	STACK[TOP-2] = STACK[TOP-3];	/* A B B C */
+	STACK[TOP-3] = STACK[TOP-4];	/* A A B C */
+	STACK[TOP-4] = tmp;		/* D A B C */
+}
+
+void js_rot2pop1(js_State *J)
+{
+	/* A B -> B */
+	STACK[TOP-2] = STACK[TOP-1];
+	--TOP;
+}
+
+void js_rot3pop2(js_State *J)
+{
+	/* A B C -> C */
+	STACK[TOP-3] = STACK[TOP-1];
+	TOP -= 2;
+}
+
+void js_rot(js_State *J, int n)
+{
+	int i;
+	js_Value tmp = STACK[TOP-1];
+	for (i = 1; i < n; ++i)
+		STACK[TOP-i] = STACK[TOP-i-1];
+	STACK[TOP-i] = tmp;
+}
+
+/* Property access that takes care of attributes and getters/setters */
+
+int js_isarrayindex(js_State *J, const char *p, int *idx)
+{
+	int n = 0;
+
+	/* check for empty string */
+	if (p[0] == 0)
+		return 0;
+
+	/* check for '0' and integers with leading zero */
+	if (p[0] == '0')
+		return (p[1] == 0) ? *idx = 0, 1 : 0;
+
+	while (*p) {
+		int c = *p++;
+		if (c >= '0' && c <= '9') {
+			if (n >= INT_MAX / 10)
+				return 0;
+			n = n * 10 + (c - '0');
+		} else {
+			return 0;
+		}
+	}
+	return *idx = n, 1;
+}
+
+static void js_pushrune(js_State *J, Rune rune)
+{
+	char buf[UTFmax + 1];
+	if (rune >= 0) {
+		buf[runetochar(buf, &rune)] = 0;
+		js_pushstring(J, buf);
+	} else {
+		js_pushundefined(J);
+	}
+}
+
+void jsR_unflattenarray(js_State *J, js_Object *obj) {
+	if (obj->type == JS_CARRAY && obj->u.a.simple) {
+		js_Property *ref;
+		int i;
+		char name[32];
+		if (js_try(J)) {
+			obj->properties = NULL;
+			js_throw(J);
+		}
+		for (i = 0; i < obj->u.a.flat_length; ++i) {
+			js_itoa(name, i);
+			ref = jsV_setproperty(J, obj, name);
+			ref->value = obj->u.a.array[i];
+		}
+		js_free(J, obj->u.a.array);
+		obj->u.a.simple = 0;
+		obj->u.a.flat_length = 0;
+		obj->u.a.flat_capacity = 0;
+		obj->u.a.array = NULL;
+		js_endtry(J);
+	}
+}
+
+static int jsR_hasproperty(js_State *J, js_Object *obj, const char *name)
+{
+	js_Property *ref;
+	int k;
+
+	if (obj->type == JS_CARRAY) {
+		if (!strcmp(name, "length")) {
+			js_pushnumber(J, obj->u.a.length);
+			return 1;
+		}
+		if (obj->u.a.simple) {
+			if (js_isarrayindex(J, name, &k)) {
+				if (k >= 0 && k < obj->u.a.flat_length) {
+					js_pushvalue(J, obj->u.a.array[k]);
+					return 1;
+				}
+				return 0;
+			}
+		}
+	}
+
+	else if (obj->type == JS_CSTRING) {
+		if (!strcmp(name, "length")) {
+			js_pushnumber(J, obj->u.s.length);
+			return 1;
+		}
+		if (js_isarrayindex(J, name, &k)) {
+			if (k >= 0 && k < obj->u.s.length) {
+				js_pushrune(J, js_runeat(J, obj->u.s.string, k));
+				return 1;
+			}
+		}
+	}
+
+	else if (obj->type == JS_CREGEXP) {
+		if (!strcmp(name, "source")) {
+			js_pushstring(J, obj->u.r.source);
+			return 1;
+		}
+		if (!strcmp(name, "global")) {
+			js_pushboolean(J, obj->u.r.flags & JS_REGEXP_G);
+			return 1;
+		}
+		if (!strcmp(name, "ignoreCase")) {
+			js_pushboolean(J, obj->u.r.flags & JS_REGEXP_I);
+			return 1;
+		}
+		if (!strcmp(name, "multiline")) {
+			js_pushboolean(J, obj->u.r.flags & JS_REGEXP_M);
+			return 1;
+		}
+		if (!strcmp(name, "lastIndex")) {
+			js_pushnumber(J, obj->u.r.last);
+			return 1;
+		}
+	}
+
+	else if (obj->type == JS_CUSERDATA) {
+		if (obj->u.user.has && obj->u.user.has(J, obj->u.user.data, name))
+			return 1;
+	}
+
+	ref = jsV_getproperty(J, obj, name);
+	if (ref) {
+		if (ref->getter) {
+			js_pushobject(J, ref->getter);
+			js_pushobject(J, obj);
+			js_call(J, 0);
+		} else {
+			js_pushvalue(J, ref->value);
+		}
+		return 1;
+	}
+
+	return 0;
+}
+
+static void jsR_getproperty(js_State *J, js_Object *obj, const char *name)
+{
+	if (!jsR_hasproperty(J, obj, name))
+		js_pushundefined(J);
+}
+
+static int jsR_hasindex(js_State *J, js_Object *obj, int k)
+{
+	char buf[32];
+	if (obj->type == JS_CARRAY && obj->u.a.simple) {
+		if (k >= 0 && k < obj->u.a.flat_length) {
+			js_pushvalue(J, obj->u.a.array[k]);
+			return 1;
+		}
+		return 0;
+	}
+	return jsR_hasproperty(J, obj, js_itoa(buf, k));
+}
+
+static void jsR_getindex(js_State *J, js_Object *obj, int k)
+{
+	if (!jsR_hasindex(J, obj, k))
+		js_pushundefined(J);
+}
+
+static void jsR_setarrayindex(js_State *J, js_Object *obj, int k, js_Value *value)
+{
+	int newlen = k + 1;
+	assert(obj->u.a.simple);
+	assert(k >= 0);
+	if (newlen > JS_ARRAYLIMIT)
+		js_rangeerror(J, "array too large");
+	if (newlen > obj->u.a.flat_length) {
+		assert(newlen == obj->u.a.flat_length + 1);
+		if (newlen > obj->u.a.flat_capacity) {
+			int newcap = obj->u.a.flat_capacity;
+			if (newcap == 0)
+				newcap = 8;
+			while (newcap < newlen)
+				newcap <<= 1;
+			obj->u.a.array = js_realloc(J, obj->u.a.array, newcap * sizeof(js_Value));
+			obj->u.a.flat_capacity = newcap;
+		}
+		obj->u.a.flat_length = newlen;
+	}
+	if (newlen > obj->u.a.length)
+		obj->u.a.length = newlen;
+	obj->u.a.array[k] = *value;
+}
+
+static void jsR_setproperty(js_State *J, js_Object *obj, const char *name, int transient)
+{
+	js_Value *value = stackidx(J, -1);
+	js_Property *ref;
+	int k;
+	int own;
+
+	if (obj->type == JS_CARRAY) {
+		if (!strcmp(name, "length")) {
+			double rawlen = jsV_tonumber(J, value);
+			int newlen = jsV_numbertointeger(rawlen);
+			if (newlen != rawlen || newlen < 0)
+				js_rangeerror(J, "invalid array length");
+			if (newlen > JS_ARRAYLIMIT)
+				js_rangeerror(J, "array too large");
+			if (obj->u.a.simple) {
+				obj->u.a.length = newlen;
+				if (newlen <= obj->u.a.flat_length)
+					obj->u.a.flat_length = newlen;
+			} else  {
+				jsV_resizearray(J, obj, newlen);
+			}
+			return;
+		}
+
+		if (js_isarrayindex(J, name, &k)) {
+			if (obj->u.a.simple) {
+				if (k >= 0 && k <= obj->u.a.flat_length) {
+					jsR_setarrayindex(J, obj, k, value);
+				} else {
+					jsR_unflattenarray(J, obj);
+					if (obj->u.a.length < k + 1)
+						obj->u.a.length = k + 1;
+				}
+			} else {
+				if (obj->u.a.length < k + 1)
+					obj->u.a.length = k + 1;
+			}
+		}
+	}
+
+	else if (obj->type == JS_CSTRING) {
+		if (!strcmp(name, "length"))
+			goto readonly;
+		if (js_isarrayindex(J, name, &k))
+			if (k >= 0 && k < obj->u.s.length)
+				goto readonly;
+	}
+
+	else if (obj->type == JS_CREGEXP) {
+		if (!strcmp(name, "source")) goto readonly;
+		if (!strcmp(name, "global")) goto readonly;
+		if (!strcmp(name, "ignoreCase")) goto readonly;
+		if (!strcmp(name, "multiline")) goto readonly;
+		if (!strcmp(name, "lastIndex")) {
+			obj->u.r.last = jsV_tointeger(J, value);
+			return;
+		}
+	}
+
+	else if (obj->type == JS_CUSERDATA) {
+		if (obj->u.user.put && obj->u.user.put(J, obj->u.user.data, name))
+			return;
+	}
+
+	/* First try to find a setter in prototype chain */
+	ref = jsV_getpropertyx(J, obj, name, &own);
+	if (ref) {
+		if (ref->setter) {
+			js_pushobject(J, ref->setter);
+			js_pushobject(J, obj);
+			js_pushvalue(J, *value);
+			js_call(J, 1);
+			js_pop(J, 1);
+			return;
+		} else {
+			if (J->strict)
+				if (ref->getter)
+					js_typeerror(J, "setting property '%s' that only has a getter", name);
+			if (ref->atts & JS_READONLY)
+				goto readonly;
+		}
+	}
+
+	/* Property not found on this object, so create one */
+	if (!ref || !own) {
+		if (transient) {
+			if (J->strict)
+				js_typeerror(J, "cannot create property '%s' on transient object", name);
+			return;
+		}
+		ref = jsV_setproperty(J, obj, name);
+	}
+
+	if (ref) {
+		if (!(ref->atts & JS_READONLY))
+			ref->value = *value;
+		else
+			goto readonly;
+	}
+
+	return;
+
+readonly:
+	if (J->strict)
+		js_typeerror(J, "'%s' is read-only", name);
+}
+
+static void jsR_setindex(js_State *J, js_Object *obj, int k, int transient)
+{
+	char buf[32];
+	if (obj->type == JS_CARRAY && obj->u.a.simple && k >= 0 && k <= obj->u.a.flat_length) {
+		jsR_setarrayindex(J, obj, k, stackidx(J, -1));
+	} else {
+		jsR_setproperty(J, obj, js_itoa(buf, k), transient);
+	}
+}
+
+static void jsR_defproperty(js_State *J, js_Object *obj, const char *name,
+	int atts, js_Value *value, js_Object *getter, js_Object *setter,
+	int throw)
+{
+	js_Property *ref;
+	int k;
+
+	if (obj->type == JS_CARRAY) {
+		if (!strcmp(name, "length"))
+			goto readonly;
+		if (obj->u.a.simple)
+			jsR_unflattenarray(J, obj);
+	}
+
+	else if (obj->type == JS_CSTRING) {
+		if (!strcmp(name, "length"))
+			goto readonly;
+		if (js_isarrayindex(J, name, &k))
+			if (k >= 0 && k < obj->u.s.length)
+				goto readonly;
+	}
+
+	else if (obj->type == JS_CREGEXP) {
+		if (!strcmp(name, "source")) goto readonly;
+		if (!strcmp(name, "global")) goto readonly;
+		if (!strcmp(name, "ignoreCase")) goto readonly;
+		if (!strcmp(name, "multiline")) goto readonly;
+		if (!strcmp(name, "lastIndex")) goto readonly;
+	}
+
+	else if (obj->type == JS_CUSERDATA) {
+		if (obj->u.user.put && obj->u.user.put(J, obj->u.user.data, name))
+			return;
+	}
+
+	ref = jsV_setproperty(J, obj, name);
+	if (ref) {
+		if (value) {
+			if (!(ref->atts & JS_READONLY))
+				ref->value = *value;
+			else if (J->strict)
+				js_typeerror(J, "'%s' is read-only", name);
+		}
+		if (getter) {
+			if (!(ref->atts & JS_DONTCONF))
+				ref->getter = getter;
+			else if (J->strict)
+				js_typeerror(J, "'%s' is non-configurable", name);
+		}
+		if (setter) {
+			if (!(ref->atts & JS_DONTCONF))
+				ref->setter = setter;
+			else if (J->strict)
+				js_typeerror(J, "'%s' is non-configurable", name);
+		}
+		ref->atts |= atts;
+	}
+
+	return;
+
+readonly:
+	if (J->strict || throw)
+		js_typeerror(J, "'%s' is read-only or non-configurable", name);
+}
+
+static int jsR_delproperty(js_State *J, js_Object *obj, const char *name)
+{
+	js_Property *ref;
+	int k;
+
+	if (obj->type == JS_CARRAY) {
+		if (!strcmp(name, "length"))
+			goto dontconf;
+		if (obj->u.a.simple)
+			jsR_unflattenarray(J, obj);
+	}
+
+	else if (obj->type == JS_CSTRING) {
+		if (!strcmp(name, "length"))
+			goto dontconf;
+		if (js_isarrayindex(J, name, &k))
+			if (k >= 0 && k < obj->u.s.length)
+				goto dontconf;
+	}
+
+	else if (obj->type == JS_CREGEXP) {
+		if (!strcmp(name, "source")) goto dontconf;
+		if (!strcmp(name, "global")) goto dontconf;
+		if (!strcmp(name, "ignoreCase")) goto dontconf;
+		if (!strcmp(name, "multiline")) goto dontconf;
+		if (!strcmp(name, "lastIndex")) goto dontconf;
+	}
+
+	else if (obj->type == JS_CUSERDATA) {
+		if (obj->u.user.delete && obj->u.user.delete(J, obj->u.user.data, name))
+			return 1;
+	}
+
+	ref = jsV_getownproperty(J, obj, name);
+	if (ref) {
+		if (ref->atts & JS_DONTCONF)
+			goto dontconf;
+		jsV_delproperty(J, obj, name);
+	}
+	return 1;
+
+dontconf:
+	if (J->strict)
+		js_typeerror(J, "'%s' is non-configurable", name);
+	return 0;
+}
+
+static void jsR_delindex(js_State *J, js_Object *obj, int k)
+{
+	char buf[32];
+	/* Allow deleting last element of a simple array without unflattening */
+	if (obj->type == JS_CARRAY && obj->u.a.simple && k == obj->u.a.flat_length - 1)
+		obj->u.a.flat_length = k;
+	else
+		jsR_delproperty(J, obj, js_itoa(buf, k));
+}
+
+/* Registry, global and object property accessors */
+
+const char *js_ref(js_State *J)
+{
+	js_Value *v = stackidx(J, -1);
+	const char *s;
+	char buf[32];
+	switch (v->t.type) {
+	case JS_TUNDEFINED: s = "_Undefined"; break;
+	case JS_TNULL: s = "_Null"; break;
+	case JS_TBOOLEAN:
+		s = v->u.boolean ? "_True" : "_False";
+		break;
+	case JS_TOBJECT:
+		sprintf(buf, "%p", (void*)v->u.object);
+		s = js_intern(J, buf);
+		break;
+	default:
+		sprintf(buf, "%d", J->nextref++);
+		s = js_intern(J, buf);
+		break;
+	}
+	js_setregistry(J, s);
+	return s;
+}
+
+void js_unref(js_State *J, const char *ref)
+{
+	js_delregistry(J, ref);
+}
+
+void js_getregistry(js_State *J, const char *name)
+{
+	jsR_getproperty(J, J->R, name);
+}
+
+void js_setregistry(js_State *J, const char *name)
+{
+	jsR_setproperty(J, J->R, name, 0);
+	js_pop(J, 1);
+}
+
+void js_delregistry(js_State *J, const char *name)
+{
+	jsR_delproperty(J, J->R, name);
+}
+
+void js_getglobal(js_State *J, const char *name)
+{
+	jsR_getproperty(J, J->G, name);
+}
+
+void js_setglobal(js_State *J, const char *name)
+{
+	jsR_setproperty(J, J->G, name, 0);
+	js_pop(J, 1);
+}
+
+void js_defglobal(js_State *J, const char *name, int atts)
+{
+	jsR_defproperty(J, J->G, name, atts, stackidx(J, -1), NULL, NULL, 0);
+	js_pop(J, 1);
+}
+
+void js_delglobal(js_State *J, const char *name)
+{
+	jsR_delproperty(J, J->G, name);
+}
+
+void js_getproperty(js_State *J, int idx, const char *name)
+{
+	jsR_getproperty(J, js_toobject(J, idx), name);
+}
+
+void js_setproperty(js_State *J, int idx, const char *name)
+{
+	jsR_setproperty(J, js_toobject(J, idx), name, !js_isobject(J, idx));
+	js_pop(J, 1);
+}
+
+void js_defproperty(js_State *J, int idx, const char *name, int atts)
+{
+	jsR_defproperty(J, js_toobject(J, idx), name, atts, stackidx(J, -1), NULL, NULL, 1);
+	js_pop(J, 1);
+}
+
+void js_delproperty(js_State *J, int idx, const char *name)
+{
+	jsR_delproperty(J, js_toobject(J, idx), name);
+}
+
+void js_defaccessor(js_State *J, int idx, const char *name, int atts)
+{
+	jsR_defproperty(J, js_toobject(J, idx), name, atts, NULL, jsR_tofunction(J, -2), jsR_tofunction(J, -1), 1);
+	js_pop(J, 2);
+}
+
+int js_hasproperty(js_State *J, int idx, const char *name)
+{
+	return jsR_hasproperty(J, js_toobject(J, idx), name);
+}
+
+void js_getindex(js_State *J, int idx, int i)
+{
+	jsR_getindex(J, js_toobject(J, idx), i);
+}
+
+int js_hasindex(js_State *J, int idx, int i)
+{
+	return jsR_hasindex(J, js_toobject(J, idx), i);
+}
+
+void js_setindex(js_State *J, int idx, int i)
+{
+	jsR_setindex(J, js_toobject(J, idx), i, !js_isobject(J, idx));
+	js_pop(J, 1);
+}
+
+void js_delindex(js_State *J, int idx, int i)
+{
+	jsR_delindex(J, js_toobject(J, idx), i);
+}
+
+/* Iterator */
+
+void js_pushiterator(js_State *J, int idx, int own)
+{
+	js_pushobject(J, jsV_newiterator(J, js_toobject(J, idx), own));
+}
+
+const char *js_nextiterator(js_State *J, int idx)
+{
+	return jsV_nextiterator(J, js_toobject(J, idx));
+}
+
+/* Environment records */
+
+js_Environment *jsR_newenvironment(js_State *J, js_Object *vars, js_Environment *outer)
+{
+	js_Environment *E = js_malloc(J, sizeof *E);
+	E->gcmark = 0;
+	E->gcnext = J->gcenv;
+	J->gcenv = E;
+	++J->gccounter;
+
+	E->outer = outer;
+	E->variables = vars;
+	return E;
+}
+
+static void js_initvar(js_State *J, const char *name, int idx)
+{
+	jsR_defproperty(J, J->E->variables, name, JS_DONTENUM | JS_DONTCONF, stackidx(J, idx), NULL, NULL, 0);
+}
+
+static int js_hasvar(js_State *J, const char *name)
+{
+	js_Environment *E = J->E;
+	do {
+		js_Property *ref = jsV_getproperty(J, E->variables, name);
+		if (ref) {
+			if (ref->getter) {
+				js_pushobject(J, ref->getter);
+				js_pushobject(J, E->variables);
+				js_call(J, 0);
+			} else {
+				js_pushvalue(J, ref->value);
+			}
+			return 1;
+		}
+		E = E->outer;
+	} while (E);
+	return 0;
+}
+
+static void js_setvar(js_State *J, const char *name)
+{
+	js_Environment *E = J->E;
+	do {
+		js_Property *ref = jsV_getproperty(J, E->variables, name);
+		if (ref) {
+			if (ref->setter) {
+				js_pushobject(J, ref->setter);
+				js_pushobject(J, E->variables);
+				js_copy(J, -3);
+				js_call(J, 1);
+				js_pop(J, 1);
+				return;
+			}
+			if (!(ref->atts & JS_READONLY))
+				ref->value = *stackidx(J, -1);
+			else if (J->strict)
+				js_typeerror(J, "'%s' is read-only", name);
+			return;
+		}
+		E = E->outer;
+	} while (E);
+	if (J->strict)
+		js_referenceerror(J, "assignment to undeclared variable '%s'", name);
+	jsR_setproperty(J, J->G, name, 0);
+}
+
+static int js_delvar(js_State *J, const char *name)
+{
+	js_Environment *E = J->E;
+	do {
+		js_Property *ref = jsV_getownproperty(J, E->variables, name);
+		if (ref) {
+			if (ref->atts & JS_DONTCONF) {
+				if (J->strict)
+					js_typeerror(J, "'%s' is non-configurable", name);
+				return 0;
+			}
+			jsV_delproperty(J, E->variables, name);
+			return 1;
+		}
+		E = E->outer;
+	} while (E);
+	return jsR_delproperty(J, J->G, name);
+}
+
+/* Function calls */
+
+static void jsR_savescope(js_State *J, js_Environment *newE)
+{
+	if (J->envtop + 1 >= JS_ENVLIMIT)
+		js_stackoverflow(J);
+	J->envstack[J->envtop++] = J->E;
+	J->E = newE;
+}
+
+static void jsR_restorescope(js_State *J)
+{
+	J->E = J->envstack[--J->envtop];
+}
+
+static void jsR_calllwfunction(js_State *J, int n, js_Function *F, js_Environment *scope)
+{
+	js_Value v;
+	int i;
+
+	jsR_savescope(J, scope);
+
+	if (n > F->numparams) {
+		js_pop(J, n - F->numparams);
+		n = F->numparams;
+	}
+
+	for (i = n; i < F->varlen; ++i)
+		js_pushundefined(J);
+
+	jsR_run(J, F);
+	v = *stackidx(J, -1);
+	TOP = --BOT; /* clear stack */
+	js_pushvalue(J, v);
+
+	jsR_restorescope(J);
+}
+
+static void jsR_callfunction(js_State *J, int n, js_Function *F, js_Environment *scope)
+{
+	js_Value v;
+	int i;
+
+	scope = jsR_newenvironment(J, jsV_newobject(J, JS_COBJECT, NULL), scope);
+
+	jsR_savescope(J, scope);
+
+	if (F->arguments) {
+		js_newarguments(J);
+		if (!J->strict) {
+			js_currentfunction(J);
+			js_defproperty(J, -2, "callee", JS_DONTENUM);
+		}
+		js_pushnumber(J, n);
+		js_defproperty(J, -2, "length", JS_DONTENUM);
+		for (i = 0; i < n; ++i) {
+			js_copy(J, i + 1);
+			js_setindex(J, -2, i);
+		}
+		js_initvar(J, "arguments", -1);
+		js_pop(J, 1);
+	}
+
+	for (i = 0; i < n && i < F->numparams; ++i)
+		js_initvar(J, F->vartab[i], i + 1);
+	js_pop(J, n);
+
+	for (; i < F->varlen; ++i) {
+		js_pushundefined(J);
+		js_initvar(J, F->vartab[i], -1);
+		js_pop(J, 1);
+	}
+
+	jsR_run(J, F);
+	v = *stackidx(J, -1);
+	TOP = --BOT; /* clear stack */
+	js_pushvalue(J, v);
+
+	jsR_restorescope(J);
+}
+
+static void jsR_callscript(js_State *J, int n, js_Function *F, js_Environment *scope)
+{
+	js_Value v;
+	int i;
+
+	if (scope)
+		jsR_savescope(J, scope);
+
+	/* scripts take no arguments */
+	js_pop(J, n);
+
+	for (i = 0; i < F->varlen; ++i) {
+		/* Bug 701886: don't redefine existing vars in eval/scripts */
+		if (!js_hasvar(J, F->vartab[i])) {
+			js_pushundefined(J);
+			js_initvar(J, F->vartab[i], -1);
+			js_pop(J, 1);
+		}
+	}
+
+	jsR_run(J, F);
+	v = *stackidx(J, -1);
+	TOP = --BOT; /* clear stack */
+	js_pushvalue(J, v);
+
+	if (scope)
+		jsR_restorescope(J);
+}
+
+static void jsR_callcfunction(js_State *J, int n, int min, js_CFunction F)
+{
+	int save_top;
+	int i;
+	js_Value v;
+
+	for (i = n; i < min; ++i)
+		js_pushundefined(J);
+
+	save_top = TOP;
+	F(J);
+	if (TOP > save_top) {
+		v = *stackidx(J, -1);
+		TOP = --BOT; /* clear stack */
+		js_pushvalue(J, v);
+	} else {
+		TOP = --BOT; /* clear stack */
+		js_pushundefined(J);
+	}
+}
+
+static void jsR_pushtrace(js_State *J, const char *name, const char *file, int line)
+{
+	if (J->tracetop + 1 == JS_ENVLIMIT)
+		js_error(J, "call stack overflow");
+	++J->tracetop;
+	J->trace[J->tracetop].name = name;
+	J->trace[J->tracetop].file = file;
+	J->trace[J->tracetop].line = line;
+}
+
+void js_call(js_State *J, int n)
+{
+	js_Object *obj;
+	int savebot;
+
+	if (n < 0)
+		js_rangeerror(J, "number of arguments cannot be negative");
+
+	if (!js_iscallable(J, -n-2))
+		js_typeerror(J, "%s is not callable", js_typeof(J, -n-2));
+
+	obj = js_toobject(J, -n-2);
+
+	savebot = BOT;
+	BOT = TOP - n - 1;
+
+	if (obj->type == JS_CFUNCTION) {
+		jsR_pushtrace(J, obj->u.f.function->name, obj->u.f.function->filename, obj->u.f.function->line);
+		if (obj->u.f.function->lightweight)
+			jsR_calllwfunction(J, n, obj->u.f.function, obj->u.f.scope);
+		else
+			jsR_callfunction(J, n, obj->u.f.function, obj->u.f.scope);
+		--J->tracetop;
+	} else if (obj->type == JS_CSCRIPT) {
+		jsR_pushtrace(J, obj->u.f.function->name, obj->u.f.function->filename, obj->u.f.function->line);
+		jsR_callscript(J, n, obj->u.f.function, obj->u.f.scope);
+		--J->tracetop;
+	} else if (obj->type == JS_CCFUNCTION) {
+		jsR_pushtrace(J, obj->u.c.name, "native", 0);
+		jsR_callcfunction(J, n, obj->u.c.length, obj->u.c.function);
+		--J->tracetop;
+	}
+
+	BOT = savebot;
+}
+
+void js_construct(js_State *J, int n)
+{
+	js_Object *obj;
+	js_Object *prototype;
+	js_Object *newobj;
+
+	if (!js_iscallable(J, -n-1))
+		js_typeerror(J, "%s is not callable", js_typeof(J, -n-1));
+
+	obj = js_toobject(J, -n-1);
+
+	/* built-in constructors create their own objects, give them a 'null' this */
+	if (obj->type == JS_CCFUNCTION && obj->u.c.constructor) {
+		int savebot = BOT;
+		js_pushnull(J);
+		if (n > 0)
+			js_rot(J, n + 1);
+		BOT = TOP - n - 1;
+
+		jsR_pushtrace(J, obj->u.c.name, "native", 0);
+		jsR_callcfunction(J, n, obj->u.c.length, obj->u.c.constructor);
+		--J->tracetop;
+
+		BOT = savebot;
+		return;
+	}
+
+	/* extract the function object's prototype property */
+	js_getproperty(J, -n - 1, "prototype");
+	if (js_isobject(J, -1))
+		prototype = js_toobject(J, -1);
+	else
+		prototype = J->Object_prototype;
+	js_pop(J, 1);
+
+	/* create a new object with above prototype, and shift it into the 'this' slot */
+	newobj = jsV_newobject(J, JS_COBJECT, prototype);
+	js_pushobject(J, newobj);
+	if (n > 0)
+		js_rot(J, n + 1);
+
+	/* and save a copy to return */
+	js_pushobject(J, newobj);
+	js_rot(J, n + 3);
+
+	/* call the function */
+	js_call(J, n);
+
+	/* if result is not an object, return the original object we created */
+	if (!js_isobject(J, -1)) {
+		js_pop(J, 1);
+	} else {
+		js_rot2pop1(J);
+	}
+}
+
+void js_eval(js_State *J)
+{
+	if (!js_isstring(J, -1))
+		return;
+	js_loadeval(J, "(eval)", js_tostring(J, -1));
+	js_rot2pop1(J);
+	js_copy(J, 0); /* copy 'this' */
+	js_call(J, 0);
+}
+
+int js_pconstruct(js_State *J, int n)
+{
+	int savetop = TOP - n - 2;
+	if (js_try(J)) {
+		/* clean up the stack to only hold the error object */
+		STACK[savetop] = STACK[TOP-1];
+		TOP = savetop + 1;
+		return 1;
+	}
+	js_construct(J, n);
+	js_endtry(J);
+	return 0;
+}
+
+int js_pcall(js_State *J, int n)
+{
+	int savetop = TOP - n - 2;
+	if (js_try(J)) {
+		/* clean up the stack to only hold the error object */
+		STACK[savetop] = STACK[TOP-1];
+		TOP = savetop + 1;
+		return 1;
+	}
+	js_call(J, n);
+	js_endtry(J);
+	return 0;
+}
+
+/* Exceptions */
+
+void *js_savetrypc(js_State *J, js_Instruction *pc)
+{
+	if (J->trytop == JS_TRYLIMIT)
+		js_trystackoverflow(J);
+	J->trybuf[J->trytop].E = J->E;
+	J->trybuf[J->trytop].envtop = J->envtop;
+	J->trybuf[J->trytop].tracetop = J->tracetop;
+	J->trybuf[J->trytop].top = J->top;
+	J->trybuf[J->trytop].bot = J->bot;
+	J->trybuf[J->trytop].strict = J->strict;
+	J->trybuf[J->trytop].pc = pc;
+	return J->trybuf[J->trytop++].buf;
+}
+
+void *js_savetry(js_State *J)
+{
+	if (J->trytop == JS_TRYLIMIT)
+		js_trystackoverflow(J);
+	J->trybuf[J->trytop].E = J->E;
+	J->trybuf[J->trytop].envtop = J->envtop;
+	J->trybuf[J->trytop].tracetop = J->tracetop;
+	J->trybuf[J->trytop].top = J->top;
+	J->trybuf[J->trytop].bot = J->bot;
+	J->trybuf[J->trytop].strict = J->strict;
+	J->trybuf[J->trytop].pc = NULL;
+	return J->trybuf[J->trytop++].buf;
+}
+
+void js_endtry(js_State *J)
+{
+	if (J->trytop == 0)
+		js_error(J, "endtry: exception stack underflow");
+	--J->trytop;
+}
+
+void js_throw(js_State *J)
+{
+	if (J->trytop > 0) {
+		js_Value v = *stackidx(J, -1);
+		--J->trytop;
+		J->E = J->trybuf[J->trytop].E;
+		J->envtop = J->trybuf[J->trytop].envtop;
+		J->tracetop = J->trybuf[J->trytop].tracetop;
+		J->top = J->trybuf[J->trytop].top;
+		J->bot = J->trybuf[J->trytop].bot;
+		J->strict = J->trybuf[J->trytop].strict;
+		js_pushvalue(J, v);
+		longjmp(J->trybuf[J->trytop].buf, 1);
+	}
+	if (J->panic)
+		J->panic(J);
+	abort();
+}
+
+/* Main interpreter loop */
+
+static void js_dumpvalue(js_State *J, js_Value v)
+{
+	switch (v.t.type) {
+	case JS_TUNDEFINED: printf("undefined"); break;
+	case JS_TNULL: printf("null"); break;
+	case JS_TBOOLEAN: printf(v.u.boolean ? "true" : "false"); break;
+	case JS_TNUMBER: printf("%.9g", v.u.number); break;
+	case JS_TSHRSTR: printf("'%s'", v.u.shrstr); break;
+	case JS_TLITSTR: printf("'%s'", v.u.litstr); break;
+	case JS_TMEMSTR: printf("'%s'", v.u.memstr->p); break;
+	case JS_TOBJECT:
+		if (v.u.object == J->G) {
+			printf("[Global]");
+			break;
+		}
+		switch (v.u.object->type) {
+		case JS_COBJECT: printf("[Object %p]", (void*)v.u.object); break;
+		case JS_CARRAY: printf("[Array %p]", (void*)v.u.object); break;
+		case JS_CFUNCTION:
+			printf("[Function %p, %s, %s:%d]",
+				(void*)v.u.object,
+				v.u.object->u.f.function->name,
+				v.u.object->u.f.function->filename,
+				v.u.object->u.f.function->line);
+			break;
+		case JS_CSCRIPT: printf("[Script %s]", v.u.object->u.f.function->filename); break;
+		case JS_CCFUNCTION: printf("[CFunction %s]", v.u.object->u.c.name); break;
+		case JS_CBOOLEAN: printf("[Boolean %d]", v.u.object->u.boolean); break;
+		case JS_CNUMBER: printf("[Number %g]", v.u.object->u.number); break;
+		case JS_CSTRING: printf("[String'%s']", v.u.object->u.s.string); break;
+		case JS_CERROR: printf("[Error]"); break;
+		case JS_CARGUMENTS: printf("[Arguments %p]", (void*)v.u.object); break;
+		case JS_CITERATOR: printf("[Iterator %p]", (void*)v.u.object); break;
+		case JS_CUSERDATA:
+			printf("[Userdata %s %p]", v.u.object->u.user.tag, v.u.object->u.user.data);
+			break;
+		default: printf("[Object %p]", (void*)v.u.object); break;
+		}
+		break;
+	}
+}
+
+static void js_stacktrace(js_State *J)
+{
+	int n;
+	printf("stack trace:\n");
+	for (n = J->tracetop; n >= 0; --n) {
+		const char *name = J->trace[n].name;
+		const char *file = J->trace[n].file;
+		int line = J->trace[n].line;
+		if (line > 0) {
+			if (name[0])
+				printf("\tat %s (%s:%d)\n", name, file, line);
+			else
+				printf("\tat %s:%d\n", file, line);
+		} else
+			printf("\tat %s (%s)\n", name, file);
+	}
+}
+
+static void js_dumpstack(js_State *J)
+{
+	int i;
+	printf("stack {\n");
+	for (i = 0; i < TOP; ++i) {
+		putchar(i == BOT ? '>' : ' ');
+		printf("%4d: ", i);
+		js_dumpvalue(J, STACK[i]);
+		putchar('\n');
+	}
+	printf("}\n");
+}
+
+void js_trap(js_State *J, int pc)
+{
+	js_dumpstack(J);
+	js_stacktrace(J);
+}
+
+static int jsR_isindex(js_State *J, int idx, int *k)
+{
+	js_Value *v = stackidx(J, idx);
+	if (v->t.type == JS_TNUMBER) {
+		*k = v->u.number;
+		return *k == v->u.number && *k >= 0;
+	}
+	return 0;
+}
+
+static void jsR_run(js_State *J, js_Function *F)
+{
+	js_Function **FT = F->funtab;
+	const char **VT = F->vartab-1;
+	int lightweight = F->lightweight;
+	js_Instruction *pcstart = F->code;
+	js_Instruction *pc = F->code;
+	enum js_OpCode opcode;
+	int offset;
+	int savestrict;
+
+	const char *str;
+	js_Object *obj;
+	double x, y;
+	unsigned int ux, uy;
+	int ix, iy, okay;
+	int b;
+	int transient;
+
+	savestrict = J->strict;
+	J->strict = F->strict;
+
+#define READSTRING() \
+	memcpy(&str, pc, sizeof(str)); \
+	pc += sizeof(str) / sizeof(*pc)
+
+	while (1) {
+		if (J->gccounter > J->gcthresh)
+			js_gc(J, 0);
+
+		J->trace[J->tracetop].line = *pc++;
+
+		opcode = *pc++;
+
+		switch (opcode) {
+		case OP_POP: js_pop(J, 1); break;
+		case OP_DUP: js_dup(J); break;
+		case OP_DUP2: js_dup2(J); break;
+		case OP_ROT2: js_rot2(J); break;
+		case OP_ROT3: js_rot3(J); break;
+		case OP_ROT4: js_rot4(J); break;
+
+		case OP_INTEGER:
+			js_pushnumber(J, *pc++ - 32768);
+			break;
+
+		case OP_NUMBER:
+			memcpy(&x, pc, sizeof(x));
+			pc += sizeof(x) / sizeof(*pc);
+			js_pushnumber(J, x);
+			break;
+
+		case OP_STRING:
+			READSTRING();
+			js_pushliteral(J, str);
+			break;
+
+		case OP_CLOSURE: js_newfunction(J, FT[*pc++], J->E); break;
+		case OP_NEWOBJECT: js_newobject(J); break;
+		case OP_NEWARRAY: js_newarray(J); break;
+		case OP_NEWREGEXP:
+			READSTRING();
+			js_newregexp(J, str, *pc++);
+			break;
+
+		case OP_UNDEF: js_pushundefined(J); break;
+		case OP_NULL: js_pushnull(J); break;
+		case OP_TRUE: js_pushboolean(J, 1); break;
+		case OP_FALSE: js_pushboolean(J, 0); break;
+
+		case OP_THIS:
+			if (J->strict) {
+				js_copy(J, 0);
+			} else {
+				if (js_iscoercible(J, 0))
+					js_copy(J, 0);
+				else
+					js_pushglobal(J);
+			}
+			break;
+
+		case OP_CURRENT:
+			js_currentfunction(J);
+			break;
+
+		case OP_GETLOCAL:
+			if (lightweight) {
+				CHECKSTACK(1);
+				STACK[TOP++] = STACK[BOT + *pc++];
+			} else {
+				str = VT[*pc++];
+				if (!js_hasvar(J, str))
+					js_referenceerror(J, "'%s' is not defined", str);
+			}
+			break;
+
+		case OP_SETLOCAL:
+			if (lightweight) {
+				STACK[BOT + *pc++] = STACK[TOP-1];
+			} else {
+				js_setvar(J, VT[*pc++]);
+			}
+			break;
+
+		case OP_DELLOCAL:
+			if (lightweight) {
+				++pc;
+				js_pushboolean(J, 0);
+			} else {
+				b = js_delvar(J, VT[*pc++]);
+				js_pushboolean(J, b);
+			}
+			break;
+
+		case OP_GETVAR:
+			READSTRING();
+			if (!js_hasvar(J, str))
+				js_referenceerror(J, "'%s' is not defined", str);
+			break;
+
+		case OP_HASVAR:
+			READSTRING();
+			if (!js_hasvar(J, str))
+				js_pushundefined(J);
+			break;
+
+		case OP_SETVAR:
+			READSTRING();
+			js_setvar(J, str);
+			break;
+
+		case OP_DELVAR:
+			READSTRING();
+			b = js_delvar(J, str);
+			js_pushboolean(J, b);
+			break;
+
+		case OP_IN:
+			str = js_tostring(J, -2);
+			if (!js_isobject(J, -1))
+				js_typeerror(J, "operand to 'in' is not an object");
+			b = js_hasproperty(J, -1, str);
+			js_pop(J, 2 + b);
+			js_pushboolean(J, b);
+			break;
+
+		case OP_SKIPARRAY:
+			js_setlength(J, -1, js_getlength(J, -1) + 1);
+			break;
+		case OP_INITARRAY:
+			js_setindex(J, -2, js_getlength(J, -2));
+			break;
+
+		case OP_INITPROP:
+			obj = js_toobject(J, -3);
+			str = js_tostring(J, -2);
+			jsR_setproperty(J, obj, str, 0);
+			js_pop(J, 2);
+			break;
+
+		case OP_INITGETTER:
+			obj = js_toobject(J, -3);
+			str = js_tostring(J, -2);
+			jsR_defproperty(J, obj, str, 0, NULL, jsR_tofunction(J, -1), NULL, 0);
+			js_pop(J, 2);
+			break;
+
+		case OP_INITSETTER:
+			obj = js_toobject(J, -3);
+			str = js_tostring(J, -2);
+			jsR_defproperty(J, obj, str, 0, NULL, NULL, jsR_tofunction(J, -1), 0);
+			js_pop(J, 2);
+			break;
+
+		case OP_GETPROP:
+			if (jsR_isindex(J, -1, &ix)) {
+				obj = js_toobject(J, -2);
+				jsR_getindex(J, obj, ix);
+			} else {
+				str = js_tostring(J, -1);
+				obj = js_toobject(J, -2);
+				jsR_getproperty(J, obj, str);
+			}
+			js_rot3pop2(J);
+			break;
+
+		case OP_GETPROP_S:
+			READSTRING();
+			obj = js_toobject(J, -1);
+			jsR_getproperty(J, obj, str);
+			js_rot2pop1(J);
+			break;
+
+		case OP_SETPROP:
+			if (jsR_isindex(J, -2, &ix)) {
+				obj = js_toobject(J, -3);
+				transient = !js_isobject(J, -3);
+				jsR_setindex(J, obj, ix, transient);
+			} else {
+				str = js_tostring(J, -2);
+				obj = js_toobject(J, -3);
+				transient = !js_isobject(J, -3);
+				jsR_setproperty(J, obj, str, transient);
+			}
+			js_rot3pop2(J);
+			break;
+
+		case OP_SETPROP_S:
+			READSTRING();
+			obj = js_toobject(J, -2);
+			transient = !js_isobject(J, -2);
+			jsR_setproperty(J, obj, str, transient);
+			js_rot2pop1(J);
+			break;
+
+		case OP_DELPROP:
+			str = js_tostring(J, -1);
+			obj = js_toobject(J, -2);
+			b = jsR_delproperty(J, obj, str);
+			js_pop(J, 2);
+			js_pushboolean(J, b);
+			break;
+
+		case OP_DELPROP_S:
+			READSTRING();
+			obj = js_toobject(J, -1);
+			b = jsR_delproperty(J, obj, str);
+			js_pop(J, 1);
+			js_pushboolean(J, b);
+			break;
+
+		case OP_ITERATOR:
+			if (js_iscoercible(J, -1)) {
+				obj = jsV_newiterator(J, js_toobject(J, -1), 0);
+				js_pop(J, 1);
+				js_pushobject(J, obj);
+			}
+			break;
+
+		case OP_NEXTITER:
+			if (js_isobject(J, -1)) {
+				obj = js_toobject(J, -1);
+				str = jsV_nextiterator(J, obj);
+				if (str) {
+					js_pushstring(J, str);
+					js_pushboolean(J, 1);
+				} else {
+					js_pop(J, 1);
+					js_pushboolean(J, 0);
+				}
+			} else {
+				js_pop(J, 1);
+				js_pushboolean(J, 0);
+			}
+			break;
+
+		/* Function calls */
+
+		case OP_EVAL:
+			js_eval(J);
+			break;
+
+		case OP_CALL:
+			js_call(J, *pc++);
+			break;
+
+		case OP_NEW:
+			js_construct(J, *pc++);
+			break;
+
+		/* Unary operators */
+
+		case OP_TYPEOF:
+			str = js_typeof(J, -1);
+			js_pop(J, 1);
+			js_pushliteral(J, str);
+			break;
+
+		case OP_POS:
+			x = js_tonumber(J, -1);
+			js_pop(J, 1);
+			js_pushnumber(J, x);
+			break;
+
+		case OP_NEG:
+			x = js_tonumber(J, -1);
+			js_pop(J, 1);
+			js_pushnumber(J, -x);
+			break;
+
+		case OP_BITNOT:
+			ix = js_toint32(J, -1);
+			js_pop(J, 1);
+			js_pushnumber(J, ~ix);
+			break;
+
+		case OP_LOGNOT:
+			b = js_toboolean(J, -1);
+			js_pop(J, 1);
+			js_pushboolean(J, !b);
+			break;
+
+		case OP_INC:
+			x = js_tonumber(J, -1);
+			js_pop(J, 1);
+			js_pushnumber(J, x + 1);
+			break;
+
+		case OP_DEC:
+			x = js_tonumber(J, -1);
+			js_pop(J, 1);
+			js_pushnumber(J, x - 1);
+			break;
+
+		case OP_POSTINC:
+			x = js_tonumber(J, -1);
+			js_pop(J, 1);
+			js_pushnumber(J, x + 1);
+			js_pushnumber(J, x);
+			break;
+
+		case OP_POSTDEC:
+			x = js_tonumber(J, -1);
+			js_pop(J, 1);
+			js_pushnumber(J, x - 1);
+			js_pushnumber(J, x);
+			break;
+
+		/* Multiplicative operators */
+
+		case OP_MUL:
+			x = js_tonumber(J, -2);
+			y = js_tonumber(J, -1);
+			js_pop(J, 2);
+			js_pushnumber(J, x * y);
+			break;
+
+		case OP_DIV:
+			x = js_tonumber(J, -2);
+			y = js_tonumber(J, -1);
+			js_pop(J, 2);
+			js_pushnumber(J, x / y);
+			break;
+
+		case OP_MOD:
+			x = js_tonumber(J, -2);
+			y = js_tonumber(J, -1);
+			js_pop(J, 2);
+			js_pushnumber(J, fmod(x, y));
+			break;
+
+		/* Additive operators */
+
+		case OP_ADD:
+			js_concat(J);
+			break;
+
+		case OP_SUB:
+			x = js_tonumber(J, -2);
+			y = js_tonumber(J, -1);
+			js_pop(J, 2);
+			js_pushnumber(J, x - y);
+			break;
+
+		/* Shift operators */
+
+		case OP_SHL:
+			ix = js_toint32(J, -2);
+			uy = js_touint32(J, -1);
+			js_pop(J, 2);
+			js_pushnumber(J, ix << (uy & 0x1F));
+			break;
+
+		case OP_SHR:
+			ix = js_toint32(J, -2);
+			uy = js_touint32(J, -1);
+			js_pop(J, 2);
+			js_pushnumber(J, ix >> (uy & 0x1F));
+			break;
+
+		case OP_USHR:
+			ux = js_touint32(J, -2);
+			uy = js_touint32(J, -1);
+			js_pop(J, 2);
+			js_pushnumber(J, ux >> (uy & 0x1F));
+			break;
+
+		/* Relational operators */
+
+		case OP_LT: b = js_compare(J, &okay); js_pop(J, 2); js_pushboolean(J, okay && b < 0); break;
+		case OP_GT: b = js_compare(J, &okay); js_pop(J, 2); js_pushboolean(J, okay && b > 0); break;
+		case OP_LE: b = js_compare(J, &okay); js_pop(J, 2); js_pushboolean(J, okay && b <= 0); break;
+		case OP_GE: b = js_compare(J, &okay); js_pop(J, 2); js_pushboolean(J, okay && b >= 0); break;
+
+		case OP_INSTANCEOF:
+			b = js_instanceof(J);
+			js_pop(J, 2);
+			js_pushboolean(J, b);
+			break;
+
+		/* Equality */
+
+		case OP_EQ: b = js_equal(J); js_pop(J, 2); js_pushboolean(J, b); break;
+		case OP_NE: b = js_equal(J); js_pop(J, 2); js_pushboolean(J, !b); break;
+		case OP_STRICTEQ: b = js_strictequal(J); js_pop(J, 2); js_pushboolean(J, b); break;
+		case OP_STRICTNE: b = js_strictequal(J); js_pop(J, 2); js_pushboolean(J, !b); break;
+
+		case OP_JCASE:
+			offset = *pc++;
+			b = js_strictequal(J);
+			if (b) {
+				js_pop(J, 2);
+				pc = pcstart + offset;
+			} else {
+				js_pop(J, 1);
+			}
+			break;
+
+		/* Binary bitwise operators */
+
+		case OP_BITAND:
+			ix = js_toint32(J, -2);
+			iy = js_toint32(J, -1);
+			js_pop(J, 2);
+			js_pushnumber(J, ix & iy);
+			break;
+
+		case OP_BITXOR:
+			ix = js_toint32(J, -2);
+			iy = js_toint32(J, -1);
+			js_pop(J, 2);
+			js_pushnumber(J, ix ^ iy);
+			break;
+
+		case OP_BITOR:
+			ix = js_toint32(J, -2);
+			iy = js_toint32(J, -1);
+			js_pop(J, 2);
+			js_pushnumber(J, ix | iy);
+			break;
+
+		/* Try and Catch */
+
+		case OP_THROW:
+			js_throw(J);
+
+		case OP_TRY:
+			offset = *pc++;
+			if (js_trypc(J, pc)) {
+				pc = J->trybuf[J->trytop].pc;
+			} else {
+				pc = pcstart + offset;
+			}
+			break;
+
+		case OP_ENDTRY:
+			js_endtry(J);
+			break;
+
+		case OP_CATCH:
+			READSTRING();
+			obj = jsV_newobject(J, JS_COBJECT, NULL);
+			js_pushobject(J, obj);
+			js_rot2(J);
+			js_setproperty(J, -2, str);
+			J->E = jsR_newenvironment(J, obj, J->E);
+			js_pop(J, 1);
+			break;
+
+		case OP_ENDCATCH:
+			J->E = J->E->outer;
+			break;
+
+		/* With */
+
+		case OP_WITH:
+			obj = js_toobject(J, -1);
+			J->E = jsR_newenvironment(J, obj, J->E);
+			js_pop(J, 1);
+			break;
+
+		case OP_ENDWITH:
+			J->E = J->E->outer;
+			break;
+
+		/* Branching */
+
+		case OP_DEBUGGER:
+			js_trap(J, (int)(pc - pcstart) - 1);
+			break;
+
+		case OP_JUMP:
+			pc = pcstart + *pc;
+			break;
+
+		case OP_JTRUE:
+			offset = *pc++;
+			b = js_toboolean(J, -1);
+			js_pop(J, 1);
+			if (b)
+				pc = pcstart + offset;
+			break;
+
+		case OP_JFALSE:
+			offset = *pc++;
+			b = js_toboolean(J, -1);
+			js_pop(J, 1);
+			if (!b)
+				pc = pcstart + offset;
+			break;
+
+		case OP_RETURN:
+			J->strict = savestrict;
+			return;
+		}
+	}
+}