shithub: libmujs

Download patch

ref: 3a92e13e47b58c6416a9c5c2738a6ed96032c059
parent: 6c8400d872634c40ca072d94696eadf0d87c9393
author: Tor Andersson <[email protected]>
date: Tue Jan 21 08:58:32 EST 2014

Rename files and move a few functions around.

--- a/js.h
+++ b/js.h
@@ -4,19 +4,29 @@
 #include "jsconf.h"
 
 typedef struct js_State js_State;
+
 typedef int (*js_CFunction)(js_State *J, int argc);
 
 /* Basic functions */
-
 js_State *js_newstate(void);
 void js_freestate(js_State *J);
-
 int js_dostring(js_State *J, const char *source);
 int js_dofile(js_State *J, const char *filename);
-
 void js_gc(js_State *J, int report);
 
-const char *js_intern(js_State *J, const char *s);
+/* RegExp flags */
+enum {
+	JS_REGEXP_G = 1,
+	JS_REGEXP_I = 2,
+	JS_REGEXP_M = 4,
+};
+
+/* Hint to ToPrimitive() */
+enum {
+	JS_HNONE,
+	JS_HNUMBER,
+	JS_HSTRING
+};
 
 /* Property attribute flags */
 enum {
--- /dev/null
+++ b/jsarray.c
@@ -1,0 +1,28 @@
+#include "jsi.h"
+#include "jsvalue.h"
+#include "jsbuiltin.h"
+
+static int jsB_Array(js_State *J, int argc) { return 0; }
+static int jsB_new_Array(js_State *J, int argc) { return 0; }
+
+static int A_isArray(js_State *J, int argc)
+{
+	if (js_isobject(J, 1)) {
+		js_Object *T = js_toobject(J, 1);
+		js_pushboolean(J, T->type == JS_CARRAY);
+		return 1;
+	}
+	js_pushboolean(J, 0);
+	return 1;
+}
+
+void jsB_initarray(js_State *J)
+{
+	js_pushobject(J, J->Array_prototype);
+	js_newcconstructor(J, jsB_Array, jsB_new_Array);
+	{
+		/* ECMA-262-5 */
+		jsB_propf(J, "isArray", A_isArray, 1);
+	}
+	js_setglobal(J, "Array");
+}
--- a/jsbarray.c
+++ /dev/null
@@ -1,28 +1,0 @@
-#include "jsi.h"
-#include "jsvalue.h"
-#include "jsbuiltin.h"
-
-static int jsB_Array(js_State *J, int argc) { return 0; }
-static int jsB_new_Array(js_State *J, int argc) { return 0; }
-
-static int A_isArray(js_State *J, int argc)
-{
-	if (js_isobject(J, 1)) {
-		js_Object *T = js_toobject(J, 1);
-		js_pushboolean(J, T->type == JS_CARRAY);
-		return 1;
-	}
-	js_pushboolean(J, 0);
-	return 1;
-}
-
-void jsB_initarray(js_State *J)
-{
-	js_pushobject(J, J->Array_prototype);
-	js_newcconstructor(J, jsB_Array, jsB_new_Array);
-	{
-		/* ECMA-262-5 */
-		jsB_propf(J, "isArray", A_isArray, 1);
-	}
-	js_setglobal(J, "Array");
-}
--- a/jsbboolean.c
+++ /dev/null
@@ -1,44 +1,0 @@
-#include "jsi.h"
-#include "jsvalue.h"
-#include "jsbuiltin.h"
-
-static int jsB_new_Boolean(js_State *J, int argc)
-{
-	js_newboolean(J, js_toboolean(J, 1));
-	return 1;
-}
-
-static int jsB_Boolean(js_State *J, int argc)
-{
-	js_pushboolean(J, js_toboolean(J, 1));
-	return 1;
-}
-
-static int Bp_toString(js_State *J, int argc)
-{
-	js_Object *self = js_toobject(J, 0);
-	if (self->type != JS_CBOOLEAN) js_typeerror(J, "not a boolean");
-	js_pushliteral(J, self->u.boolean ? "true" : "false");
-	return 1;
-}
-
-static int Bp_valueOf(js_State *J, int argc)
-{
-	js_Object *self = js_toobject(J, 0);
-	if (self->type != JS_CBOOLEAN) js_typeerror(J, "not a boolean");
-	js_pushboolean(J, self->u.boolean);
-	return 1;
-}
-
-void jsB_initboolean(js_State *J)
-{
-	J->Boolean_prototype->u.boolean = 0;
-
-	js_pushobject(J, J->Boolean_prototype);
-	{
-		jsB_propf(J, "toString", Bp_toString, 0);
-		jsB_propf(J, "valueOf", Bp_valueOf, 0);
-	}
-	js_newcconstructor(J, jsB_Boolean, jsB_new_Boolean);
-	js_setglobal(J, "Boolean");
-}
--- a/jsberror.c
+++ /dev/null
@@ -1,59 +1,0 @@
-#include "jsi.h"
-#include "jsvalue.h"
-#include "jsbuiltin.h"
-
-#define QQ(X) #X
-#define Q(X) QQ(X)
-
-static int Ep_toString(js_State *J, int argc)
-{
-	js_getproperty(J, 0, "name");
-	js_pushliteral(J, ": ");
-	js_concat(J);
-	js_getproperty(J, 0, "message");
-	js_concat(J);
-	return 1;
-}
-
-#define DECL(NAME) \
-	static int jsB_##NAME(js_State *J, int argc) { \
-		js_pushobject(J, jsV_newobject(J, JS_CERROR, J->NAME##_prototype)); \
-		if (argc > 0) { \
-			js_pushstring(J, js_tostring(J, 1)); \
-			js_setproperty(J, -2, "message"); \
-		} \
-		return 1; \
-	} \
-
-DECL(Error);
-DECL(EvalError);
-DECL(RangeError);
-DECL(ReferenceError);
-DECL(SyntaxError);
-DECL(TypeError);
-DECL(URIError);
-
-void jsB_initerror(js_State *J)
-{
-	js_pushobject(J, J->Error_prototype);
-	{
-			jsB_props(J, "name", "Error");
-			jsB_props(J, "message", "an error has occurred");
-			jsB_propf(J, "toString", Ep_toString, 0);
-	}
-	js_newcconstructor(J, jsB_Error, jsB_Error);
-	js_setglobal(J, "Error");
-
-	#define INIT(NAME) \
-		js_pushobject(J, J->NAME##_prototype); \
-		jsB_props(J, "name", Q(NAME)); \
-		js_newcconstructor(J, jsB_##NAME, jsB_##NAME); \
-		js_setglobal(J, Q(NAME));
-
-	INIT(EvalError);
-	INIT(RangeError);
-	INIT(ReferenceError);
-	INIT(SyntaxError);
-	INIT(TypeError);
-	INIT(URIError);
-}
--- a/jsbfunction.c
+++ /dev/null
@@ -1,101 +1,0 @@
-#include "jsi.h"
-#include "jscompile.h"
-#include "jsvalue.h"
-#include "jsbuiltin.h"
-
-static int jsB_new_Function(js_State *J, int argc) { return 0; }
-static int jsB_Function(js_State *J, int argc) { return 0; }
-
-static int jsB_Function_prototype(js_State *J, int argc)
-{
-	js_pushundefined(J);
-	return 1;
-}
-
-static int Fp_toString(js_State *J, int argc)
-{
-	js_Object *self = js_toobject(J, 0);
-	char *s;
-	int i, n;
-
-	if (!js_iscallable(J, 0))
-		js_typeerror(J, "not a function");
-
-	if (self->type == JS_CFUNCTION || self->type == JS_CSCRIPT) {
-		js_Function *F = self->u.f.function;
-		n = strlen("function () { ... }");
-		n += strlen(F->name);
-		for (i = 0; i < F->numparams; i++)
-			n += strlen(F->params[i]) + 1;
-		s = malloc(n);
-		strcpy(s, "function ");
-		strcat(s, F->name);
-		strcat(s, "(");
-		for (i = 0; i < F->numparams; i++) {
-			if (i > 0) strcat(s, ",");
-			strcat(s, F->params[i]);
-		}
-		strcat(s, ") { ... }");
-		js_pushstring(J, s);
-		free(s);
-	} else {
-		js_pushliteral(J, "function () { ... }");
-	}
-
-	return 1;
-}
-
-static int Fp_apply(js_State *J, int argc)
-{
-	int i, n;
-	char name[20];
-
-	if (!js_iscallable(J, 0))
-		js_typeerror(J, "not a function");
-
-	js_copy(J, 0);
-	js_copy(J, 1);
-
-	js_getproperty(J, 2, "length");
-	n = js_tonumber(J, -1);
-	js_pop(J, 1);
-
-	for (i = 0; i < n; ++i) {
-		sprintf(name, "%d", i);
-		js_getproperty(J, 2, name);
-	}
-
-	js_call(J, n);
-	return 1;
-}
-
-static int Fp_call(js_State *J, int argc)
-{
-	int i;
-
-	if (!js_iscallable(J, 0))
-		js_typeerror(J, "not a function");
-
-	js_copy(J, 0);
-	js_copy(J, 1);
-	for (i = 2; i <= argc; ++i)
-		js_copy(J, i);
-
-	js_call(J, argc - 1);
-	return 1;
-}
-
-void jsB_initfunction(js_State *J)
-{
-	J->Function_prototype->u.c.function = jsB_Function_prototype;
-	J->Function_prototype->u.c.constructor = NULL;
-
-	js_pushobject(J, J->Function_prototype);
-	{
-		jsB_propf(J, "toString", Fp_toString, 2);
-		jsB_propf(J, "apply", Fp_apply, 2);
-		jsB_propf(J, "call", Fp_call, 1);
-	}
-	js_newcconstructor(J, jsB_Function, jsB_new_Function);
-	js_setglobal(J, "Function");
-}
--- a/jsbmath.c
+++ /dev/null
@@ -1,111 +1,0 @@
-#include "jsi.h"
-#include "jsvalue.h"
-#include "jsbuiltin.h"
-
-static int Math_abs(js_State *J, int argc) {
-	return js_pushnumber(J, abs(js_tonumber(J, 1))), 1;
-}
-static int Math_acos(js_State *J, int argc) {
-	return js_pushnumber(J, acos(js_tonumber(J, 1))), 1;
-}
-static int Math_asin(js_State *J, int argc) {
-	return js_pushnumber(J, asin(js_tonumber(J, 1))), 1;
-}
-static int Math_atan(js_State *J, int argc) {
-	return js_pushnumber(J, atan(js_tonumber(J, 1))), 1;
-}
-static int Math_atan2(js_State *J, int argc) {
-	return js_pushnumber(J, atan2(js_tonumber(J, 1), js_tonumber(J, 2))), 1;
-}
-static int Math_ceil(js_State *J, int argc) {
-	return js_pushnumber(J, ceil(js_tonumber(J, 1))), 1;
-}
-static int Math_cos(js_State *J, int argc) {
-	return js_pushnumber(J, cos(js_tonumber(J, 1))), 1;
-}
-static int Math_exp(js_State *J, int argc) {
-	return js_pushnumber(J, exp(js_tonumber(J, 1))), 1;
-}
-static int Math_floor(js_State *J, int argc) {
-	return js_pushnumber(J, floor(js_tonumber(J, 1))), 1;
-}
-static int Math_log(js_State *J, int argc) {
-	return js_pushnumber(J, log(js_tonumber(J, 1))), 1;
-}
-static int Math_pow(js_State *J, int argc) {
-	return js_pushnumber(J, pow(js_tonumber(J, 1), js_tonumber(J, 2))), 1;
-}
-static int Math_random(js_State *J, int argc) {
-	return js_pushnumber(J, (double)rand() / (RAND_MAX - 1)), 1;
-}
-static int Math_round(js_State *J, int argc) {
-	return js_pushnumber(J, round(js_tonumber(J, 1))), 1;
-}
-static int Math_sin(js_State *J, int argc) {
-	return js_pushnumber(J, sin(js_tonumber(J, 1))), 1;
-}
-static int Math_sqrt(js_State *J, int argc) {
-	return js_pushnumber(J, sqrt(js_tonumber(J, 1))), 1;
-}
-static int Math_tan(js_State *J, int argc) {
-	return js_pushnumber(J, tan(js_tonumber(J, 1))), 1;
-}
-
-static int Math_max(js_State *J, int argc)
-{
-	double n = js_tonumber(J, 1);
-	int i;
-	for (i = 2; i <= argc; ++i) {
-		double m = js_tonumber(J, i);
-		n = n > m ? n : m;
-	}
-	js_pushnumber(J, n);
-	return 1;
-}
-
-static int Math_min(js_State *J, int argc)
-{
-	double n = js_tonumber(J, 1);
-	int i;
-	for (i = 2; i <= argc; ++i) {
-		double m = js_tonumber(J, i);
-		n = n < m ? n : m;
-	}
-	js_pushnumber(J, n);
-	return 1;
-}
-
-void jsB_initmath(js_State *J)
-{
-	js_pushobject(J, jsV_newobject(J, JS_CMATH, J->Object_prototype));
-	{
-		jsB_propn(J, "E", M_E);
-		jsB_propn(J, "LN10", M_LN10);
-		jsB_propn(J, "LN2", M_LN2);
-		jsB_propn(J, "LOG2E", M_LOG2E);
-		jsB_propn(J, "LOG10E", M_LOG10E);
-		jsB_propn(J, "PI", M_PI);
-		jsB_propn(J, "SQRT1_2", M_SQRT1_2);
-		jsB_propn(J, "SQRT2", M_SQRT2);
-
-		jsB_propf(J, "abs", Math_abs, 1);
-		jsB_propf(J, "acos", Math_acos, 1);
-		jsB_propf(J, "asin", Math_asin, 1);
-		jsB_propf(J, "atan", Math_atan, 1);
-		jsB_propf(J, "atan2", Math_atan2, 2);
-		jsB_propf(J, "ceil", Math_ceil, 1);
-		jsB_propf(J, "cos", Math_cos, 1);
-		jsB_propf(J, "exp", Math_exp, 1);
-		jsB_propf(J, "floor", Math_floor, 1);
-		jsB_propf(J, "log", Math_log, 1);
-		jsB_propf(J, "max", Math_max, 2);
-		jsB_propf(J, "min", Math_min, 2);
-		jsB_propf(J, "pow", Math_pow, 2);
-		jsB_propf(J, "random", Math_random, 0);
-		jsB_propf(J, "round", Math_round, 1);
-		jsB_propf(J, "sin", Math_sin, 1);
-		jsB_propf(J, "sqrt", Math_sqrt, 1);
-		jsB_propf(J, "tan", Math_tan, 1);
-	}
-	js_setglobal(J, "Math");
-}
--- a/jsbnumber.c
+++ /dev/null
@@ -1,88 +1,0 @@
-#include "jsi.h"
-#include "jsvalue.h"
-#include "jsbuiltin.h"
-
-static int jsB_new_Number(js_State *J, int argc)
-{
-	js_newnumber(J, argc > 0 ? js_tonumber(J, 1) : 0);
-	return 1;
-}
-
-static int jsB_Number(js_State *J, int argc)
-{
-	js_pushnumber(J, argc > 0 ? js_tonumber(J, 1) : 0);
-	return 1;
-}
-
-static int Np_valueOf(js_State *J, int argc)
-{
-	js_Object *self = js_toobject(J, 0);
-	if (self->type != JS_CNUMBER) js_typeerror(J, "not a number");
-	js_pushnumber(J, self->u.number);
-	return 1;
-}
-
-static int Np_toString(js_State *J, int argc)
-{
-	js_Object *self = js_toobject(J, 0);
-	if (self->type != JS_CNUMBER) js_typeerror(J, "not a number");
-	js_pushliteral(J, jsV_numbertostring(J, self->u.number));
-	return 1;
-}
-
-static int Np_toFixed(js_State *J, int argc)
-{
-	char buf[40];
-	js_Object *self = js_toobject(J, 0);
-	int width = js_tonumber(J, 1);
-	if (self->type != JS_CNUMBER) js_typeerror(J, "not a number");
-	sprintf(buf, "%.*f", width, self->u.number);
-	js_pushstring(J, buf);
-	return 1;
-}
-
-static int Np_toExponential(js_State *J, int argc)
-{
-	char buf[40];
-	js_Object *self = js_toobject(J, 0);
-	int width = js_tonumber(J, 1);
-	if (self->type != JS_CNUMBER) js_typeerror(J, "not a number");
-	sprintf(buf, "%.*e", width, self->u.number);
-	js_pushstring(J, buf);
-	return 1;
-}
-
-static int Np_toPrecision(js_State *J, int argc)
-{
-	char buf[40];
-	js_Object *self = js_toobject(J, 0);
-	int width = js_tonumber(J, 1);
-	if (self->type != JS_CNUMBER) js_typeerror(J, "not a number");
-	sprintf(buf, "%.*g", width, self->u.number);
-	js_pushstring(J, buf);
-	return 1;
-}
-
-void jsB_initnumber(js_State *J)
-{
-	J->Number_prototype->u.number = 0;
-
-	js_pushobject(J, J->Number_prototype);
-	{
-		jsB_propf(J, "valueOf", Np_valueOf, 0);
-		jsB_propf(J, "toString", Np_toString, 0);
-		jsB_propf(J, "toLocaleString", Np_toString, 0);
-		jsB_propf(J, "toFixed", Np_toFixed, 1);
-		jsB_propf(J, "toExponential", Np_toExponential, 1);
-		jsB_propf(J, "toPrecision", Np_toPrecision, 1);
-	}
-	js_newcconstructor(J, jsB_Number, jsB_new_Number);
-	{
-		jsB_propn(J, "MAX_VALUE", DBL_MAX);
-		jsB_propn(J, "MIN_VALUE", DBL_MIN);
-		jsB_propn(J, "NaN", NAN);
-		jsB_propn(J, "NEGATIVE_INFINITY", -INFINITY);
-		jsB_propn(J, "POSITIVE_INFINITY", INFINITY);
-	}
-	js_setglobal(J, "Number");
-}
--- a/jsbobject.c
+++ /dev/null
@@ -1,98 +1,0 @@
-#include "jsi.h"
-#include "jsvalue.h"
-#include "jsbuiltin.h"
-
-static int jsB_new_Object(js_State *J, int argc)
-{
-	if (argc == 0 || js_isundefined(J, 1) || js_isnull(J, 1))
-		js_newobject(J);
-	else
-		js_pushobject(J, js_toobject(J, 1));
-	return 1;
-}
-
-static int jsB_Object(js_State *J, int argc)
-{
-	if (argc == 0 || js_isundefined(J, 1) || js_isnull(J, 1))
-		js_newobject(J);
-	else
-		js_pushobject(J, js_toobject(J, 1));
-	return 1;
-}
-
-static int Op_toString(js_State *J, int argc)
-{
-	js_Object *self = js_toobject(J, 0);
-	switch (self->type) {
-	case JS_COBJECT: js_pushliteral(J, "[object Object]"); break;
-	case JS_CARRAY: js_pushliteral(J, "[object Array]"); break;
-	case JS_CFUNCTION: js_pushliteral(J, "[object Function]"); break;
-	case JS_CSCRIPT: js_pushliteral(J, "[object Function]"); break;
-	case JS_CCFUNCTION: js_pushliteral(J, "[object Function]"); break;
-	case JS_CERROR: js_pushliteral(J, "[object Error]"); break;
-	case JS_CBOOLEAN: js_pushliteral(J, "[object Boolean]"); break;
-	case JS_CNUMBER: js_pushliteral(J, "[object Number]"); break;
-	case JS_CSTRING: js_pushliteral(J, "[object String]"); break;
-	case JS_CREGEXP: js_pushliteral(J, "[object RegExp]"); break;
-	case JS_CDATE: js_pushliteral(J, "[object Date]"); break;
-	case JS_CMATH: js_pushliteral(J, "[object Math]"); break;
-	default: return 0;
-	}
-	return 1;
-}
-
-static int Op_valueOf(js_State *J, int argc)
-{
-	/* return the 'this' object */
-	return 1;
-}
-
-static int Op_hasOwnProperty(js_State *J, int argc)
-{
-	js_Object *self = js_toobject(J, 0);
-	const char *name = js_tostring(J, 1);
-	js_Property *ref = jsV_getownproperty(J, self, name);
-	js_pushboolean(J, ref != NULL);
-	return 1;
-}
-
-static int Op_isPrototypeOf(js_State *J, int argc)
-{
-	js_Object *self = js_toobject(J, 0);
-	if (js_isobject(J, 1)) {
-		js_Object *V = js_toobject(J, 1);
-		do {
-			V = V->prototype;
-			if (V == self) {
-				js_pushboolean(J, 1);
-				return 1;
-			}
-		} while (V);
-	}
-	js_pushboolean(J, 0);
-	return 1;
-}
-
-static int Op_propertyIsEnumerable(js_State *J, int argc)
-{
-	js_Object *self = js_toobject(J, 0);
-	const char *name = js_tostring(J, 1);
-	js_Property *ref = jsV_getownproperty(J, self, name);
-	js_pushboolean(J, ref && !(ref->atts & JS_DONTENUM));
-	return 1;
-}
-
-void jsB_initobject(js_State *J)
-{
-	js_pushobject(J, J->Object_prototype);
-	{
-		jsB_propf(J, "toString", Op_toString, 0);
-		jsB_propf(J, "toLocaleString", Op_toString, 0);
-		jsB_propf(J, "valueOf", Op_valueOf, 0);
-		jsB_propf(J, "hasOwnProperty", Op_hasOwnProperty, 1);
-		jsB_propf(J, "isPrototypeOf", Op_isPrototypeOf, 1);
-		jsB_propf(J, "propertyIsEnumerable", Op_propertyIsEnumerable, 1);
-	}
-	js_newcconstructor(J, jsB_Object, jsB_new_Object);
-	js_setglobal(J, "Object");
-}
--- /dev/null
+++ b/jsboolean.c
@@ -1,0 +1,44 @@
+#include "jsi.h"
+#include "jsvalue.h"
+#include "jsbuiltin.h"
+
+static int jsB_new_Boolean(js_State *J, int argc)
+{
+	js_newboolean(J, js_toboolean(J, 1));
+	return 1;
+}
+
+static int jsB_Boolean(js_State *J, int argc)
+{
+	js_pushboolean(J, js_toboolean(J, 1));
+	return 1;
+}
+
+static int Bp_toString(js_State *J, int argc)
+{
+	js_Object *self = js_toobject(J, 0);
+	if (self->type != JS_CBOOLEAN) js_typeerror(J, "not a boolean");
+	js_pushliteral(J, self->u.boolean ? "true" : "false");
+	return 1;
+}
+
+static int Bp_valueOf(js_State *J, int argc)
+{
+	js_Object *self = js_toobject(J, 0);
+	if (self->type != JS_CBOOLEAN) js_typeerror(J, "not a boolean");
+	js_pushboolean(J, self->u.boolean);
+	return 1;
+}
+
+void jsB_initboolean(js_State *J)
+{
+	J->Boolean_prototype->u.boolean = 0;
+
+	js_pushobject(J, J->Boolean_prototype);
+	{
+		jsB_propf(J, "toString", Bp_toString, 0);
+		jsB_propf(J, "valueOf", Bp_valueOf, 0);
+	}
+	js_newcconstructor(J, jsB_Boolean, jsB_new_Boolean);
+	js_setglobal(J, "Boolean");
+}
--- a/jsbstring.c
+++ /dev/null
@@ -1,120 +1,0 @@
-#include "jsi.h"
-#include "jsvalue.h"
-#include "jsbuiltin.h"
-#include "jsutf.h"
-
-static int jsB_new_String(js_State *J, int argc)
-{
-	js_newstring(J, argc > 0 ? js_tostring(J, 1) : "");
-	return 1;
-}
-
-static int jsB_String(js_State *J, int argc)
-{
-	js_pushliteral(J, argc > 0 ? js_tostring(J, 1) : "");
-	return 1;
-}
-
-static int Sp_toString(js_State *J, int argc)
-{
-	js_Object *self = js_toobject(J, 0);
-	if (self->type != JS_CSTRING) js_typeerror(J, "not a string");
-	js_pushliteral(J, self->u.string);
-	return 1;
-}
-
-static int Sp_valueOf(js_State *J, int argc)
-{
-	js_Object *self = js_toobject(J, 0);
-	if (self->type != JS_CSTRING) js_typeerror(J, "not a string");
-	js_pushliteral(J, self->u.string);
-	return 1;
-}
-
-static inline Rune runeAt(const char *s, int i)
-{
-	Rune rune = 0;
-	while (i-- >= 0) {
-		rune = *(unsigned char*)s;
-		if (rune < Runeself) {
-			if (rune == 0)
-				return 0;
-			++s;
-		} else
-			s += chartorune(&rune, s);
-	}
-	return rune;
-}
-
-static int Sp_charAt(js_State *J, int argc)
-{
-	char buf[UTFmax + 1];
-	const char *s = js_tostring(J, 0);
-	int pos = js_tointeger(J, 1);
-	Rune rune = runeAt(s, pos);
-	if (rune > 0) {
-		buf[runetochar(buf, &rune)] = 0;
-		js_pushstring(J, buf);
-	} else {
-		js_pushliteral(J, "");
-	}
-	return 1;
-}
-
-static int Sp_charCodeAt(js_State *J, int argc)
-{
-	const char *s = js_tostring(J, 0);
-	int pos = js_tointeger(J, 1);
-	Rune rune = runeAt(s, pos);
-	if (rune > 0)
-		js_pushnumber(J, rune);
-	else
-		js_pushnumber(J, NAN);
-	return 1;
-}
-
-static int S_fromCharCode(js_State *J, int argc)
-{
-	int i;
-	Rune c;
-	char *s = malloc(argc * UTFmax + 1), *p = s;
-	// TODO: guard malloc with try/catch
-	for (i = 0; i <= argc; ++i) {
-		c = js_tointeger(J, i + 1); // TODO: ToUInt16()
-		p += runetochar(p, &c);
-	}
-	*p = 0;
-	js_pushstring(J, s);
-	free(s);
-	return 1;
-}
-
-void jsB_initstring(js_State *J)
-{
-	J->String_prototype->u.string = "";
-
-	js_pushobject(J, J->String_prototype);
-	{
-		jsB_propf(J, "toString", Sp_toString, 0);
-		jsB_propf(J, "valueOf", Sp_valueOf, 0);
-		jsB_propf(J, "charAt", Sp_charAt, 1);
-		jsB_propf(J, "charCodeAt", Sp_charCodeAt, 1);
-		//jsB_propf(J, "concat", Sp_concat, 1);
-		//jsB_propf(J, "indexOf", Sp_indexOf, 1);
-		//jsB_propf(J, "lastIndexOf", Sp_lastIndexOf, 1);
-		//jsB_propf(J, "localeCompare", Sp_localeCompare, 1);
-		//jsB_propf(J, "slice", Sp_slice, 2);
-		// match (uses regexp)
-		// replace (uses regexp)
-		// search (uses regexp)
-		// split (uses regexp)
-		//jsB_propf(J, "substring", Sp_substring, 2);
-		//jsB_propf(J, "toLowerCase", Sp_toLowerCase, 0);
-		//jsB_propf(J, "toUpperCase", Sp_toUpperCase, 0);
-	}
-	js_newcconstructor(J, jsB_String, jsB_new_String);
-	{
-		jsB_propf(J, "fromCharCode", S_fromCharCode, 1);
-	}
-	js_setglobal(J, "String");
-}
--- a/jsbuiltin.c
+++ b/jsbuiltin.c
@@ -1,26 +1,32 @@
 #include "jsi.h"
+#include "jscompile.h"
 #include "jsvalue.h"
 #include "jsbuiltin.h"
 
-static int jsB_print(js_State *J, int argc)
+static void jsB_globalf(js_State *J, const char *name, js_CFunction cfun, int n)
 {
-	int i;
-	for (i = 1; i <= argc; ++i) {
-		const char *s = js_tostring(J, i);
-		if (i > 1) putchar(' ');
-		fputs(s, stdout);
-	}
-	putchar('\n');
-	return 0;
+	js_newcfunction(J, cfun, n);
+	js_setglobal(J, name);
 }
 
-static int jsB_collectGarbage(js_State *J, int argc)
+void jsB_propf(js_State *J, const char *name, js_CFunction cfun, int n)
 {
-	int report = js_toboolean(J, 1);
-	js_gc(J, report);
-	return 0;
+	js_newcfunction(J, cfun, n);
+	js_setproperty(J, -2, name);
 }
 
+void jsB_propn(js_State *J, const char *name, double number)
+{
+	js_pushnumber(J, number);
+	js_setproperty(J, -2, name);
+}
+
+void jsB_props(js_State *J, const char *name, const char *string)
+{
+	js_pushliteral(J, string);
+	js_setproperty(J, -2, name);
+}
+
 static int jsB_eval(js_State *J, int argc)
 {
 	if (!js_isstring(J, -1))
@@ -60,30 +66,25 @@
 	return 1;
 }
 
-static void jsB_globalf(js_State *J, const char *name, js_CFunction cfun, int n)
+static int jsB_print(js_State *J, int argc)
 {
-	js_newcfunction(J, cfun, n);
-	js_setglobal(J, name);
+	int i;
+	for (i = 1; i <= argc; ++i) {
+		const char *s = js_tostring(J, i);
+		if (i > 1) putchar(' ');
+		fputs(s, stdout);
+	}
+	putchar('\n');
+	return 0;
 }
 
-void jsB_propf(js_State *J, const char *name, js_CFunction cfun, int n)
+static int jsB_collectGarbage(js_State *J, int argc)
 {
-	js_newcfunction(J, cfun, n);
-	js_setproperty(J, -2, name);
+	int report = js_toboolean(J, 1);
+	js_gc(J, report);
+	return 0;
 }
 
-void jsB_propn(js_State *J, const char *name, double number)
-{
-	js_pushnumber(J, number);
-	js_setproperty(J, -2, name);
-}
-
-void jsB_props(js_State *J, const char *name, const char *string)
-{
-	js_pushliteral(J, string);
-	js_setproperty(J, -2, name);
-}
-
 void jsB_init(js_State *J)
 {
 	/* Create the prototype objects here, before the constructors */
@@ -111,7 +112,6 @@
 	jsB_initnumber(J);
 	jsB_initstring(J);
 	jsB_initerror(J);
-
 	jsB_initmath(J);
 
 	/* Initialize the global object */
--- a/jserror.c
+++ b/jserror.c
@@ -1,6 +1,30 @@
 #include "jsi.h"
 #include "jsvalue.h"
+#include "jsbuiltin.h"
 
+#define QQ(X) #X
+#define Q(X) QQ(X)
+
+static int Ep_toString(js_State *J, int argc)
+{
+	js_getproperty(J, 0, "name");
+	js_pushliteral(J, ": ");
+	js_concat(J);
+	js_getproperty(J, 0, "message");
+	js_concat(J);
+	return 1;
+}
+
+static int jsB_ErrorX(js_State *J, int argc, js_Object *prototype)
+{
+	js_pushobject(J, jsV_newobject(J, JS_CERROR, prototype));
+	if (argc > 0) {
+		js_pushstring(J, js_tostring(J, 1));
+		js_setproperty(J, -2, "message");
+	}
+	return 1;
+}
+
 static void js_newerrorx(js_State *J, const char *message, js_Object *prototype)
 {
 	js_pushobject(J, jsV_newobject(J, JS_CERROR, prototype));
@@ -8,27 +32,56 @@
 	js_setproperty(J, -2, "message");
 }
 
-void js_newerror(js_State *J, const char *s) { js_newerrorx(J, s, J->Error_prototype); }
-void js_newevalerror(js_State *J, const char *s) { js_newerrorx(J, s, J->EvalError_prototype); }
-void js_newrangeerror(js_State *J, const char *s) { js_newerrorx(J, s, J->RangeError_prototype); }
-void js_newreferenceerror(js_State *J, const char *s) { js_newerrorx(J, s, J->ReferenceError_prototype); }
-void js_newsyntaxerror(js_State *J, const char *s) { js_newerrorx(J, s, J->SyntaxError_prototype); }
-void js_newtypeerror(js_State *J, const char *s) { js_newerrorx(J, s, J->TypeError_prototype); }
-void js_newurierror(js_State *J, const char *s) { js_newerrorx(J, s, J->URIError_prototype); }
+#define DERROR(name, Name) \
+	static int jsB_##Name(js_State *J, int n) { \
+		return jsB_ErrorX(J, n, J->Name##_prototype); \
+	} \
+	void js_new##name(js_State *J, const char *s) { \
+		js_newerrorx(J, s, J->Name##_prototype); \
+	} \
+	void js_##name(js_State *J, const char *fmt, ...) { \
+		va_list ap; \
+		char buf[256]; \
+		va_start(ap, fmt); \
+		vsnprintf(buf, sizeof buf, fmt, ap); \
+		va_end(ap); \
+		js_newerrorx(J, buf, J->Name##_prototype); \
+		js_throw(J); \
+	}
 
-#define ERR(NAME) \
-	va_list ap; \
-	char buf[256]; \
-	va_start(ap, fmt); \
-	vsnprintf(buf, sizeof buf, fmt, ap); \
-	va_end(ap); \
-	js_newerrorx(J, buf, J->NAME##_prototype); \
-	js_throw(J)
+DERROR(error, Error)
+DERROR(evalerror, EvalError)
+DERROR(rangeerror, RangeError)
+DERROR(referenceerror, ReferenceError)
+DERROR(syntaxerror, SyntaxError)
+DERROR(typeerror, TypeError)
+DERROR(urierror, URIError)
 
-void js_error(js_State *J, const char *fmt, ...) { ERR(Error); }
-void js_evalerror(js_State *J, const char *fmt, ...) { ERR(EvalError); }
-void js_rangeerror(js_State *J, const char *fmt, ...) { ERR(RangeError); }
-void js_referenceerror(js_State *J, const char *fmt, ...) { ERR(ReferenceError); }
-void js_syntaxerror(js_State *J, const char *fmt, ...) { ERR(SyntaxError); }
-void js_typeerror(js_State *J, const char *fmt, ...) { ERR(TypeError); }
-void js_urierror(js_State *J, const char *fmt, ...) { ERR(URIError); }
+#undef DERROR
+
+void jsB_initerror(js_State *J)
+{
+	js_pushobject(J, J->Error_prototype);
+	{
+			jsB_props(J, "name", "Error");
+			jsB_props(J, "message", "an error has occurred");
+			jsB_propf(J, "toString", Ep_toString, 0);
+	}
+	js_newcconstructor(J, jsB_Error, jsB_Error);
+	js_setglobal(J, "Error");
+
+	#define IERROR(NAME) \
+		js_pushobject(J, J->NAME##_prototype); \
+		jsB_props(J, "name", Q(NAME)); \
+		js_newcconstructor(J, jsB_##NAME, jsB_##NAME); \
+		js_setglobal(J, Q(NAME));
+
+	IERROR(EvalError);
+	IERROR(RangeError);
+	IERROR(ReferenceError);
+	IERROR(SyntaxError);
+	IERROR(TypeError);
+	IERROR(URIError);
+
+	#undef IERROR
+}
--- /dev/null
+++ b/jsfunction.c
@@ -1,0 +1,103 @@
+#include "jsi.h"
+#include "jscompile.h"
+#include "jsvalue.h"
+#include "jsbuiltin.h"
+
+static int jsB_Function(js_State *J, int argc)
+{
+	return 0;
+}
+
+static int jsB_Function_prototype(js_State *J, int argc)
+{
+	js_pushundefined(J);
+	return 1;
+}
+
+static int Fp_toString(js_State *J, int argc)
+{
+	js_Object *self = js_toobject(J, 0);
+	char *s;
+	int i, n;
+
+	if (!js_iscallable(J, 0))
+		js_typeerror(J, "not a function");
+
+	if (self->type == JS_CFUNCTION || self->type == JS_CSCRIPT) {
+		js_Function *F = self->u.f.function;
+		n = strlen("function () { ... }");
+		n += strlen(F->name);
+		for (i = 0; i < F->numparams; i++)
+			n += strlen(F->params[i]) + 1;
+		s = malloc(n);
+		strcpy(s, "function ");
+		strcat(s, F->name);
+		strcat(s, "(");
+		for (i = 0; i < F->numparams; i++) {
+			if (i > 0) strcat(s, ",");
+			strcat(s, F->params[i]);
+		}
+		strcat(s, ") { ... }");
+		js_pushstring(J, s);
+		free(s);
+	} else {
+		js_pushliteral(J, "function () { ... }");
+	}
+
+	return 1;
+}
+
+static int Fp_apply(js_State *J, int argc)
+{
+	int i, n;
+	char name[20];
+
+	if (!js_iscallable(J, 0))
+		js_typeerror(J, "not a function");
+
+	js_copy(J, 0);
+	js_copy(J, 1);
+
+	js_getproperty(J, 2, "length");
+	n = js_tonumber(J, -1);
+	js_pop(J, 1);
+
+	for (i = 0; i < n; ++i) {
+		sprintf(name, "%d", i);
+		js_getproperty(J, 2, name);
+	}
+
+	js_call(J, n);
+	return 1;
+}
+
+static int Fp_call(js_State *J, int argc)
+{
+	int i;
+
+	if (!js_iscallable(J, 0))
+		js_typeerror(J, "not a function");
+
+	js_copy(J, 0);
+	js_copy(J, 1);
+	for (i = 2; i <= argc; ++i)
+		js_copy(J, i);
+
+	js_call(J, argc - 1);
+	return 1;
+}
+
+void jsB_initfunction(js_State *J)
+{
+	J->Function_prototype->u.c.function = jsB_Function_prototype;
+	J->Function_prototype->u.c.constructor = NULL;
+
+	js_pushobject(J, J->Function_prototype);
+	{
+		jsB_propf(J, "toString", Fp_toString, 2);
+		jsB_propf(J, "apply", Fp_apply, 2);
+		jsB_propf(J, "call", Fp_call, 1);
+	}
+	js_newcconstructor(J, jsB_Function, jsB_Function);
+	js_setglobal(J, "Function");
+}
--- a/jsi.h
+++ b/jsi.h
@@ -12,21 +12,22 @@
 #include <math.h>
 #include <float.h>
 
-enum { JS_REGEXP_G = 1, JS_REGEXP_I = 2, JS_REGEXP_M = 4 }; /* RegExp flags */
-
-enum { JS_HNONE, JS_HNUMBER, JS_HSTRING }; /* Hint to ToPrimitive() */
-
 typedef struct js_Value js_Value;
 typedef struct js_Object js_Object;
-
 typedef struct js_Ast js_Ast;
 typedef struct js_Function js_Function;
 typedef struct js_Environment js_Environment;
-
 typedef struct js_StringNode js_StringNode;
+typedef struct js_Jumpbuf js_Jumpbuf;
+
+/* String interning */
+
+const char *js_intern(js_State *J, const char *s);
 void jsS_dumpstrings(js_State *J);
 void jsS_freestrings(js_State *J);
 
+/* Private stack functions */
+
 void js_newfunction(js_State *J, js_Function *function, js_Environment *scope);
 void js_newscript(js_State *J, js_Function *function);
 
@@ -36,8 +37,6 @@
 void js_rot3(js_State *J);
 
 /* Exception handling */
-
-typedef struct js_Jumpbuf js_Jumpbuf;
 
 struct js_Jumpbuf
 {
--- /dev/null
+++ b/jsmath.c
@@ -1,0 +1,111 @@
+#include "jsi.h"
+#include "jsvalue.h"
+#include "jsbuiltin.h"
+
+static int Math_abs(js_State *J, int argc) {
+	return js_pushnumber(J, abs(js_tonumber(J, 1))), 1;
+}
+static int Math_acos(js_State *J, int argc) {
+	return js_pushnumber(J, acos(js_tonumber(J, 1))), 1;
+}
+static int Math_asin(js_State *J, int argc) {
+	return js_pushnumber(J, asin(js_tonumber(J, 1))), 1;
+}
+static int Math_atan(js_State *J, int argc) {
+	return js_pushnumber(J, atan(js_tonumber(J, 1))), 1;
+}
+static int Math_atan2(js_State *J, int argc) {
+	return js_pushnumber(J, atan2(js_tonumber(J, 1), js_tonumber(J, 2))), 1;
+}
+static int Math_ceil(js_State *J, int argc) {
+	return js_pushnumber(J, ceil(js_tonumber(J, 1))), 1;
+}
+static int Math_cos(js_State *J, int argc) {
+	return js_pushnumber(J, cos(js_tonumber(J, 1))), 1;
+}
+static int Math_exp(js_State *J, int argc) {
+	return js_pushnumber(J, exp(js_tonumber(J, 1))), 1;
+}
+static int Math_floor(js_State *J, int argc) {
+	return js_pushnumber(J, floor(js_tonumber(J, 1))), 1;
+}
+static int Math_log(js_State *J, int argc) {
+	return js_pushnumber(J, log(js_tonumber(J, 1))), 1;
+}
+static int Math_pow(js_State *J, int argc) {
+	return js_pushnumber(J, pow(js_tonumber(J, 1), js_tonumber(J, 2))), 1;
+}
+static int Math_random(js_State *J, int argc) {
+	return js_pushnumber(J, (double)rand() / (RAND_MAX - 1)), 1;
+}
+static int Math_round(js_State *J, int argc) {
+	return js_pushnumber(J, round(js_tonumber(J, 1))), 1;
+}
+static int Math_sin(js_State *J, int argc) {
+	return js_pushnumber(J, sin(js_tonumber(J, 1))), 1;
+}
+static int Math_sqrt(js_State *J, int argc) {
+	return js_pushnumber(J, sqrt(js_tonumber(J, 1))), 1;
+}
+static int Math_tan(js_State *J, int argc) {
+	return js_pushnumber(J, tan(js_tonumber(J, 1))), 1;
+}
+
+static int Math_max(js_State *J, int argc)
+{
+	double n = js_tonumber(J, 1);
+	int i;
+	for (i = 2; i <= argc; ++i) {
+		double m = js_tonumber(J, i);
+		n = n > m ? n : m;
+	}
+	js_pushnumber(J, n);
+	return 1;
+}
+
+static int Math_min(js_State *J, int argc)
+{
+	double n = js_tonumber(J, 1);
+	int i;
+	for (i = 2; i <= argc; ++i) {
+		double m = js_tonumber(J, i);
+		n = n < m ? n : m;
+	}
+	js_pushnumber(J, n);
+	return 1;
+}
+
+void jsB_initmath(js_State *J)
+{
+	js_pushobject(J, jsV_newobject(J, JS_CMATH, J->Object_prototype));
+	{
+		jsB_propn(J, "E", M_E);
+		jsB_propn(J, "LN10", M_LN10);
+		jsB_propn(J, "LN2", M_LN2);
+		jsB_propn(J, "LOG2E", M_LOG2E);
+		jsB_propn(J, "LOG10E", M_LOG10E);
+		jsB_propn(J, "PI", M_PI);
+		jsB_propn(J, "SQRT1_2", M_SQRT1_2);
+		jsB_propn(J, "SQRT2", M_SQRT2);
+
+		jsB_propf(J, "abs", Math_abs, 1);
+		jsB_propf(J, "acos", Math_acos, 1);
+		jsB_propf(J, "asin", Math_asin, 1);
+		jsB_propf(J, "atan", Math_atan, 1);
+		jsB_propf(J, "atan2", Math_atan2, 2);
+		jsB_propf(J, "ceil", Math_ceil, 1);
+		jsB_propf(J, "cos", Math_cos, 1);
+		jsB_propf(J, "exp", Math_exp, 1);
+		jsB_propf(J, "floor", Math_floor, 1);
+		jsB_propf(J, "log", Math_log, 1);
+		jsB_propf(J, "max", Math_max, 2);
+		jsB_propf(J, "min", Math_min, 2);
+		jsB_propf(J, "pow", Math_pow, 2);
+		jsB_propf(J, "random", Math_random, 0);
+		jsB_propf(J, "round", Math_round, 1);
+		jsB_propf(J, "sin", Math_sin, 1);
+		jsB_propf(J, "sqrt", Math_sqrt, 1);
+		jsB_propf(J, "tan", Math_tan, 1);
+	}
+	js_setglobal(J, "Math");
+}
--- /dev/null
+++ b/jsnumber.c
@@ -1,0 +1,88 @@
+#include "jsi.h"
+#include "jsvalue.h"
+#include "jsbuiltin.h"
+
+static int jsB_new_Number(js_State *J, int argc)
+{
+	js_newnumber(J, argc > 0 ? js_tonumber(J, 1) : 0);
+	return 1;
+}
+
+static int jsB_Number(js_State *J, int argc)
+{
+	js_pushnumber(J, argc > 0 ? js_tonumber(J, 1) : 0);
+	return 1;
+}
+
+static int Np_valueOf(js_State *J, int argc)
+{
+	js_Object *self = js_toobject(J, 0);
+	if (self->type != JS_CNUMBER) js_typeerror(J, "not a number");
+	js_pushnumber(J, self->u.number);
+	return 1;
+}
+
+static int Np_toString(js_State *J, int argc)
+{
+	js_Object *self = js_toobject(J, 0);
+	if (self->type != JS_CNUMBER) js_typeerror(J, "not a number");
+	js_pushliteral(J, jsV_numbertostring(J, self->u.number));
+	return 1;
+}
+
+static int Np_toFixed(js_State *J, int argc)
+{
+	char buf[40];
+	js_Object *self = js_toobject(J, 0);
+	int width = js_tonumber(J, 1);
+	if (self->type != JS_CNUMBER) js_typeerror(J, "not a number");
+	sprintf(buf, "%.*f", width, self->u.number);
+	js_pushstring(J, buf);
+	return 1;
+}
+
+static int Np_toExponential(js_State *J, int argc)
+{
+	char buf[40];
+	js_Object *self = js_toobject(J, 0);
+	int width = js_tonumber(J, 1);
+	if (self->type != JS_CNUMBER) js_typeerror(J, "not a number");
+	sprintf(buf, "%.*e", width, self->u.number);
+	js_pushstring(J, buf);
+	return 1;
+}
+
+static int Np_toPrecision(js_State *J, int argc)
+{
+	char buf[40];
+	js_Object *self = js_toobject(J, 0);
+	int width = js_tonumber(J, 1);
+	if (self->type != JS_CNUMBER) js_typeerror(J, "not a number");
+	sprintf(buf, "%.*g", width, self->u.number);
+	js_pushstring(J, buf);
+	return 1;
+}
+
+void jsB_initnumber(js_State *J)
+{
+	J->Number_prototype->u.number = 0;
+
+	js_pushobject(J, J->Number_prototype);
+	{
+		jsB_propf(J, "valueOf", Np_valueOf, 0);
+		jsB_propf(J, "toString", Np_toString, 0);
+		jsB_propf(J, "toLocaleString", Np_toString, 0);
+		jsB_propf(J, "toFixed", Np_toFixed, 1);
+		jsB_propf(J, "toExponential", Np_toExponential, 1);
+		jsB_propf(J, "toPrecision", Np_toPrecision, 1);
+	}
+	js_newcconstructor(J, jsB_Number, jsB_new_Number);
+	{
+		jsB_propn(J, "MAX_VALUE", DBL_MAX);
+		jsB_propn(J, "MIN_VALUE", DBL_MIN);
+		jsB_propn(J, "NaN", NAN);
+		jsB_propn(J, "NEGATIVE_INFINITY", -INFINITY);
+		jsB_propn(J, "POSITIVE_INFINITY", INFINITY);
+	}
+	js_setglobal(J, "Number");
+}
--- /dev/null
+++ b/jsobject.c
@@ -1,0 +1,98 @@
+#include "jsi.h"
+#include "jsvalue.h"
+#include "jsbuiltin.h"
+
+static int jsB_new_Object(js_State *J, int argc)
+{
+	if (argc == 0 || js_isundefined(J, 1) || js_isnull(J, 1))
+		js_newobject(J);
+	else
+		js_pushobject(J, js_toobject(J, 1));
+	return 1;
+}
+
+static int jsB_Object(js_State *J, int argc)
+{
+	if (argc == 0 || js_isundefined(J, 1) || js_isnull(J, 1))
+		js_newobject(J);
+	else
+		js_pushobject(J, js_toobject(J, 1));
+	return 1;
+}
+
+static int Op_toString(js_State *J, int argc)
+{
+	js_Object *self = js_toobject(J, 0);
+	switch (self->type) {
+	case JS_COBJECT: js_pushliteral(J, "[object Object]"); break;
+	case JS_CARRAY: js_pushliteral(J, "[object Array]"); break;
+	case JS_CFUNCTION: js_pushliteral(J, "[object Function]"); break;
+	case JS_CSCRIPT: js_pushliteral(J, "[object Function]"); break;
+	case JS_CCFUNCTION: js_pushliteral(J, "[object Function]"); break;
+	case JS_CERROR: js_pushliteral(J, "[object Error]"); break;
+	case JS_CBOOLEAN: js_pushliteral(J, "[object Boolean]"); break;
+	case JS_CNUMBER: js_pushliteral(J, "[object Number]"); break;
+	case JS_CSTRING: js_pushliteral(J, "[object String]"); break;
+	case JS_CREGEXP: js_pushliteral(J, "[object RegExp]"); break;
+	case JS_CDATE: js_pushliteral(J, "[object Date]"); break;
+	case JS_CMATH: js_pushliteral(J, "[object Math]"); break;
+	default: return 0;
+	}
+	return 1;
+}
+
+static int Op_valueOf(js_State *J, int argc)
+{
+	/* return the 'this' object */
+	return 1;
+}
+
+static int Op_hasOwnProperty(js_State *J, int argc)
+{
+	js_Object *self = js_toobject(J, 0);
+	const char *name = js_tostring(J, 1);
+	js_Property *ref = jsV_getownproperty(J, self, name);
+	js_pushboolean(J, ref != NULL);
+	return 1;
+}
+
+static int Op_isPrototypeOf(js_State *J, int argc)
+{
+	js_Object *self = js_toobject(J, 0);
+	if (js_isobject(J, 1)) {
+		js_Object *V = js_toobject(J, 1);
+		do {
+			V = V->prototype;
+			if (V == self) {
+				js_pushboolean(J, 1);
+				return 1;
+			}
+		} while (V);
+	}
+	js_pushboolean(J, 0);
+	return 1;
+}
+
+static int Op_propertyIsEnumerable(js_State *J, int argc)
+{
+	js_Object *self = js_toobject(J, 0);
+	const char *name = js_tostring(J, 1);
+	js_Property *ref = jsV_getownproperty(J, self, name);
+	js_pushboolean(J, ref && !(ref->atts & JS_DONTENUM));
+	return 1;
+}
+
+void jsB_initobject(js_State *J)
+{
+	js_pushobject(J, J->Object_prototype);
+	{
+		jsB_propf(J, "toString", Op_toString, 0);
+		jsB_propf(J, "toLocaleString", Op_toString, 0);
+		jsB_propf(J, "valueOf", Op_valueOf, 0);
+		jsB_propf(J, "hasOwnProperty", Op_hasOwnProperty, 1);
+		jsB_propf(J, "isPrototypeOf", Op_isPrototypeOf, 1);
+		jsB_propf(J, "propertyIsEnumerable", Op_propertyIsEnumerable, 1);
+	}
+	js_newcconstructor(J, jsB_Object, jsB_new_Object);
+	js_setglobal(J, "Object");
+}
--- a/jsoptim.c
+++ /dev/null
@@ -1,78 +1,0 @@
-#include "jsi.h"
-#include "jsparse.h"
-
-static inline int i32(double d)
-{
-	double two32 = 4294967296.0;
-	double two31 = 2147483648.0;
-
-	if (!isfinite(d) || d == 0)
-		return 0;
-
-	d = fmod(d, two32);
-	d = d >= 0 ? floor(d) : ceil(d) + two32;
-	if (d >= two31)
-		return d - two32;
-	else
-		return d;
-}
-
-static inline unsigned int u32(double d)
-{
-	return i32(d);
-}
-
-static int N(js_Ast *node, double x)
-{
-	node->type = AST_NUMBER;
-	node->number = x;
-	node->a = node->b = node->c = node->d = NULL;
-	return 1;
-}
-
-static int foldnumber(js_Ast *node)
-{
-	double x, y;
-	int a, b;
-
-	if (node->type == AST_NUMBER)
-		return 1;
-
-	a = node->a ? foldnumber(node->a) : 0;
-	b = node->b ? foldnumber(node->b) : 0;
-	if (node->c) foldnumber(node->c);
-	if (node->d) foldnumber(node->d);
-
-	if (a) {
-		x = node->a->number;
-		switch (node->type) {
-		case EXP_NEG: return N(node, -x);
-		case EXP_POS: return N(node, x);
-		case EXP_BITNOT: return N(node, ~i32(x));
-		}
-
-		if (b) {
-			y = node->b->number;
-			switch (node->type) {
-			case EXP_MUL: return N(node, x * y);
-			case EXP_DIV: return N(node, x / y);
-			case EXP_MOD: return N(node, fmod(x, y));
-			case EXP_ADD: return N(node, x + y);
-			case EXP_SUB: return N(node, x - y);
-			case EXP_SHL: return N(node, i32(x) << (u32(y) & 0x1F));
-			case EXP_SHR: return N(node, i32(x) >> (u32(y) & 0x1F));
-			case EXP_USHR: return N(node, u32(x) >> (u32(y) & 0x1F));
-			case EXP_BITAND: return N(node, i32(x) & i32(y));
-			case EXP_BITXOR: return N(node, i32(x) ^ i32(y));
-			case EXP_BITOR: return N(node, i32(x) | i32(y));
-			}
-		}
-	}
-
-	return 0;
-}
-
-void jsP_optimize(js_State *J, js_Ast *prog)
-{
-	foldnumber(prog);
-}
--- a/jsparse.c
+++ b/jsparse.c
@@ -54,7 +54,7 @@
 	fprintf(stderr, "\n");
 }
 
-js_Ast *jsP_newnode(js_State *J, int type, js_Ast *a, js_Ast *b, js_Ast *c, js_Ast *d)
+static js_Ast *jsP_newnode(js_State *J, int type, js_Ast *a, js_Ast *b, js_Ast *c, js_Ast *d)
 {
 	js_Ast *node = malloc(sizeof(js_Ast));
 
@@ -73,7 +73,7 @@
 	return node;
 }
 
-js_Ast *jsP_newstrnode(js_State *J, int type, const char *s)
+static js_Ast *jsP_newstrnode(js_State *J, int type, const char *s)
 {
 	js_Ast *node = jsP_newnode(J, type, 0, 0, 0, 0);
 	node->string = s;
@@ -80,7 +80,7 @@
 	return node;
 }
 
-js_Ast *jsP_newnumnode(js_State *J, int type, double n)
+static js_Ast *jsP_newnumnode(js_State *J, int type, double n)
 {
 	js_Ast *node = jsP_newnode(J, type, 0, 0, 0, 0);
 	node->number = n;
@@ -866,6 +866,81 @@
 	return a;
 }
 
+/* Constant folding */
+
+static inline int toint32(double d)
+{
+	double two32 = 4294967296.0;
+	double two31 = 2147483648.0;
+
+	if (!isfinite(d) || d == 0)
+		return 0;
+
+	d = fmod(d, two32);
+	d = d >= 0 ? floor(d) : ceil(d) + two32;
+	if (d >= two31)
+		return d - two32;
+	else
+		return d;
+}
+
+static inline unsigned int touint32(double d)
+{
+	return toint32(d);
+}
+
+static int jsP_setnumnode(js_Ast *node, double x)
+{
+	node->type = AST_NUMBER;
+	node->number = x;
+	node->a = node->b = node->c = node->d = NULL;
+	return 1;
+}
+
+static int jsP_foldconst(js_Ast *node)
+{
+	double x, y;
+	int a, b;
+
+	if (node->type == AST_NUMBER)
+		return 1;
+
+	a = node->a ? jsP_foldconst(node->a) : 0;
+	b = node->b ? jsP_foldconst(node->b) : 0;
+	if (node->c) jsP_foldconst(node->c);
+	if (node->d) jsP_foldconst(node->d);
+
+	if (a) {
+		x = node->a->number;
+		switch (node->type) {
+		case EXP_NEG: return jsP_setnumnode(node, -x);
+		case EXP_POS: return jsP_setnumnode(node, x);
+		case EXP_BITNOT: return jsP_setnumnode(node, ~toint32(x));
+		}
+
+		if (b) {
+			y = node->b->number;
+			switch (node->type) {
+			case EXP_MUL: return jsP_setnumnode(node, x * y);
+			case EXP_DIV: return jsP_setnumnode(node, x / y);
+			case EXP_MOD: return jsP_setnumnode(node, fmod(x, y));
+			case EXP_ADD: return jsP_setnumnode(node, x + y);
+			case EXP_SUB: return jsP_setnumnode(node, x - y);
+			case EXP_SHL: return jsP_setnumnode(node, toint32(x) << (touint32(y) & 0x1F));
+			case EXP_SHR: return jsP_setnumnode(node, toint32(x) >> (touint32(y) & 0x1F));
+			case EXP_USHR: return jsP_setnumnode(node, touint32(x) >> (touint32(y) & 0x1F));
+			case EXP_BITAND: return jsP_setnumnode(node, toint32(x) & toint32(y));
+			case EXP_BITXOR: return jsP_setnumnode(node, toint32(x) ^ toint32(y));
+			case EXP_BITOR: return jsP_setnumnode(node, toint32(x) | toint32(y));
+			}
+		}
+	}
+
+	return 0;
+}
+
+/* Main entry point */
+
 js_Ast *jsP_parse(js_State *J, const char *filename, const char *source)
 {
 	js_Ast *p, *last;
@@ -874,6 +949,8 @@
 
 	next(J);
 	p = script(J);
+
+	jsP_foldconst(p);
 
 	/* patch up global and eval code to return value of last expression */
 	last = p;
--- a/jsparse.h
+++ b/jsparse.h
@@ -125,7 +125,6 @@
 };
 
 js_Ast *jsP_parse(js_State *J, const char *filename, const char *source);
-void jsP_optimize(js_State *J, js_Ast *prog);
 void jsP_freeparse(js_State *J);
 
 const char *jsP_aststring(js_AstType type);
--- a/jsstate.c
+++ b/jsstate.c
@@ -16,7 +16,6 @@
 	}
 
 	P = jsP_parse(J, filename, source);
-	jsP_optimize(J, P);
 	F = jsC_compile(J, P);
 	jsP_freeparse(J);
 	js_newscript(J, F);
--- /dev/null
+++ b/jsstring.c
@@ -1,0 +1,120 @@
+#include "jsi.h"
+#include "jsvalue.h"
+#include "jsbuiltin.h"
+#include "jsutf.h"
+
+static int jsB_new_String(js_State *J, int argc)
+{
+	js_newstring(J, argc > 0 ? js_tostring(J, 1) : "");
+	return 1;
+}
+
+static int jsB_String(js_State *J, int argc)
+{
+	js_pushliteral(J, argc > 0 ? js_tostring(J, 1) : "");
+	return 1;
+}
+
+static int Sp_toString(js_State *J, int argc)
+{
+	js_Object *self = js_toobject(J, 0);
+	if (self->type != JS_CSTRING) js_typeerror(J, "not a string");
+	js_pushliteral(J, self->u.string);
+	return 1;
+}
+
+static int Sp_valueOf(js_State *J, int argc)
+{
+	js_Object *self = js_toobject(J, 0);
+	if (self->type != JS_CSTRING) js_typeerror(J, "not a string");
+	js_pushliteral(J, self->u.string);
+	return 1;
+}
+
+static inline Rune runeAt(const char *s, int i)
+{
+	Rune rune = 0;
+	while (i-- >= 0) {
+		rune = *(unsigned char*)s;
+		if (rune < Runeself) {
+			if (rune == 0)
+				return 0;
+			++s;
+		} else
+			s += chartorune(&rune, s);
+	}
+	return rune;
+}
+
+static int Sp_charAt(js_State *J, int argc)
+{
+	char buf[UTFmax + 1];
+	const char *s = js_tostring(J, 0);
+	int pos = js_tointeger(J, 1);
+	Rune rune = runeAt(s, pos);
+	if (rune > 0) {
+		buf[runetochar(buf, &rune)] = 0;
+		js_pushstring(J, buf);
+	} else {
+		js_pushliteral(J, "");
+	}
+	return 1;
+}
+
+static int Sp_charCodeAt(js_State *J, int argc)
+{
+	const char *s = js_tostring(J, 0);
+	int pos = js_tointeger(J, 1);
+	Rune rune = runeAt(s, pos);
+	if (rune > 0)
+		js_pushnumber(J, rune);
+	else
+		js_pushnumber(J, NAN);
+	return 1;
+}
+
+static int S_fromCharCode(js_State *J, int argc)
+{
+	int i;
+	Rune c;
+	char *s = malloc(argc * UTFmax + 1), *p = s;
+	// TODO: guard malloc with try/catch
+	for (i = 0; i <= argc; ++i) {
+		c = js_tointeger(J, i + 1); // TODO: ToUInt16()
+		p += runetochar(p, &c);
+	}
+	*p = 0;
+	js_pushstring(J, s);
+	free(s);
+	return 1;
+}
+
+void jsB_initstring(js_State *J)
+{
+	J->String_prototype->u.string = "";
+
+	js_pushobject(J, J->String_prototype);
+	{
+		jsB_propf(J, "toString", Sp_toString, 0);
+		jsB_propf(J, "valueOf", Sp_valueOf, 0);
+		jsB_propf(J, "charAt", Sp_charAt, 1);
+		jsB_propf(J, "charCodeAt", Sp_charCodeAt, 1);
+		//jsB_propf(J, "concat", Sp_concat, 1);
+		//jsB_propf(J, "indexOf", Sp_indexOf, 1);
+		//jsB_propf(J, "lastIndexOf", Sp_lastIndexOf, 1);
+		//jsB_propf(J, "localeCompare", Sp_localeCompare, 1);
+		//jsB_propf(J, "slice", Sp_slice, 2);
+		// match (uses regexp)
+		// replace (uses regexp)
+		// search (uses regexp)
+		// split (uses regexp)
+		//jsB_propf(J, "substring", Sp_substring, 2);
+		//jsB_propf(J, "toLowerCase", Sp_toLowerCase, 0);
+		//jsB_propf(J, "toUpperCase", Sp_toUpperCase, 0);
+	}
+	js_newcconstructor(J, jsB_String, jsB_new_String);
+	{
+		jsB_propf(J, "fromCharCode", S_fromCharCode, 1);
+	}
+	js_setglobal(J, "String");
+}