shithub: libmujs

Download patch

ref: 288eef80e136441fe00bc618b523139cb6bb5bca
parent: 3715d6d145225f957de9f40d3154e0ef458b94e6
author: Tor Andersson <tor@ccxvii.net>
date: Thu Jan 16 10:54:27 EST 2014

Clean up. Rearrange files. Rename functions.

--- a/js.h
+++ b/js.h
@@ -44,38 +44,16 @@
 int js_loadstring(js_State *J, const char *s);
 int js_loadfile(js_State *J, const char *filename);
 
-/* binding API */
+/* binding API: TODO: move from jsrun.h */
 
 typedef int (*js_CFunction)(js_State *J, int argc);
-typedef struct js_Object js_Object;
 
-void js_pushundefined(js_State *J);
-void js_pushnull(js_State *J);
-void js_pushboolean(js_State *J, int v);
-void js_pushnumber(js_State *J, double v);
-void js_pushlstring(js_State *J, const char *v);
-void js_pushstring(js_State *J, const char *v);
-void js_pushobject(js_State *J, js_Object *v);
-
-int js_isundefined(js_State *J, int idx);
-int js_isstring(js_State *J, int idx);
-
-int js_toboolean(js_State *J, int idx);
-double js_tonumber(js_State *J, int idx);
-double js_tointeger(js_State *J, int idx);
-const char *js_tostring(js_State *J, int idx);
-js_Object *js_toobject(js_State *J, int idx);
-
-void js_pop(js_State *J, int n);
-void js_dup(js_State *J);
-
-void js_setglobal(js_State *J, const char *name);
-
 /* private */
 
 typedef struct js_Ast js_Ast;
 typedef struct js_Environment js_Environment;
 typedef struct js_Function js_Function;
+typedef struct js_Object js_Object;
 typedef struct js_StringNode js_StringNode;
 
 const char *js_intern(js_State *J, const char *s);
--- a/jsdump.c
+++ b/jsdump.c
@@ -655,3 +655,36 @@
 		}
 	}
 }
+
+/* Runtime values */
+
+void js_dumpvalue(js_State *J, js_Value v)
+{
+	switch (v.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_TSTRING: printf("'%s'", v.u.string); break;
+	case JS_TOBJECT: printf("<object %p>", v.u.object); break;
+	}
+}
+
+static void js_dumpproperty(js_State *J, js_Property *node)
+{
+	if (node->left->level)
+		js_dumpproperty(J, node->left);
+	printf("\t%s: ", node->name);
+	js_dumpvalue(J, node->value);
+	printf(",\n");
+	if (node->right->level)
+		js_dumpproperty(J, node->right);
+}
+
+void js_dumpobject(js_State *J, js_Object *obj)
+{
+	printf("{\n");
+	if (obj->properties->level)
+		js_dumpproperty(J, obj->properties);
+	printf("}\n");
+}
--- a/jslex.c
+++ b/jslex.c
@@ -1,6 +1,6 @@
 #include "js.h"
-#include "jsstate.h"
 #include "jslex.h"
+#include "jsstate.h"
 
 #define nelem(a) (sizeof (a) / sizeof (a)[0])
 
--- a/jslex.h
+++ b/jslex.h
@@ -1,7 +1,8 @@
 #ifndef js_lex_h
 #define js_lex_h
 
-enum {
+enum
+{
 	TK_IDENTIFIER = 256,
 	TK_NUMBER,
 	TK_STRING,
@@ -65,9 +66,11 @@
 	TK_WITH,
 };
 
+const char *jsP_tokenstring(int token);
+
 void jsP_initlex(js_State *J, const char *filename, const char *source);
 int jsP_lex(js_State *J);
-const char *jsP_tokenstring(int token);
+
 JS_NORETURN int jsP_error(js_State *J, const char *fmt, ...);
 void jsP_warning(js_State *J, const char *fmt, ...);
 
--- a/jsload.c
+++ b/jsload.c
@@ -1,6 +1,7 @@
 #include "js.h"
 #include "jsparse.h"
 #include "jscompile.h"
+#include "jsobject.h"
 #include "jsrun.h"
 
 static int jsP_loadstring(js_State *J, const char *filename, const char *source)
--- a/jsobject.c
+++ b/jsobject.c
@@ -1,248 +1,38 @@
 #include "js.h"
 #include "jsobject.h"
 
-/*
-	Use an AA-tree to quickly look up properties in objects:
-
-	The level of every leaf node is one.
-	The level of every left child is one less than its parent.
-	The level of every right child is equal or one less than its parent.
-	The level of every right grandchild is less than its grandparent.
-	Every node of level greater than one has two children.
-
-	A link where the child's level is equal to that of its parent is called a horizontal link.
-	Individual right horizontal links are allowed, but consecutive ones are forbidden.
-	Left horizontal links are forbidden.
-
-	skew() fixes left horizontal links.
-	split() fixes consecutive right horizontal links.
-*/
-
-static js_Property sentinel = { "", &sentinel, &sentinel, 0 };
-
-static js_Property *newproperty(const char *name)
+js_Object *jsR_newfunction(js_State *J, js_Function *function, js_Environment *scope)
 {
-	js_Property *node = malloc(sizeof(js_Property));
-	node->name = strdup(name);
-	node->left = node->right = &sentinel;
-	node->level = 1;
-	node->value.type = JS_TUNDEFINED;
-	node->value.u.number = 0;
-	node->flags = 0;
-	return node;
-}
-
-static js_Property *lookup(js_Property *node, const char *name)
-{
-	while (node != &sentinel) {
-		int c = strcmp(name, node->name);
-		if (c == 0)
-			return node;
-		else if (c < 0)
-			node = node->left;
-		else
-			node = node->right;
-	}
-	return NULL;
-}
-
-static inline js_Property *skew(js_Property *node)
-{
-	if (node->level != 0) {
-		if (node->left->level == node->level) {
-			js_Property *save = node;
-			node = node->left;
-			save->left = node->right;
-			node->right = save;
-		}
-		node->right = skew(node->right);
-	}
-	return node;
-}
-
-static inline js_Property *split(js_Property *node)
-{
-	if (node->level != 0 && node->right->right->level == node->level) {
-		js_Property *save = node;
-		node = node->right;
-		save->right = node->left;
-		node->left = save;
-		node->level++;
-		node->right = split(node->right);
-	}
-	return node;
-}
-
-static js_Property *insert(js_Property *node, const char *name, js_Property **result)
-{
-	if (node != &sentinel) {
-		int c = strcmp(name, node->name);
-		if (c < 0)
-			node->left = insert(node->left, name, result);
-		else if (c > 0)
-			node->right = insert(node->right, name, result);
-		else
-			return *result = node;
-		node = skew(node);
-		node = split(node);
-		return node;
-	}
-	return *result = newproperty(name);
-}
-
-static js_Property *lookupfirst(js_Property *node)
-{
-	while (node != &sentinel) {
-		if (node->left == &sentinel)
-			return node;
-		node = node->left;
-	}
-	return NULL;
-}
-
-static js_Property *lookupnext(js_Property *node, const char *name)
-{
-	js_Property *stack[100], *parent;
-	int top = 0;
-
-	stack[0] = NULL;
-	while (node != &sentinel) {
-		stack[++top] = node;
-		int c = strcmp(name, node->name);
-		if (c == 0)
-			goto found;
-		else if (c < 0)
-			node = node->left;
-		else
-			node = node->right;
-	}
-	return NULL;
-
-found:
-	if (node->right != &sentinel)
-		return lookupfirst(node->right);
-	parent = stack[--top];
-	while (parent && node == parent->right) {
-		node = parent;
-		parent = stack[--top];
-	}
-	return parent;
-}
-
-js_Object *js_newobject(js_State *J, js_Class type)
-{
-	js_Object *obj = malloc(sizeof(js_Object));
-	obj->type = type;
-	obj->properties = &sentinel;
-	obj->prototype = NULL;
-	obj->primitive.number = 0;
-	obj->scope = NULL;
-	obj->function = NULL;
-	obj->cfunction = NULL;
-	return obj;
-}
-
-js_Object *js_newfunction(js_State *J, js_Function *function, js_Environment *scope)
-{
-	js_Object *obj = js_newobject(J, JS_CFUNCTION);
+	js_Object *obj = jsR_newobject(J, JS_CFUNCTION);
 	obj->function = function;
 	obj->scope = scope;
 	return obj;
 }
 
-js_Object *js_newcfunction(js_State *J, js_CFunction cfunction)
+js_Object *jsR_newcfunction(js_State *J, js_CFunction cfunction)
 {
-	js_Object *obj = js_newobject(J, JS_CCFUNCTION);
+	js_Object *obj = jsR_newobject(J, JS_CCFUNCTION);
 	obj->cfunction = cfunction;
 	return obj;
 }
 
-js_Environment *js_newenvironment(js_State *J, js_Environment *outer, js_Object *vars)
+js_Object *jsR_newboolean(js_State *J, int v)
 {
-	js_Environment *E = malloc(sizeof *E);
-	E->outer = outer;
-	E->variables = vars;
-	return E;
+	js_Object *obj = jsR_newobject(J, JS_CBOOLEAN);
+	obj->primitive.boolean = v;
+	return obj;
 }
 
-js_Property *js_decvar(js_State *J, js_Environment *E, const char *name)
+js_Object *jsR_newnumber(js_State *J, double v)
 {
-	return js_setproperty(J, E->variables, name);
+	js_Object *obj = jsR_newobject(J, JS_CNUMBER);
+	obj->primitive.number = v;
+	return obj;
 }
 
-js_Property *js_getvar(js_State *J, js_Environment *E, const char *name)
+js_Object *jsR_newstring(js_State *J, const char *v)
 {
-	while (E) {
-		js_Property *ref = js_getproperty(J, E->variables, name);
-		if (ref)
-			return ref;
-		E = E->outer;
-	}
-	return NULL;
-}
-
-js_Property *js_setvar(js_State *J, js_Environment *E, const char *name)
-{
-	while (1) {
-		js_Property *ref = js_getproperty(J, E->variables, name);
-		if (ref)
-			return ref;
-		if (!E->outer)
-			break;
-		E = E->outer;
-	}
-	return js_setproperty(J, E->variables, name);
-}
-
-js_Property *js_getproperty(js_State *J, js_Object *obj, const char *name)
-{
-	return lookup(obj->properties, name);
-}
-
-js_Property *js_setproperty(js_State *J, js_Object *obj, const char *name)
-{
-	js_Property *result;
-	obj->properties = insert(obj->properties, name, &result);
-	return result;
-}
-
-js_Property *js_firstproperty(js_State *J, js_Object *obj)
-{
-	return lookupfirst(obj->properties);
-}
-
-js_Property *js_nextproperty(js_State *J, js_Object *obj, const char *name)
-{
-	return lookupnext(obj->properties, name);
-}
-
-void js_dumpvalue(js_State *J, js_Value v)
-{
-	switch (v.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_TSTRING: printf("'%s'", v.u.string); break;
-	case JS_TOBJECT: printf("<object %p>", v.u.object); break;
-	}
-}
-
-static void js_dumpproperty(js_State *J, js_Property *node)
-{
-	if (node->left != &sentinel)
-		js_dumpproperty(J, node->left);
-	printf("\t%s: ", node->name);
-	js_dumpvalue(J, node->value);
-	printf(",\n");
-	if (node->right != &sentinel)
-		js_dumpproperty(J, node->right);
-}
-
-void js_dumpobject(js_State *J, js_Object *obj)
-{
-	printf("{\n");
-	if (obj->properties != &sentinel)
-		js_dumpproperty(J, obj->properties);
-	printf("}\n");
+	js_Object *obj = jsR_newobject(J, JS_CSTRING);
+	obj->primitive.string = v;
+	return obj;
 }
--- a/jsobject.h
+++ b/jsobject.h
@@ -7,12 +7,6 @@
 typedef enum js_Class js_Class;
 typedef struct js_Property js_Property;
 
-struct js_Environment
-{
-	js_Environment *outer;
-	js_Object *variables;
-};
-
 enum js_Type {
 	JS_TUNDEFINED,
 	JS_TNULL,
@@ -22,32 +16,6 @@
 	JS_TOBJECT,
 };
 
-struct js_Value
-{
-	js_Type type;
-	union {
-		int boolean;
-		double number;
-		const char *string;
-		js_Object *object;
-	} u;
-};
-
-enum {
-	JS_PWRITABLE = 1,
-	JS_PENUMERABLE = 2,
-	JS_PCONFIGURABLE = 4,
-};
-
-struct js_Property
-{
-	char *name;
-	js_Property *left, *right;
-	int level;
-	js_Value value;
-	int flags;
-};
-
 enum js_Class {
 	JS_CARRAY,
 	JS_CBOOLEAN,
@@ -62,6 +30,23 @@
 	JS_CSTRING,
 };
 
+enum {
+	JS_PWRITABLE = 1,
+	JS_PENUMERABLE = 2,
+	JS_PCONFIGURABLE = 4,
+};
+
+struct js_Value
+{
+	js_Type type;
+	union {
+		int boolean;
+		double number;
+		const char *string;
+		js_Object *object;
+	} u;
+};
+
 struct js_Object
 {
 	js_Class type;
@@ -77,23 +62,41 @@
 	js_CFunction cfunction;
 };
 
-js_Object *js_newobject(js_State *J, js_Class type);
-js_Object *js_newfunction(js_State *J, js_Function *function, js_Environment *scope);
-js_Object *js_newcfunction(js_State *J, js_CFunction cfunction);
+struct js_Property
+{
+	char *name;
+	js_Property *left, *right;
+	int level;
+	js_Value value;
+	int flags;
+};
 
-js_Environment *js_newenvironment(js_State *J, js_Environment *outer, js_Object *vars);
-js_Property *js_decvar(js_State *J, js_Environment *E, const char *name);
-js_Property *js_getvar(js_State *J, js_Environment *E, const char *name);
-js_Property *js_setvar(js_State *J, js_Environment *E, const char *name);
+/* jsvalue.c */
+int jsR_toboolean(js_State *J, const js_Value *v);
+double jsR_tonumber(js_State *J, const js_Value *v);
+const char *jsR_tostring(js_State *J, const js_Value *v);
+js_Object *jsR_toobject(js_State *J, const js_Value *v);
 
-js_Property *js_getproperty(js_State *J, js_Object *obj, const char *name);
-js_Property *js_setproperty(js_State *J, js_Object *obj, const char *name);
-void js_deleteproperty(js_State *J, js_Object *obj, const char *name);
+/* jsproperty.c */
+js_Object *jsR_newobject(js_State *J, js_Class type);
+js_Property *jsR_getproperty(js_State *J, js_Object *obj, const char *name);
+js_Property *jsR_setproperty(js_State *J, js_Object *obj, const char *name);
+js_Property *jsR_nextproperty(js_State *J, js_Object *obj, const char *name);
 
-js_Property *js_firstproperty(js_State *J, js_Object *obj);
-js_Property *js_nextproperty(js_State *J, js_Object *obj, const char *name);
+/* jsobject.c */
+js_Object *jsR_newfunction(js_State *J, js_Function *function, js_Environment *scope);
+js_Object *jsR_newcfunction(js_State *J, js_CFunction cfunction);
+js_Object *jsR_newboolean(js_State *J, int v);
+js_Object *jsR_newnumber(js_State *J, double v);
+js_Object *jsR_newstring(js_State *J, const char *v);
 
+/* jsrun.c */
+void jsR_pushobject(js_State *J, js_Object *v);
+js_Object *js_toobject(js_State *J, int idx);
+
 void js_dumpobject(js_State *J, js_Object *obj);
 void js_dumpvalue(js_State *J, js_Value v);
+
+JS_NORETURN void jsR_error(js_State *J, const char *fmt, ...);
 
 #endif
--- a/jsparse.c
+++ b/jsparse.c
@@ -1,7 +1,7 @@
 #include "js.h"
-#include "jsstate.h"
 #include "jslex.h"
 #include "jsparse.h"
+#include "jsstate.h"
 
 #define nelem(a) (sizeof (a) / sizeof (a)[0])
 
--- a/jsparse.h
+++ b/jsparse.h
@@ -1,16 +1,6 @@
 #ifndef js_parse_h
 #define js_parse_h
 
-struct js_Ast
-{
-	int type;
-	int line;
-	js_Ast *a, *b, *c, *d;
-	double number;
-	const char *string;
-	js_Ast *next; /* next in alloc list */
-};
-
 enum
 {
 	AST_LIST,
@@ -123,10 +113,19 @@
 	STM_DEFAULT,
 };
 
+struct js_Ast
+{
+	int type;
+	int line;
+	js_Ast *a, *b, *c, *d;
+	double number;
+	const char *string;
+	js_Ast *next; /* next in alloc list */
+};
 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);
 
-void jsP_optimize(js_State *J, js_Ast *prog);
 void jsP_dumpsyntax(js_State *J, js_Ast *prog);
 void jsP_dumplist(js_State *J, js_Ast *prog);
 
--- /dev/null
+++ b/jsproperty.c
@@ -1,0 +1,162 @@
+#include "js.h"
+#include "jsobject.h"
+
+/*
+	Use an AA-tree to quickly look up properties in objects:
+
+	The level of every leaf node is one.
+	The level of every left child is one less than its parent.
+	The level of every right child is equal or one less than its parent.
+	The level of every right grandchild is less than its grandparent.
+	Every node of level greater than one has two children.
+
+	A link where the child's level is equal to that of its parent is called a horizontal link.
+	Individual right horizontal links are allowed, but consecutive ones are forbidden.
+	Left horizontal links are forbidden.
+
+	skew() fixes left horizontal links.
+	split() fixes consecutive right horizontal links.
+*/
+
+static js_Property sentinel = { "", &sentinel, &sentinel, 0 };
+
+static js_Property *newproperty(const char *name)
+{
+	js_Property *node = malloc(sizeof(js_Property));
+	node->name = strdup(name);
+	node->left = node->right = &sentinel;
+	node->level = 1;
+	node->value.type = JS_TUNDEFINED;
+	node->value.u.number = 0;
+	node->flags = 0;
+	return node;
+}
+
+static js_Property *lookup(js_Property *node, const char *name)
+{
+	while (node != &sentinel) {
+		int c = strcmp(name, node->name);
+		if (c == 0)
+			return node;
+		else if (c < 0)
+			node = node->left;
+		else
+			node = node->right;
+	}
+	return NULL;
+}
+
+static inline js_Property *skew(js_Property *node)
+{
+	if (node->level != 0) {
+		if (node->left->level == node->level) {
+			js_Property *save = node;
+			node = node->left;
+			save->left = node->right;
+			node->right = save;
+		}
+		node->right = skew(node->right);
+	}
+	return node;
+}
+
+static inline js_Property *split(js_Property *node)
+{
+	if (node->level != 0 && node->right->right->level == node->level) {
+		js_Property *save = node;
+		node = node->right;
+		save->right = node->left;
+		node->left = save;
+		node->level++;
+		node->right = split(node->right);
+	}
+	return node;
+}
+
+static js_Property *insert(js_Property *node, const char *name, js_Property **result)
+{
+	if (node != &sentinel) {
+		int c = strcmp(name, node->name);
+		if (c < 0)
+			node->left = insert(node->left, name, result);
+		else if (c > 0)
+			node->right = insert(node->right, name, result);
+		else
+			return *result = node;
+		node = skew(node);
+		node = split(node);
+		return node;
+	}
+	return *result = newproperty(name);
+}
+
+static js_Property *lookupfirst(js_Property *node)
+{
+	while (node != &sentinel) {
+		if (node->left == &sentinel)
+			return node;
+		node = node->left;
+	}
+	return NULL;
+}
+
+static js_Property *lookupnext(js_Property *node, const char *name)
+{
+	js_Property *stack[100], *parent;
+	int top = 0;
+
+	stack[0] = NULL;
+	while (node != &sentinel) {
+		stack[++top] = node;
+		int c = strcmp(name, node->name);
+		if (c == 0)
+			goto found;
+		else if (c < 0)
+			node = node->left;
+		else
+			node = node->right;
+	}
+	return NULL;
+
+found:
+	if (node->right != &sentinel)
+		return lookupfirst(node->right);
+	parent = stack[--top];
+	while (parent && node == parent->right) {
+		node = parent;
+		parent = stack[--top];
+	}
+	return parent;
+}
+
+js_Object *jsR_newobject(js_State *J, js_Class type)
+{
+	js_Object *obj = malloc(sizeof(js_Object));
+	obj->type = type;
+	obj->properties = &sentinel;
+	obj->prototype = NULL;
+	obj->primitive.number = 0;
+	obj->scope = NULL;
+	obj->function = NULL;
+	obj->cfunction = NULL;
+	return obj;
+}
+
+js_Property *jsR_getproperty(js_State *J, js_Object *obj, const char *name)
+{
+	return lookup(obj->properties, name);
+}
+
+js_Property *jsR_setproperty(js_State *J, js_Object *obj, const char *name)
+{
+	js_Property *result;
+	obj->properties = insert(obj->properties, name, &result);
+	return result;
+}
+
+js_Property *jsR_nextproperty(js_State *J, js_Object *obj, const char *name)
+{
+	if (!name)
+		return lookupfirst(obj->properties);
+	return lookupnext(obj->properties, name);
+}
--- a/jsrun.c
+++ b/jsrun.c
@@ -81,17 +81,17 @@
 	++top;
 }
 
-void js_pushlstring(js_State *J, const char *v)
+void js_pushstring(js_State *J, const char *v)
 {
 	stack[top].type = JS_TSTRING;
-	stack[top].u.string = v;
+	stack[top].u.string = js_intern(J, v);
 	++top;
 }
 
-void js_pushstring(js_State *J, const char *v)
+void jsR_pushliteral(js_State *J, const char *v)
 {
 	stack[top].type = JS_TSTRING;
-	stack[top].u.string = js_intern(J, v);
+	stack[top].u.string = v;
 	++top;
 }
 
@@ -102,6 +102,21 @@
 	++top;
 }
 
+void js_newobject(js_State *J)
+{
+	js_pushobject(J, jsR_newobject(J, JS_COBJECT));
+}
+
+void js_newarray(js_State *J)
+{
+	js_pushobject(J, jsR_newobject(J, JS_CARRAY));
+}
+
+void js_pushcfunction(js_State *J, js_CFunction v)
+{
+	js_pushobject(J, jsR_newcfunction(J, v));
+}
+
 static int stackidx(js_State *J, int idx)
 {
 	if (idx < 0)
@@ -129,66 +144,26 @@
 
 int js_toboolean(js_State *J, int idx)
 {
-	const char *s;
-	double n;
 	idx = stackidx(J, idx);
-	switch (stack[idx].type) {
-	case JS_TUNDEFINED: return 0;
-	case JS_TNULL: return 0;
-	case JS_TBOOLEAN: return stack[idx].u.boolean;
-	case JS_TNUMBER: n = stack[idx].u.number; return n != 0 || !isnan(n);
-	case JS_TSTRING: s = stack[idx].u.string; return s[0] != 0;
-	case JS_TOBJECT: return 0;
-	}
-	return 0;
+	return jsR_toboolean(J, &stack[idx]);
 }
 
 double js_tonumber(js_State *J, int idx)
 {
 	idx = stackidx(J, idx);
-	switch (stack[idx].type) {
-	case JS_TUNDEFINED: return NAN;
-	case JS_TNULL: return 0;
-	case JS_TBOOLEAN: return stack[idx].u.boolean;
-	case JS_TNUMBER: return stack[idx].u.number;
-	case JS_TSTRING: return strtod(stack[idx].u.string, NULL);
-	case JS_TOBJECT: return 0;
-	}
-	return 0;
+	return jsR_tonumber(J, &stack[idx]);
 }
 
-double js_tointeger(js_State *J, int idx)
-{
-	return toint32(js_tonumber(J, idx));
-}
-
 const char *js_tostring(js_State *J, int idx)
 {
-	char buf[20];
 	idx = stackidx(J, idx);
-	switch (stack[idx].type) {
-	case JS_TUNDEFINED: return "undefined";
-	case JS_TNULL: return "null";
-	case JS_TBOOLEAN: return stack[idx].u.boolean ? "true" : "false";
-	case JS_TNUMBER: sprintf(buf, "%.9g", stack[idx].u.number); return js_intern(J, buf);
-	case JS_TSTRING: return stack[idx].u.string;
-	case JS_TOBJECT: return "<object>";
-	}
-	return NULL;
+	return jsR_tostring(J, &stack[idx]);
 }
 
 js_Object *js_toobject(js_State *J, int idx)
 {
 	idx = stackidx(J, idx);
-	switch (stack[idx].type) {
-	case JS_TUNDEFINED: jsR_error(J, "TypeError (undefined)");
-	case JS_TNULL: jsR_error(J, "TypeError (null)");
-	case JS_TBOOLEAN: jsR_error(J, "new Boolean()");
-	case JS_TNUMBER: jsR_error(J, "new Number()");
-	case JS_TSTRING: jsR_error(J, "new String()");
-	case JS_TOBJECT: return stack[idx].u.object;
-	}
-	return NULL;
+	return jsR_toobject(J, &stack[idx]);
 }
 
 void js_pop(js_State *J, int n)
@@ -277,11 +252,11 @@
 		case OP_NUMBER_1: js_pushnumber(J, 1); break;
 		case OP_NUMBER_X: js_pushnumber(J, *pc++); break;
 		case OP_NUMBER: js_pushnumber(J, NT[*pc++]); break;
-		case OP_STRING: js_pushlstring(J, ST[*pc++]); break;
+		case OP_STRING: jsR_pushliteral(J, ST[*pc++]); break;
 
-		case OP_CLOSURE: js_pushobject(J, js_newfunction(J, FT[*pc++], E)); break;
-		case OP_NEWOBJECT: js_pushobject(J, js_newobject(J, JS_COBJECT)); break;
-		case OP_NEWARRAY: js_pushobject(J, js_newobject(J, JS_CARRAY)); break;
+		case OP_CLOSURE: js_pushobject(J, jsR_newfunction(J, FT[*pc++], E)); break;
+		case OP_NEWOBJECT: js_newobject(J); break;
+		case OP_NEWARRAY: js_newarray(J); break;
 
 		case OP_UNDEF: js_pushundefined(J); break;
 		case OP_NULL: js_pushnull(J); break;
@@ -321,7 +296,7 @@
 		case OP_IN:
 			str = js_tostring(J, -2);
 			obj = js_toobject(J, -1);
-			ref = js_getproperty(J, obj, str);
+			ref = jsR_getproperty(J, obj, str);
 			js_pop(J, 2);
 			js_pushboolean(J, ref != NULL);
 			break;
@@ -329,7 +304,7 @@
 		case OP_GETPROP:
 			obj = js_toobject(J, -2);
 			str = js_tostring(J, -1);
-			ref = js_getproperty(J, obj, str);
+			ref = jsR_getproperty(J, obj, str);
 			js_pop(J, 2);
 			if (ref)
 				js_pushvalue(J, ref->value);
@@ -340,7 +315,7 @@
 		case OP_SETPROP:
 			obj = js_toobject(J, -3);
 			str = js_tostring(J, -2);
-			ref = js_setproperty(J, obj, str);
+			ref = jsR_setproperty(J, obj, str);
 			if (ref)
 				ref->value = js_tovalue(J, -1);
 			js_rot3pop2(J);
@@ -351,12 +326,12 @@
 		case OP_NEXTPROP:
 			obj = js_toobject(J, -2);
 			if (js_isundefined(J, -1))
-				ref = js_firstproperty(J, obj);
+				ref = jsR_nextproperty(J, obj, NULL);
 			else
-				ref = js_nextproperty(J, obj, js_tostring(J, -1));
+				ref = jsR_nextproperty(J, obj, js_tostring(J, -1));
 			if (ref) {
 				js_pop(J, 1);
-				js_pushlstring(J, ref->name);
+				jsR_pushliteral(J, ref->name);
 				js_pushboolean(J, 1);
 			} else {
 				js_pop(J, 2);
@@ -568,7 +543,7 @@
 
 	F = obj->function;
 	if (F) {
-		E = js_newenvironment(J, obj->scope, js_newobject(J, JS_COBJECT));
+		E = jsR_newenvironment(J, jsR_newobject(J, JS_COBJECT), obj->scope);
 
 		for (i = 0; i < F->numparams; ++i) {
 			ref = js_decvar(J, E, F->params[i]);
@@ -594,13 +569,50 @@
 	bot = savebot;
 }
 
+js_Environment *jsR_newenvironment(js_State *J, js_Object *vars, js_Environment *outer)
+{
+	js_Environment *E = malloc(sizeof *E);
+	E->outer = outer;
+	E->variables = vars;
+	return E;
+}
+
 void js_setglobal(js_State *J, const char *name)
 {
-	js_Property *ref = js_setproperty(J, J->E->variables, name);
+	js_Property *ref = jsR_setproperty(J, J->G, name);
 	ref->value = js_tovalue(J, -1);
 	js_pop(J, 1);
 }
 
+js_Property *js_decvar(js_State *J, js_Environment *E, const char *name)
+{
+	return jsR_setproperty(J, E->variables, name);
+}
+
+js_Property *js_getvar(js_State *J, js_Environment *E, const char *name)
+{
+	while (E) {
+		js_Property *ref = jsR_getproperty(J, E->variables, name);
+		if (ref)
+			return ref;
+		E = E->outer;
+	}
+	return NULL;
+}
+
+js_Property *js_setvar(js_State *J, js_Environment *E, const char *name)
+{
+	while (1) {
+		js_Property *ref = jsR_getproperty(J, E->variables, name);
+		if (ref)
+			return ref;
+		if (!E->outer)
+			break;
+		E = E->outer;
+	}
+	return jsR_setproperty(J, E->variables, name);
+}
+
 void jsR_error(js_State *J, const char *fmt, ...)
 {
 	va_list ap;
@@ -617,12 +629,12 @@
 void jsR_runfunction(js_State *J, js_Function *F)
 {
 	if (setjmp(J->jb)) {
-		js_dumpobject(J, J->E->variables);
+		js_dumpobject(J, J->G);
 		return;
 	}
 
-	runfun(J, F, J->E);
+	runfun(J, F, J->GE);
 
-	js_dumpobject(J, J->E->variables);
+	js_dumpobject(J, J->G);
 	js_dumpstack(J);
 }
--- a/jsrun.h
+++ b/jsrun.h
@@ -1,7 +1,36 @@
 #ifndef js_run_h
 #define js_run_h
 
-void jsR_error(js_State *J, const char *fmt, ...);
+struct js_Environment
+{
+	js_Environment *outer;
+	js_Object *variables;
+};
+
+void js_setglobal(js_State *J, const char *name);
+
+js_Environment *jsR_newenvironment(js_State *J, js_Object *variables, js_Environment *outer);
+js_Property *js_decvar(js_State *J, js_Environment *E, const char *name);
+js_Property *js_getvar(js_State *J, js_Environment *E, const char *name);
+js_Property *js_setvar(js_State *J, js_Environment *E, const char *name);
+
 void jsR_runfunction(js_State *J, js_Function *F);
+
+void js_pushundefined(js_State *J);
+void js_pushnull(js_State *J);
+void js_pushboolean(js_State *J, int v);
+void js_pushnumber(js_State *J, double v);
+void js_pushstring(js_State *J, const char *v);
+void js_newobject(js_State *J);
+void js_newarray(js_State *J);
+void js_pushcfunction(js_State *J, js_CFunction v);
+int js_isundefined(js_State *J, int idx);
+int js_isstring(js_State *J, int idx);
+int js_toboolean(js_State *J, int idx);
+double js_tonumber(js_State *J, int idx);
+double js_tointeger(js_State *J, int idx);
+const char *js_tostring(js_State *J, int idx);
+void js_pop(js_State *J, int n);
+void js_dup(js_State *J);
 
 #endif
--- a/jsstate.c
+++ b/jsstate.c
@@ -1,5 +1,6 @@
 #include "js.h"
 #include "jsobject.h"
+#include "jsrun.h"
 #include "jsstate.h"
 
 static int jsB_print(js_State *J, int argc)
@@ -19,10 +20,10 @@
 	js_State *J = malloc(sizeof *J);
 	memset(J, 0, sizeof(*J));
 
-	J->global = js_newobject(J, JS_COBJECT);
-	J->E = js_newenvironment(J, NULL, J->global);
+	J->G = jsR_newobject(J, JS_COBJECT);
+	J->GE = jsR_newenvironment(J, J->G, NULL);
 
-	js_pushobject(J, js_newcfunction(J, jsB_print));
+	js_pushcfunction(J, jsB_print);
 	js_setglobal(J, "print");
 
 	return J;
--- a/jsstate.h
+++ b/jsstate.h
@@ -26,11 +26,12 @@
 	/* compiler */
 	js_Function *fun; /* list of allocated functions to free on errors */
 
+	int strict;
+
 	/* runtime */
-	js_Environment *E;
-	js_Object *global;
+	js_Object *G;
+	js_Environment *GE;
 
-	int strict;
 };
 
 #endif
--- /dev/null
+++ b/jsvalue.c
@@ -1,0 +1,102 @@
+#include "js.h"
+#include "jsobject.h"
+
+enum {
+	JS_HNONE,
+	JS_HNUMBER,
+	JS_HSTRING,
+};
+
+static js_Value jsR_toprimitive(js_State *J, const js_Value *v, int preferred)
+{
+	js_Object *obj = v->u.object;
+
+	if (preferred == JS_HNONE)
+		preferred = obj->type == JS_CDATE ? JS_HSTRING : JS_HNUMBER;
+
+	if (preferred == JS_HSTRING) {
+		// try "toString"
+		// if result is primitive, return result
+		// try "valueOf"
+		// if result is primitive, return result
+	} else {
+		// try "toString"
+		// if result is primitive, return result
+		// try "valueOf"
+		// if result is primitive, return result
+	}
+	jsR_error(J, "TypeError (ToPrimitive)");
+}
+
+int jsR_toboolean(js_State *J, const js_Value *v)
+{
+	switch (v->type) {
+	case JS_TUNDEFINED: return 0;
+	case JS_TNULL: return 0;
+	case JS_TBOOLEAN: return v->u.boolean;
+	case JS_TNUMBER: return v->u.number != 0 && !isnan(v->u.number);
+	case JS_TSTRING: return v->u.string[0] != 0;
+	case JS_TOBJECT: return 0;
+	}
+	return 0;
+}
+
+double jsR_tonumber(js_State *J, const js_Value *v)
+{
+	switch (v->type) {
+	case JS_TUNDEFINED: return NAN;
+	case JS_TNULL: return 0;
+	case JS_TBOOLEAN: return v->u.boolean;
+	case JS_TNUMBER: return v->u.number;
+	case JS_TSTRING:
+		{
+			/* TODO: use lexer to parse string grammar */
+			return strtod(v->u.string, NULL);
+		}
+	case JS_TOBJECT:
+		{
+			js_Value vv = jsR_toprimitive(J, v, JS_HNUMBER);
+			return jsR_tonumber(J, &vv);
+		}
+	}
+	return 0;
+}
+
+const char *jsR_tostring(js_State *J, const js_Value *v)
+{
+	switch (v->type) {
+	case JS_TUNDEFINED: return "undefined";
+	case JS_TNULL: return "null";
+	case JS_TBOOLEAN: return v->u.boolean ? "true" : "false";
+	case JS_TNUMBER:
+		{
+			char buf[32];
+			double n = v->u.number;
+			if (isnan(n)) return "NaN";
+			if (isinf(n)) return n < 0 ? "-Infinity" : "Infinity";
+			if (n == 0) return "0";
+			sprintf(buf, "%.17g", n); /* DBL_DECIMAL_DIG == 17 */
+			return js_intern(J, buf);
+		}
+	case JS_TSTRING: return v->u.string;
+	case JS_TOBJECT:
+		{
+			js_Value vv = jsR_toprimitive(J, v, JS_HSTRING);
+			return jsR_tostring(J, &vv);
+		}
+	}
+	return NULL;
+}
+
+js_Object *jsR_toobject(js_State *J, const js_Value *v)
+{
+	switch (v->type) {
+	case JS_TUNDEFINED: jsR_error(J, "TypeError (ToObject(undefined))");
+	case JS_TNULL: jsR_error(J, "TypeError (ToObject(null))");
+	case JS_TBOOLEAN: return jsR_newboolean(J, v->u.boolean);
+	case JS_TNUMBER: return jsR_newnumber(J, v->u.number);
+	case JS_TSTRING: return jsR_newstring(J, v->u.string);
+	case JS_TOBJECT: return v->u.object;
+	}
+	return NULL;
+}