shithub: libmujs

Download patch

ref: 8342452b13a5df602dbb85ecc6602e8eaa4f0206
parent: 8c21bb0411c9180520f65a113a9237c4235c6d2a
author: Tor Andersson <[email protected]>
date: Fri Jan 10 06:37:33 EST 2014

Rename files and put stuff in the correct headers.

--- a/js-ast.c
+++ /dev/null
@@ -1,554 +1,0 @@
-#include "js.h"
-#include "js-ast.h"
-
-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));
-
-	node->type = type;
-	node->line = J->line;
-	node->a = a;
-	node->b = b;
-	node->c = c;
-	node->d = d;
-	node->n = 0;
-	node->s = NULL;
-
-	node->next = J->ast;
-	J->ast = node;
-
-	return node;
-}
-
-js_Ast *jsP_newstrnode(js_State *J, int type, const char *s)
-{
-	js_Ast *node = jsP_newnode(J, type, 0, 0, 0, 0);
-	node->s = s;
-	return node;
-}
-
-js_Ast *jsP_newnumnode(js_State *J, int type, double n)
-{
-	js_Ast *node = jsP_newnode(J, type, 0, 0, 0, 0);
-	node->n = n;
-	return node;
-}
-
-void jsP_freeast(js_State *J)
-{
-	js_Ast *node = J->ast;
-	while (node) {
-		js_Ast *next = node->next;
-		free(node);
-		node = next;
-	}
-	J->ast = NULL;
-}
-
-static const char *strast(int type)
-{
-	switch (type) {
-	case AST_LIST: return "LIST";
-	case AST_IDENTIFIER: return "IDENTIFIER";
-	case AST_NUMBER: return "NUMBER";
-	case AST_STRING: return "STRING";
-	case AST_REGEXP: return "REGEXP";
-	case EXP_VAR: return "VAR";
-	case EXP_NULL: return "NULL";
-	case EXP_TRUE: return "TRUE";
-	case EXP_FALSE: return "FALSE";
-	case EXP_THIS: return "THIS";
-	case EXP_ARRAY: return "ARRAY";
-	case EXP_OBJECT: return "OBJECT";
-	case EXP_PROP_VAL: return "PROP_VAL";
-	case EXP_PROP_GET: return "PROP_GET";
-	case EXP_PROP_SET: return "PROP_SET";
-	case EXP_INDEX: return "INDEX";
-	case EXP_MEMBER: return "MEMBER";
-	case EXP_NEW: return "new";
-	case EXP_CALL: return "CALL";
-	case EXP_FUNC: return "function";
-	case EXP_COND: return "?:";
-	case EXP_COMMA: return ",";
-	case EXP_DELETE: return "delete";
-	case EXP_VOID: return "void";
-	case EXP_TYPEOF: return "typeof";
-	case EXP_PREINC: return "PRE++";
-	case EXP_PREDEC: return "PRE--";
-	case EXP_POSTINC: return "POST++";
-	case EXP_POSTDEC: return "POST--";
-	case EXP_POS: return "+";
-	case EXP_NEG: return "-";
-	case EXP_BITNOT: return "~";
-	case EXP_LOGNOT: return "!";
-	case EXP_LOGOR: return "||";
-	case EXP_LOGAND: return "&&";
-	case EXP_BITOR: return "|";
-	case EXP_BITXOR: return "^";
-	case EXP_BITAND: return "&";
-	case EXP_EQ: return "==";
-	case EXP_NE: return "!=";
-	case EXP_EQ3: return "===";
-	case EXP_NE3: return "!==";
-	case EXP_LT: return "<";
-	case EXP_GT: return ">";
-	case EXP_LE: return "<=";
-	case EXP_GE: return ">=";
-	case EXP_INSTANCEOF: return "instanceof";
-	case EXP_IN: return "in";
-	case EXP_SHL: return "<<";
-	case EXP_SHR: return ">>";
-	case EXP_USHR: return ">>>";
-	case EXP_ADD: return "+";
-	case EXP_SUB: return "-";
-	case EXP_MUL: return "*";
-	case EXP_DIV: return "/";
-	case EXP_MOD: return "%";
-	case EXP_ASS: return "=";
-	case EXP_ASS_MUL: return "*=";
-	case EXP_ASS_DIV: return "/=";
-	case EXP_ASS_MOD: return "%=";
-	case EXP_ASS_ADD: return "+=";
-	case EXP_ASS_SUB: return "-=";
-	case EXP_ASS_SHL: return "<<=";
-	case EXP_ASS_SHR: return ">>=";
-	case EXP_ASS_USHR: return ">>>=";
-	case EXP_ASS_BITAND: return "&=";
-	case EXP_ASS_BITXOR: return "^=";
-	case EXP_ASS_BITOR: return "|=";
-	case STM_BLOCK: return "BLOCK";
-	case STM_FUNC: return "function-decl";
-	case STM_NOP: return "NOP";
-	case STM_VAR: return "var";
-	case STM_IF: return "if";
-	case STM_DO: return "do-while";
-	case STM_WHILE: return "while";
-	case STM_FOR: return "for";
-	case STM_FOR_VAR: return "for_var";
-	case STM_FOR_IN: return "for_in";
-	case STM_FOR_IN_VAR: return "for_in_var";
-	case STM_CONTINUE: return "continue";
-	case STM_BREAK: return "break";
-	case STM_RETURN: return "return";
-	case STM_WITH: return "with";
-	case STM_SWITCH: return "switch";
-	case STM_THROW: return "throw";
-	case STM_TRY: return "try";
-	case STM_LABEL: return "label";
-	case STM_CASE: return "case";
-	case STM_DEFAULT: return "default";
-	case STM_DEBUGGER: return "debugger";
-	default: return "(unknown)";
-	}
-}
-
-static void indent(int level)
-{
-	while (level--)
-		putchar('\t');
-}
-
-static void printlist(js_Ast *n, int level, const char *sep)
-{
-	while (n) {
-		printast(n->a, level);
-		n = n->b;
-		if (n)
-			fputs(sep, stdout);
-	}
-}
-
-void printblock(js_Ast *n, int level)
-{
-	while (n) {
-		indent(level);
-		printast(n->a, level);
-		if (n->a->type < STM_BLOCK) // expression
-			putchar(';');
-		n = n->b;
-		if (n)
-			putchar('\n');
-	}
-}
-
-static void printstm(js_Ast *n, int level)
-{
-	if (n->type == STM_BLOCK) {
-		printf(" {\n");
-		printblock(n->a, level + 1);
-		putchar('\n');
-		indent(level);
-		printf("}");
-	} else {
-		putchar('\n');
-		indent(level + 1);
-		printast(n, level + 1);
-		if (n->type < STM_BLOCK) // expression
-			putchar(';');
-	}
-}
-
-static void printunary(int level, js_Ast *n, const char *pre, const char *suf)
-{
-	printf(pre);
-	printast(n, level);
-	printf(suf);
-}
-
-static void printbinary(int level, js_Ast *a, js_Ast *b, const char *op)
-{
-	printf("(");
-	printast(a, level);
-	printf(" %s ", op);
-	printast(b, level);
-	printf(")");
-}
-
-void printast(js_Ast *n, int level)
-{
-	switch (n->type) {
-	case AST_IDENTIFIER: printf("%s", n->s); return;
-	case AST_NUMBER: printf("%g", n->n); return;
-	case AST_STRING: printf("'%s'", n->s); return;
-	case AST_REGEXP: printf("/%s/", n->s); return;
-	case AST_LIST:
-		putchar('[');
-		printlist(n, level, " ");
-		putchar(']');
-		break;
-
-	case STM_BLOCK:
-		putchar('{');
-		putchar('\n');
-		printblock(n->a, level + 1);
-		putchar('\n');
-		indent(level);
-		putchar('}');
-		break;
-
-	case STM_FOR:
-		printf("for (");
-		printast(n->a, level); printf("; ");
-		printast(n->b, level); printf("; ");
-		printast(n->c, level); printf(")");
-		printstm(n->d, level);
-		break;
-	case STM_FOR_VAR:
-		printf("for (var ");
-		printlist(n->a, level, ", "); printf("; ");
-		printast(n->b, level); printf("; ");
-		printast(n->c, level); printf(")");
-		printstm(n->d, level);
-		break;
-	case STM_FOR_IN:
-		printf("for (");
-		printast(n->a, level); printf(" in ");
-		printast(n->b, level); printf(")");
-		printstm(n->c, level);
-		break;
-	case STM_FOR_IN_VAR:
-		printf("for (var ");
-		printlist(n->a, level, ", "); printf(" in ");
-		printast(n->b, level); printf(")");
-		printstm(n->c, level);
-		break;
-
-	case STM_NOP:
-		putchar(';');
-		break;
-
-	case STM_VAR:
-		printf("var ");
-		printlist(n->a, level, ", ");
-		putchar(';');
-		break;
-
-	case EXP_VAR:
-		printast(n->a, level);
-		if (n->b) {
-			printf(" = ");
-			printast(n->b, level);
-		}
-		break;
-
-	case STM_IF:
-		printf("if (");
-		printast(n->a, level);
-		printf(")");
-		printstm(n->b, level);
-		if (n->c) {
-			putchar('\n');
-			indent(level);
-			printf("else");
-			printstm(n->c, level);
-		}
-		break;
-
-	case STM_DO:
-		printf("do");
-		printstm(n->a, level);
-		if (n->a->type == STM_BLOCK) {
-			putchar(' ');
-		} else {
-			putchar('\n');
-			indent(level);
-		}
-		printf("while (");
-		printast(n->b, level);
-		printf(");");
-		break;
-
-	case STM_WHILE:
-		printf("while (");
-		printast(n->a, level);
-		printf(")");
-		printstm(n->b, level);
-		break;
-
-	case STM_CONTINUE:
-		if (n->a) {
-			printf("continue ");
-			printast(n->a, level);
-			printf(";");
-		} else {
-			printf("continue;");
-		}
-		break;
-
-	case STM_BREAK:
-		if (n->a) {
-			printf("break ");
-			printast(n->a, level);
-			printf(";");
-		} else {
-			printf("break;");
-		}
-		break;
-
-	case STM_RETURN:
-		if (n->a) {
-			printf("return ");
-			printast(n->a, level);
-			printf(";");
-		} else {
-			printf("return;");
-		}
-		break;
-
-	case STM_THROW:
-		printf("throw ");
-		printast(n->a, level);
-		printf(";");
-		break;
-
-	case STM_SWITCH:
-		printf("switch (");
-		printast(n->a, level);
-		printf(") {\n");
-		printblock(n->b, level);
-		putchar('\n');
-		indent(level);
-		printf("}");
-		break;
-
-	case STM_CASE:
-		printf("case ");
-		printast(n->a, level);
-		printf(":");
-		if (n->b) {
-			printf("\n");
-			printblock(n->b, level + 1);
-		}
-		break;
-
-	case STM_DEFAULT:
-		printf("default:");
-		if (n->a) {
-			printf("\n");
-			printblock(n->a, level + 1);
-		}
-		break;
-
-	case STM_LABEL:
-		printast(n->a, level);
-		printf(":");
-		printstm(n->b, level - 1);
-		break;
-
-	case STM_WITH:
-		printf("with (");
-		printast(n->a, level);
-		printf(")");
-		printstm(n->b, level);
-		break;
-
-	case STM_TRY:
-		printf("try");
-		printstm(n->a, level);
-		if (n->b && n->c) {
-			printf(" catch (");
-			printast(n->b, level);
-			printf(")");
-			printstm(n->c, level);
-		}
-		if (n->d) {
-			printf(" finally");
-			printstm(n->d, level);
-		}
-		break;
-
-	case STM_DEBUGGER:
-		printf("debugger");
-		break;
-
-	case STM_FUNC:
-		printf("function ");
-		printast(n->a, level);
-		printf("(");
-		printlist(n->b, level, ", ");
-		printf(")\n");
-		indent(level);
-		printf("{\n");
-		printblock(n->c, level + 1);
-		printf("\n");
-		indent(level);
-		printf("}");
-		break;
-
-	case EXP_FUNC:
-		printf("(function ");
-		if (n->a)
-			printast(n->a, level);
-		printf("(");
-		printlist(n->b, level, ", ");
-		printf(") {\n");
-		printblock(n->c, level + 1);
-		printf("\n");
-		indent(level);
-		printf("})");
-		break;
-
-	case EXP_OBJECT:
-		printf("{ ");
-		printlist(n->a, level, ", ");
-		printf(" }");
-		break;
-
-	case EXP_PROP_VAL:
-		printast(n->a, level);
-		printf(": ");
-		printast(n->b, level);
-		break;
-
-	case EXP_ARRAY:
-		printf("[ ");
-		printlist(n->a, level, ", ");
-		printf(" ]");
-		break;
-
-	case EXP_NEW:
-		printf("(new ");
-		printast(n->a, level);
-		printf("(");
-		printlist(n->b, level, ", ");
-		printf("))");
-		break;
-
-	case EXP_CALL:
-		printf("(");
-		printast(n->a, level);
-		printf("(");
-		printlist(n->b, level, ", ");
-		printf("))");
-		break;
-
-	case EXP_MEMBER:
-		printf("(");
-		printast(n->a, level);
-		printf(".");
-		printast(n->b, level);
-		printf(")");
-		break;
-
-	case EXP_INDEX:
-		printf("(");
-		printast(n->a, level);
-		printf("[");
-		printast(n->b, level);
-		printf("])");
-		break;
-
-	case EXP_COND:
-		printf("(");
-		printast(n->a, level);
-		printf(" ? ");
-		printast(n->b, level);
-		printf(" : ");
-		printast(n->c, level);
-		printf(")");
-		break;
-
-	case EXP_NULL: printf("null"); break;
-	case EXP_TRUE: printf("true"); break;
-	case EXP_FALSE: printf("false"); break;
-	case EXP_THIS: printf("this"); break;
-
-	case EXP_DELETE:	printunary(level, n->a, "(delete ", ")"); break;
-	case EXP_VOID:		printunary(level, n->a, "(void ", ")"); break;
-	case EXP_TYPEOF:	printunary(level, n->a, "(typeof ", ")"); break;
-	case EXP_PREINC:	printunary(level, n->a, "(++", ")"); break;
-	case EXP_PREDEC:	printunary(level, n->a, "(--", ")"); break;
-	case EXP_POSTINC:	printunary(level, n->a, "(", "++)"); break;
-	case EXP_POSTDEC:	printunary(level, n->a, "(", "--)"); break;
-	case EXP_POS:		printunary(level, n->a, "(+", ")"); break;
-	case EXP_NEG:		printunary(level, n->a, "(-", ")"); break;
-	case EXP_BITNOT:	printunary(level, n->a, "(~", ")"); break;
-	case EXP_LOGNOT:	printunary(level, n->a, "(!", ")"); break;
-
-	case EXP_COMMA:		printbinary(level, n->a, n->b, ","); break;
-	case EXP_LOGOR:		printbinary(level, n->a, n->b, "||"); break;
-	case EXP_LOGAND:	printbinary(level, n->a, n->b, "&&"); break;
-	case EXP_BITOR:		printbinary(level, n->a, n->b, "|"); break;
-	case EXP_BITXOR:	printbinary(level, n->a, n->b, "^"); break;
-	case EXP_BITAND:	printbinary(level, n->a, n->b, "&"); break;
-	case EXP_EQ:		printbinary(level, n->a, n->b, "=="); break;
-	case EXP_NE:		printbinary(level, n->a, n->b, "!="); break;
-	case EXP_EQ3:		printbinary(level, n->a, n->b, "==="); break;
-	case EXP_NE3:		printbinary(level, n->a, n->b, "!=="); break;
-	case EXP_LT:		printbinary(level, n->a, n->b, "<"); break;
-	case EXP_GT:		printbinary(level, n->a, n->b, ">"); break;
-	case EXP_LE:		printbinary(level, n->a, n->b, "<="); break;
-	case EXP_GE:		printbinary(level, n->a, n->b, ">="); break;
-	case EXP_INSTANCEOF:	printbinary(level, n->a, n->b, "instanceof"); break;
-	case EXP_IN:		printbinary(level, n->a, n->b, "in"); break;
-	case EXP_SHL:		printbinary(level, n->a, n->b, "<<"); break;
-	case EXP_SHR:		printbinary(level, n->a, n->b, ">>"); break;
-	case EXP_USHR:		printbinary(level, n->a, n->b, ">>>"); break;
-	case EXP_ADD:		printbinary(level, n->a, n->b, "+"); break;
-	case EXP_SUB:		printbinary(level, n->a, n->b, "-"); break;
-	case EXP_MUL:		printbinary(level, n->a, n->b, "*"); break;
-	case EXP_DIV:		printbinary(level, n->a, n->b, "/"); break;
-	case EXP_MOD:		printbinary(level, n->a, n->b, "%"); break;
-	case EXP_ASS:		printbinary(level, n->a, n->b, "="); break;
-	case EXP_ASS_MUL:	printbinary(level, n->a, n->b, "*="); break;
-	case EXP_ASS_DIV:	printbinary(level, n->a, n->b, "/="); break;
-	case EXP_ASS_MOD:	printbinary(level, n->a, n->b, "%="); break;
-	case EXP_ASS_ADD:	printbinary(level, n->a, n->b, "+="); break;
-	case EXP_ASS_SUB:	printbinary(level, n->a, n->b, "-="); break;
-	case EXP_ASS_SHL:	printbinary(level, n->a, n->b, "<<="); break;
-	case EXP_ASS_SHR:	printbinary(level, n->a, n->b, ">>="); break;
-	case EXP_ASS_USHR:	printbinary(level, n->a, n->b, ">>>="); break;
-	case EXP_ASS_BITAND:	printbinary(level, n->a, n->b, "&="); break;
-	case EXP_ASS_BITXOR:	printbinary(level, n->a, n->b, "^="); break;
-	case EXP_ASS_BITOR:	printbinary(level, n->a, n->b, "|="); break;
-
-	default:
-		printf("(%s", strast(n->type));
-		if (n->a) { putchar(' '); printast(n->a, level); }
-		if (n->b) { putchar(' '); printast(n->b, level); }
-		if (n->c) { putchar(' '); printast(n->c, level); }
-		if (n->d) { putchar(' '); printast(n->d, level); }
-		putchar(')');
-		break;
-	}
-}
--- a/js-ast.h
+++ /dev/null
@@ -1,130 +1,0 @@
-#ifndef js_ast_h
-#define js_ast_h
-
-struct js_Ast
-{
-	int type;
-	int line;
-	js_Ast *a, *b, *c, *d;
-	double n;
-	const char *s;
-	js_Ast *next; /* next in alloc list */
-};
-
-enum
-{
-	AST_LIST,
-
-	AST_IDENTIFIER,
-	AST_NUMBER,
-	AST_STRING,
-	AST_REGEXP,
-
-	/* literals */
-	EXP_NULL,
-	EXP_TRUE,
-	EXP_FALSE,
-	EXP_THIS,
-
-	EXP_ARRAY,
-	EXP_OBJECT,
-	EXP_PROP_VAL,
-	EXP_PROP_GET,
-	EXP_PROP_SET,
-
-	/* expressions */
-	EXP_INDEX,
-	EXP_MEMBER,
-	EXP_CALL,
-	EXP_NEW,
-	EXP_FUNC, /* function expression */
-
-	EXP_DELETE,
-	EXP_VOID,
-	EXP_TYPEOF,
-	EXP_PREINC,
-	EXP_PREDEC,
-	EXP_POSTINC,
-	EXP_POSTDEC,
-	EXP_POS,
-	EXP_NEG,
-	EXP_BITNOT,
-	EXP_LOGNOT,
-	EXP_LOGOR,
-
-	EXP_LOGAND,
-	EXP_BITOR,
-	EXP_BITXOR,
-	EXP_BITAND,
-	EXP_EQ,
-	EXP_NE,
-	EXP_EQ3,
-	EXP_NE3,
-	EXP_LT,
-	EXP_GT,
-	EXP_LE,
-	EXP_GE,
-	EXP_INSTANCEOF,
-	EXP_IN,
-	EXP_SHL,
-	EXP_SHR,
-	EXP_USHR,
-	EXP_ADD,
-	EXP_SUB,
-	EXP_MUL,
-	EXP_DIV,
-	EXP_MOD,
-
-	EXP_COND,
-
-	EXP_ASS,
-	EXP_ASS_MUL,
-	EXP_ASS_DIV,
-	EXP_ASS_MOD,
-	EXP_ASS_ADD,
-	EXP_ASS_SUB,
-	EXP_ASS_SHL,
-	EXP_ASS_SHR,
-	EXP_ASS_USHR,
-	EXP_ASS_BITAND,
-	EXP_ASS_BITXOR,
-	EXP_ASS_BITOR,
-
-	EXP_COMMA,
-
-	EXP_VAR, /* var initializer */
-
-	/* statements */
-	STM_BLOCK,
-	STM_FUNC, /* function declaration */
-	STM_NOP,
-	STM_VAR,
-	STM_IF,
-	STM_DO,
-	STM_WHILE,
-	STM_FOR,
-	STM_FOR_VAR,
-	STM_FOR_IN,
-	STM_FOR_IN_VAR,
-	STM_CONTINUE,
-	STM_BREAK,
-	STM_RETURN,
-	STM_WITH,
-	STM_SWITCH,
-	STM_THROW,
-	STM_TRY,
-	STM_LABEL,
-	STM_CASE,
-	STM_DEFAULT,
-	STM_DEBUGGER,
-};
-
-js_Ast *jsP_newnode(js_State *J, int type, js_Ast *a, js_Ast *b, js_Ast *c, js_Ast *d);
-js_Ast *jsP_newstrnode(js_State *J, int type, const char *s);
-js_Ast *jsP_newnumnode(js_State *J, int type, double n);
-void jsP_freeast(js_State *J);
-
-void printast(js_Ast *n, int level);
-void printblock(js_Ast *n, int level);
-
-#endif
--- a/js-lex.c
+++ /dev/null
@@ -1,608 +1,0 @@
-#include "js.h"
-#include "js-parse.h"
-
-#define nelem(a) (sizeof (a) / sizeof (a)[0])
-
-static const char *tokenstring[] = {
-	"(end-of-file)",
-	"'\\x01'", "'\\x02'", "'\\x03'", "'\\x04'", "'\\x05'", "'\\x06'", "'\\x07'",
-	"'\\x08'", "'\\x09'", "'\\x0A'", "'\\x0B'", "'\\x0C'", "'\\x0D'", "'\\x0E'", "'\\x0F'",
-	"'\\x10'", "'\\x11'", "'\\x12'", "'\\x13'", "'\\x14'", "'\\x15'", "'\\x16'", "'\\x17'",
-	"'\\x18'", "'\\x19'", "'\\x1A'", "'\\x1B'", "'\\x1C'", "'\\x1D'", "'\\x1E'", "'\\x1F'",
-	"' '", "'!'", "'\"'", "'#'", "'$'", "'%'", "'&'", "'\\''",
-	"'('", "')'", "'*'", "'+'", "','", "'-'", "'.'", "'/'",
-	"'0'", "'1'", "'2'", "'3'", "'4'", "'5'", "'6'", "'7'",
-	"'8'", "'9'", "':'", "';'", "'<'", "'='", "'>'", "'?'",
-	"'@'", "'A'", "'B'", "'C'", "'D'", "'E'", "'F'", "'G'",
-	"'H'", "'I'", "'J'", "'K'", "'L'", "'M'", "'N'", "'O'",
-	"'P'", "'Q'", "'R'", "'S'", "'T'", "'U'", "'V'", "'W'",
-	"'X'", "'Y'", "'Z'", "'['", "'\'", "']'", "'^'", "'_'",
-	"'`'", "'a'", "'b'", "'c'", "'d'", "'e'", "'f'", "'g'",
-	"'h'", "'i'", "'j'", "'k'", "'l'", "'m'", "'n'", "'o'",
-	"'p'", "'q'", "'r'", "'s'", "'t'", "'u'", "'v'", "'w'",
-	"'x'", "'y'", "'z'", "'{'", "'|'", "'}'", "'~'", "'\\x7F'",
-
-	0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
-	0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
-	0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
-	0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
-	0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
-	0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
-	0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
-	0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
-
-	"(identifier)", "(number)", "(string)", "(regexp)",
-
-	"'<='", "'>='", "'=='", "'!='", "'==='", "'!=='",
-	"'<<'", "'>>'", "'>>>'", "'&&'", "'||'",
-	"'+='", "'-='", "'*='", "'/='", "'%='",
-	"'<<='", "'>>='", "'>>>='", "'&='", "'|='", "'^='",
-	"'++'", "'--'",
-
-	"'break'", "'case'", "'catch'", "'continue'", "'debugger'",
-	"'default'", "'delete'", "'do'", "'else'", "'false'", "'finally'", "'for'",
-	"'function'", "'if'", "'in'", "'instanceof'", "'new'", "'null'", "'return'",
-	"'switch'", "'this'", "'throw'", "'true'", "'try'", "'typeof'", "'var'",
-	"'void'", "'while'", "'with'",
-};
-
-const char *jsP_tokenstring(int token)
-{
-	if (token >= 0 && token < nelem(tokenstring))
-		if (tokenstring[token])
-			return tokenstring[token];
-	return "<unknown>";
-}
-
-static const char *keywords[] = {
-	"break", "case", "catch", "continue", "debugger", "default", "delete",
-	"do", "else", "false", "finally", "for", "function", "if", "in",
-	"instanceof", "new", "null", "return", "switch", "this", "throw",
-	"true", "try", "typeof", "var", "void", "while", "with",
-};
-
-static const char *futurewords[] = {
-	"class", "const", "enum", "export", "extends", "import", "super",
-};
-
-static const char *strictfuturewords[] = {
-	"implements", "interface", "let", "package", "private", "protected",
-	"public", "static", "yield",
-};
-
-static inline int findword(const char *s, const char **list, int num)
-{
-	int l = 0;
-	int r = num - 1;
-	while (l <= r) {
-		int m = (l + r) >> 1;
-		int c = strcmp(s, list[m]);
-		if (c < 0)
-			r = m - 1;
-		else if (c > 0)
-			l = m + 1;
-		else
-			return m;
-	}
-	return -1;
-}
-
-static inline int findkeyword(js_State *J, const char *s)
-{
-	int i = findword(s, keywords, nelem(keywords));
-	if (i >= 0) {
-		J->text = keywords[i];
-		return TK_BREAK + i; /* first keyword + i */
-	}
-
-	if (findword(s, futurewords, nelem(futurewords)) >= 0)
-		return jsP_error(J, "'%s' is a future reserved word", s);
-	if (J->strict && findword(s, strictfuturewords, nelem(strictfuturewords)) >= 0)
-		return jsP_error(J, "'%s' is a strict mode future reserved word", s);
-
-	J->text = js_intern(J, s);
-	return TK_IDENTIFIER;
-}
-
-#define GET() (*(*sp)++)
-#define UNGET() ((*sp)--)
-#define PEEK() (**sp)
-#define NEXT() ((*sp)++)
-#define NEXTPEEK() (NEXT(), PEEK())
-#define LOOK(x) (PEEK() == x ? (NEXT(), 1) : 0)
-
-static void textinit(js_State *J)
-{
-	if (!J->buf.text) {
-		J->buf.cap = 4096;
-		J->buf.text = malloc(J->buf.cap);
-	}
-	J->buf.len = 0;
-}
-
-static inline void textpush(js_State *J, int c)
-{
-	if (J->buf.len >= J->buf.cap) {
-		J->buf.cap = J->buf.cap * 2;
-		J->buf.text = realloc(J->buf.text, J->buf.cap);
-	}
-	J->buf.text[J->buf.len++] = c;
-}
-
-static inline char *textend(js_State *J)
-{
-	textpush(J, 0);
-	return J->buf.text;
-}
-
-static inline int iswhite(int c)
-{
-	return c == 0x9 || c == 0xB || c == 0xC || c == 0x20 || c == 0xA0 || c == 0xFEFF;
-}
-
-static inline int isnewline(c)
-{
-	return c == 0xA || c == 0xD || c == 0x2028 || c == 0x2029;
-}
-
-static inline int isidentifierstart(int c)
-{
-	return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '$' || c == '_';
-}
-
-static inline int isidentifierpart(int c)
-{
-	return (c >= '0' && c <= '9') || isidentifierstart(c);
-}
-
-static inline int isdec(int c)
-{
-	return (c >= '0' && c <= '9');
-}
-
-static inline int ishex(int c)
-{
-	return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
-}
-
-static inline int tohex(int c)
-{
-	if (c >= '0' && c <= '9')
-		return c - '0';
-	if (c >= 'a' && c <= 'f')
-		return c - 'a' + 0xA;
-	if (c >= 'A' && c <= 'F')
-		return c - 'A' + 0xA;
-	return 0;
-}
-
-static inline void lexlinecomment(const char **sp)
-{
-	int c = PEEK();
-	while (c && !isnewline(c)) {
-		c = NEXTPEEK();
-	}
-}
-
-static inline int lexcomment(const char **sp)
-{
-	while (1) {
-		int c = GET();
-		if (c == '*') {
-			while (c == '*')
-				c = GET();
-			if (c == '/')
-				return 0;
-		} else if (c == 0) {
-			return -1;
-		}
-	}
-}
-
-static inline double lexhex(const char **sp)
-{
-	double n = 0;
-	int c = PEEK();
-	while (ishex(c)) {
-		n = n * 16 + tohex(c);
-		c = NEXTPEEK();
-	}
-	return n;
-}
-
-static inline double lexinteger(const char **sp)
-{
-	double n = 0;
-	int c = PEEK();
-	while (isdec(c)) {
-		n = n * 10 + (c - '0');
-		c = NEXTPEEK();
-	}
-	return n;
-}
-
-static inline double lexfraction(const char **sp)
-{
-	double n = 0;
-	double d = 1;
-	int c = PEEK();
-	while (isdec(c)) {
-		n = n * 10 + (c - '0');
-		d = d * 10;
-		c = NEXTPEEK();
-	}
-	return n / d;
-}
-
-static inline double lexexponent(const char **sp)
-{
-	if (LOOK('e') || LOOK('E')) {
-		if (LOOK('-'))
-			return -lexinteger(sp);
-		else if (LOOK('+'))
-			return lexinteger(sp);
-		else
-			return lexinteger(sp);
-	}
-	return 0;
-}
-
-static inline int lexnumber(js_State *J, const char **sp)
-{
-	double n;
-
-	if ((*sp)[0] == '0' && ((*sp)[1] == 'x' || (*sp)[1] == 'X')) {
-		*sp += 2;
-		if (!ishex(PEEK()))
-			return jsP_error(J, "0x not followed by hexademical digit");
-		J->number = lexhex(sp);
-		return TK_NUMBER;
-	}
-
-	if ((*sp)[0] == '0' && isdec((*sp)[1]))
-		return jsP_error(J, "number with leading zero");
-
-	n = lexinteger(sp);
-	if (LOOK('.'))
-		n += lexfraction(sp);
-	n *= pow(10, lexexponent(sp));
-
-	if (isidentifierstart(PEEK()))
-		return jsP_error(J, "number with letter suffix");
-
-	J->number = n;
-	return TK_NUMBER;
-}
-
-static inline int lexescape(js_State *J, const char **sp)
-{
-	int c = GET();
-	int x = 0;
-
-	if (isnewline(c)) {
-		if (c == '\r' && PEEK() == '\n')
-			NEXT();
-		return 0;
-	}
-
-	switch (c) {
-	case 'u':
-		if (!ishex(PEEK())) return 1; else x |= NEXTPEEK() << 12;
-		if (!ishex(PEEK())) return 1; else x |= NEXTPEEK() << 8;
-		if (!ishex(PEEK())) return 1; else x |= NEXTPEEK() << 4;
-		if (!ishex(PEEK())) return 1; else x |= NEXTPEEK();
-		textpush(J, x);
-		break;
-	case 'x':
-		if (!ishex(PEEK())) return 1; else x |= NEXTPEEK() << 4;
-		if (!ishex(PEEK())) return 1; else x |= NEXTPEEK();
-		textpush(J, x);
-		break;
-	case '0': textpush(J, 0); break;
-	case '\\': textpush(J, '\\'); break;
-	case '\'': textpush(J, '\''); break;
-	case '"': textpush(J, '"'); break;
-	case 'b': textpush(J, '\b'); break;
-	case 'f': textpush(J, '\f'); break;
-	case 'n': textpush(J, '\n'); break;
-	case 'r': textpush(J, '\r'); break;
-	case 't': textpush(J, '\t'); break;
-	case 'v': textpush(J, '\v'); break;
-	default: textpush(J, c); break;
-	}
-	return 0;
-}
-
-static inline int lexstring(js_State *J, const char **sp, int q)
-{
-	const char *s;
-	int c = GET();
-
-	textinit(J);
-
-	while (c != q) {
-		if (c == 0 || isnewline(c))
-			return jsP_error(J, "string not terminated");
-		if (c == '\\') {
-			if (lexescape(J, sp))
-				return jsP_error(J, "malformed escape sequence");
-		} else {
-			textpush(J, c);
-		}
-		c = GET();
-	}
-
-	s = textend(J);
-
-	J->text = js_intern(J, s);
-	return TK_STRING;
-}
-
-/* the ugliest language wart ever... */
-static int isregexpcontext(int last)
-{
-	switch (last) {
-	case ']':
-	case ')':
-	case TK_IDENTIFIER:
-	case TK_NUMBER:
-	case TK_STRING:
-	case TK_FALSE:
-	case TK_NULL:
-	case TK_THIS:
-	case TK_TRUE:
-		return 0;
-	default:
-		return 1;
-	}
-}
-
-static int lexregexp(js_State *J, const char **sp)
-{
-	const char *s;
-	int c;
-
-	textinit(J);
-
-	/* regexp body */
-	c = GET();
-	while (c != '/') {
-		if (c == 0 || isnewline(c)) {
-			return jsP_error(J, "regular expression not terminated");
-		} else if (c == '\\') {
-			textpush(J, c);
-			c = GET();
-			if (c == 0 || isnewline(c))
-				return jsP_error(J, "regular expression not terminated");
-			textpush(J, c);
-			c = GET();
-		} else {
-			textpush(J, c);
-			c = GET();
-		}
-	}
-
-	s = textend(J);
-
-	/* regexp flags */
-	J->flags.g = J->flags.i = J->flags.m = 0;
-
-	c = PEEK();
-	while (isidentifierpart(c)) {
-		if (c == 'g') J->flags.g ++;
-		else if (c == 'i') J->flags.i ++;
-		else if (c == 'm') J->flags.m ++;
-		else return jsP_error(J, "illegal flag in regular expression: %c", c);
-		c = NEXTPEEK();
-	}
-
-	if (J->flags.g > 1 || J->flags.i > 1 || J->flags.m > 1)
-		return jsP_error(J, "duplicated flag in regular expression");
-
-	J->text = js_intern(J, s);
-	return TK_REGEXP;
-}
-
-/* simple "return [no Line Terminator here] ..." contexts */
-static inline int isnlthcontext(int last)
-{
-	switch (last) {
-	case TK_BREAK:
-	case TK_CONTINUE:
-	case TK_RETURN:
-	case TK_THROW:
-		return 1;
-	default:
-		return 0;
-	}
-}
-
-static int lex(js_State *J, const char **sp)
-{
-	J->newline = 0;
-
-	while (1) {
-		int c = GET();
-
-		while (iswhite(c))
-			c = GET();
-
-		if (isnewline(c)) {
-			/* consume CR LF as one unit */
-			if (c == '\r' && PEEK() == '\n')
-				NEXT();
-			J->line++;
-			J->newline = 1;
-			if (isnlthcontext(J->lasttoken))
-				return ';';
-			continue;
-		}
-
-		if (c == '/') {
-			if (LOOK('/')) {
-				lexlinecomment(sp);
-				continue;
-			} else if (LOOK('*')) {
-				if (lexcomment(sp))
-					return jsP_error(J, "multi-line comment not terminated");
-				continue;
-			} else if (isregexpcontext(J->lasttoken)) {
-				return lexregexp(J, sp);
-			} else if (LOOK('=')) {
-				return TK_DIV_ASS;
-			} else {
-				return '/';
-			}
-		}
-
-		if (isidentifierstart(c)) {
-			textinit(J);
-			textpush(J, c);
-
-			c = PEEK();
-			while (isidentifierpart(c)) {
-				textpush(J, c);
-				c = NEXTPEEK();
-			}
-
-			textend(J);
-
-			return findkeyword(J, J->buf.text);
-		}
-
-		if (c >= '0' && c <= '9') {
-			UNGET();
-			return lexnumber(J, sp);
-		}
-
-		switch (c) {
-		case '(':
-		case ')':
-		case ',':
-		case ':':
-		case ';':
-		case '?':
-		case '[':
-		case ']':
-		case '{':
-		case '}':
-		case '~':
-			return c;
-
-		case '\'':
-		case '"':
-			return lexstring(J, sp, c);
-
-		case '.':
-			if (isdec(PEEK())) {
-				UNGET();
-				return lexnumber(J, sp);
-			}
-			return '.';
-
-		case '<':
-			if (LOOK('<')) {
-				if (LOOK('='))
-					return TK_SHL_ASS;
-				return TK_SHL;
-			}
-			if (LOOK('='))
-				return TK_LE;
-			return '<';
-
-		case '>':
-			if (LOOK('>')) {
-				if (LOOK('>')) {
-					if (LOOK('='))
-						return TK_USHR_ASS;
-					return TK_USHR;
-				}
-				if (LOOK('='))
-					return TK_SHR_ASS;
-				return TK_SHR;
-			}
-			if (LOOK('='))
-				return TK_GE;
-			return '>';
-
-		case '=':
-			if (LOOK('=')) {
-				if (LOOK('='))
-					return TK_EQ3;
-				return TK_EQ;
-			}
-			return '=';
-
-		case '!':
-			if (LOOK('=')) {
-				if (LOOK('='))
-					return TK_NE3;
-				return TK_NE;
-			}
-			return '!';
-
-		case '+':
-			if (LOOK('+'))
-				return TK_INC;
-			if (LOOK('='))
-				return TK_ADD_ASS;
-			return '+';
-
-		case '-':
-			if (LOOK('-'))
-				return TK_DEC;
-			if (LOOK('='))
-				return TK_SUB_ASS;
-			return '-';
-
-		case '*':
-			if (LOOK('='))
-				return TK_MUL_ASS;
-			return '*';
-
-		case '%':
-			if (LOOK('='))
-				return TK_MOD_ASS;
-			return '%';
-
-		case '&':
-			if (LOOK('&'))
-				return TK_AND;
-			if (LOOK('='))
-				return TK_AND_ASS;
-			return '&';
-
-		case '|':
-			if (LOOK('|'))
-				return TK_OR;
-			if (LOOK('='))
-				return TK_OR_ASS;
-			return '|';
-
-		case '^':
-			if (LOOK('='))
-				return TK_XOR_ASS;
-			return '^';
-
-		case 0:
-			return 0; /* EOF */
-		}
-
-		if (c >= 0x20 && c <= 0x7E)
-			return jsP_error(J, "unexpected character: '%c'", c);
-		return jsP_error(J, "unexpected character: \\u%04X", c);
-	}
-}
-
-void jsP_initlex(js_State *J, const char *filename, const char *source)
-{
-	J->filename = filename;
-	J->source = source;
-	J->line = 1;
-	J->lasttoken = 0;
-}
-
-int jsP_lex(js_State *J)
-{
-	return J->lasttoken = lex(J, &J->source);
-}
--- a/js-load.c
+++ /dev/null
@@ -1,48 +1,0 @@
-#include "js.h"
-#include "js-parse.h"
-
-static int jsP_loadstring(js_State *J, const char *filename, const char *source)
-{
-	return jsP_parse(J, filename, source);
-}
-
-int js_loadstring(js_State *J, const char *source)
-{
-	return jsP_loadstring(J, "(string)", source);
-}
-
-int js_loadfile(js_State *J, const char *filename)
-{
-	FILE *f;
-	char *s;
-	int n, t;
-
-	f = fopen(filename, "r");
-	if (!f)
-		return js_error(J, "cannot open file: '%s'", filename);
-
-	fseek(f, 0, SEEK_END);
-	n = ftell(f);
-	fseek(f, 0, SEEK_SET);
-
-	s = malloc(n + 1); /* add space for string terminator */
-	if (!s) {
-		fclose(f);
-		return js_error(J, "cannot allocate storage for file contents: '%s'", filename);
-	}
-
-	t = fread(s, 1, n, f);
-	if (t != n) {
-		free(s);
-		fclose(f);
-		return js_error(J, "cannot read data from file: '%s'", filename);
-	}
-
-	s[n] = 0; /* zero-terminate string containing file data */
-
-	t = jsP_loadstring(J, filename, s);
-
-	free(s);
-	fclose(f);
-	return t;
-}
--- a/js-parse.c
+++ /dev/null
@@ -1,760 +1,0 @@
-#include "js.h"
-#include "js-parse.h"
-#include "js-ast.h"
-
-#define LIST(h)		jsP_newnode(J, AST_LIST, h, 0, 0, 0);
-
-#define EXP0(x)		jsP_newnode(J, EXP_ ## x, 0, 0, 0, 0)
-#define EXP1(x,a)	jsP_newnode(J, EXP_ ## x, a, 0, 0, 0)
-#define EXP2(x,a,b)	jsP_newnode(J, EXP_ ## x, a, b, 0, 0)
-#define EXP3(x,a,b,c)	jsP_newnode(J, EXP_ ## x, a, b, c, 0)
-
-#define STM0(x)		jsP_newnode(J, STM_ ## x, 0, 0, 0, 0)
-#define STM1(x,a)	jsP_newnode(J, STM_ ## x, a, 0, 0, 0)
-#define STM2(x,a,b)	jsP_newnode(J, STM_ ## x, a, b, 0, 0)
-#define STM3(x,a,b,c)	jsP_newnode(J, STM_ ## x, a, b, c, 0)
-#define STM4(x,a,b,c,d)	jsP_newnode(J, STM_ ## x, a, b, c, d)
-
-#define TOKSTR		jsP_tokenstring(J->lookahead)
-
-static js_Ast *expression(js_State *J, int notin);
-static js_Ast *assignment(js_State *J, int notin);
-static js_Ast *memberexp(js_State *J);
-static js_Ast *statement(js_State *J);
-static js_Ast *funcbody(js_State *J);
-
-static inline void next(js_State *J)
-{
-	J->lookahead = jsP_lex(J);
-}
-
-static inline int accept(js_State *J, int t)
-{
-	if (J->lookahead == t) {
-		next(J);
-		return 1;
-	}
-	return 0;
-}
-
-static inline void expect(js_State *J, int t)
-{
-	if (accept(J, t))
-		return;
-	jsP_error(J, "unexpected token: %s (expected %s)", TOKSTR, jsP_tokenstring(t));
-}
-
-static void semicolon(js_State *J)
-{
-	if (J->lookahead == ';') {
-		next(J);
-		return;
-	}
-	if (J->newline || J->lookahead == '}' || J->lookahead == 0)
-		return;
-	jsP_error(J, "unexpected token: %s (expected ';')", TOKSTR);
-}
-
-/* Literals */
-
-static js_Ast *identifier(js_State *J)
-{
-	if (J->lookahead == TK_IDENTIFIER) {
-		js_Ast *a = jsP_newstrnode(J, AST_IDENTIFIER, J->text);
-		next(J);
-		return a;
-	}
-	jsP_error(J, "unexpected token: %s (expected identifier)", TOKSTR);
-	return NULL;
-}
-
-static js_Ast *identifieropt(js_State *J)
-{
-	if (J->lookahead == TK_IDENTIFIER)
-		return identifier(J);
-	return NULL;
-}
-
-static js_Ast *identifiername(js_State *J)
-{
-	if (J->lookahead == TK_IDENTIFIER || J->lookahead >= TK_BREAK) {
-		js_Ast *a = jsP_newstrnode(J, AST_IDENTIFIER, J->text);
-		next(J);
-		return a;
-	}
-	jsP_error(J, "unexpected token: %s (expected identifier or keyword)", TOKSTR);
-	return NULL;
-}
-
-static js_Ast *arrayelement(js_State *J)
-{
-	if (J->lookahead == ',')
-		return EXP0(NULL); /* TODO: should be 'undefined' */
-	return assignment(J, 0);
-}
-
-static js_Ast *arrayliteral(js_State *J)
-{
-	js_Ast *head, *tail;
-	if (J->lookahead == ']')
-		return NULL;
-	head = tail = LIST(arrayelement(J));
-	while (accept(J, ',')) {
-		if (J->lookahead != ']')
-			tail = tail->b = LIST(arrayelement(J));
-	}
-	return head;
-}
-
-static js_Ast *propname(js_State *J)
-{
-	js_Ast *name;
-	if (J->lookahead == TK_NUMBER) {
-		name = jsP_newnumnode(J, AST_NUMBER, J->number);
-		next(J);
-	} else if (J->lookahead == TK_STRING) {
-		name = jsP_newstrnode(J, AST_STRING, J->text);
-		next(J);
-	} else {
-		name = identifiername(J);
-	}
-	return name;
-}
-
-static js_Ast *propassign(js_State *J)
-{
-	js_Ast *name, *value, *arg, *body;
-
-	if (J->lookahead == TK_IDENTIFIER && !strcmp(J->text, "get")) {
-		next(J);
-		name = propname(J);
-		expect(J, '(');
-		expect(J, ')');
-		body = funcbody(J);
-		return EXP2(PROP_GET, name, body);
-	}
-
-	if (J->lookahead == TK_IDENTIFIER && !strcmp(J->text, "set")) {
-		next(J);
-		name = propname(J);
-		expect(J, '(');
-		arg = identifier(J);
-		expect(J, ')');
-		body = funcbody(J);
-		return EXP3(PROP_SET, name, arg, body);
-	}
-
-	name = propname(J);
-	expect(J, ':');
-	value = assignment(J, 0);
-	return EXP2(PROP_VAL, name, value);
-}
-
-static js_Ast *objectliteral(js_State *J)
-{
-	js_Ast *head, *tail;
-	if (J->lookahead == '}')
-		return NULL;
-	head = tail = LIST(propassign(J));
-	while (accept(J, ',')) {
-		if (J->lookahead == '}')
-			break;
-		tail = tail->b = LIST(propassign(J));
-	}
-	return head;
-}
-
-/* Expressions */
-
-static js_Ast *primary(js_State *J)
-{
-	js_Ast *a;
-
-	if (J->lookahead == TK_IDENTIFIER) {
-		a = jsP_newstrnode(J, AST_IDENTIFIER, J->text);
-		next(J);
-		return a;
-	}
-	if (J->lookahead == TK_STRING) {
-		a = jsP_newstrnode(J, AST_STRING, J->text);
-		next(J);
-		return a;
-	}
-	if (J->lookahead == TK_REGEXP) {
-		a = jsP_newstrnode(J, AST_REGEXP, J->text);
-		// TODO: flags
-		next(J);
-		return a;
-	}
-	if (J->lookahead == TK_NUMBER) {
-		a = jsP_newnumnode(J, AST_NUMBER, J->number);
-		next(J);
-		return a;
-	}
-
-	if (accept(J, TK_THIS)) return EXP0(THIS);
-	if (accept(J, TK_NULL)) return EXP0(NULL);
-	if (accept(J, TK_TRUE)) return EXP0(TRUE);
-	if (accept(J, TK_FALSE)) return EXP0(FALSE);
-	if (accept(J, '{')) { a = EXP1(OBJECT, objectliteral(J)); expect(J, '}'); return a; }
-	if (accept(J, '[')) { a = EXP1(ARRAY, arrayliteral(J)); expect(J, ']'); return a; }
-	if (accept(J, '(')) { a = expression(J, 0); expect(J, ')'); return a; }
-
-	jsP_error(J, "unexpected token in expression: %s", TOKSTR);
-	return NULL;
-}
-
-static js_Ast *arguments(js_State *J)
-{
-	js_Ast *head, *tail;
-	if (J->lookahead == ')')
-		return NULL;
-	head = tail = LIST(assignment(J, 0));
-	while (accept(J, ',')) {
-		tail = tail->b = LIST(assignment(J, 0));
-	}
-	return head;
-}
-
-static js_Ast *parameters(js_State *J)
-{
-	js_Ast *head, *tail;
-	if (J->lookahead == ')')
-		return NULL;
-	head = tail = LIST(identifier(J));
-	while (accept(J, ',')) {
-		tail = tail->b = LIST(identifier(J));
-	}
-	return head;
-}
-
-static js_Ast *newexp(js_State *J)
-{
-	js_Ast *a, *b, *c;
-
-	if (accept(J, TK_NEW)) {
-		a = memberexp(J);
-		if (accept(J, '(')) {
-			b = arguments(J);
-			expect(J, ')');
-			return EXP2(NEW, a, b);
-		}
-		return EXP1(NEW, a);
-	}
-
-	if (accept(J, TK_FUNCTION)) {
-		a = identifieropt(J);
-		expect(J, '(');
-		b = parameters(J);
-		expect(J, ')');
-		c = funcbody(J);
-		return EXP3(FUNC, a, b, c);
-	}
-
-	return primary(J);
-}
-
-static js_Ast *memberexp(js_State *J)
-{
-	js_Ast *a = newexp(J);
-loop:
-	if (accept(J, '.')) { a = EXP2(MEMBER, a, identifiername(J)); goto loop; }
-	if (accept(J, '[')) { a = EXP2(INDEX, a, expression(J, 0)); expect(J, ']'); goto loop; }
-	return a;
-}
-
-static js_Ast *callexp(js_State *J)
-{
-	js_Ast *a = newexp(J);
-loop:
-	if (accept(J, '.')) { a = EXP2(MEMBER, a, identifiername(J)); goto loop; }
-	if (accept(J, '[')) { a = EXP2(INDEX, a, expression(J, 0)); expect(J, ']'); goto loop; }
-	if (accept(J, '(')) { a = EXP2(CALL, a, arguments(J)); expect(J, ')'); goto loop; }
-	return a;
-}
-
-static js_Ast *postfix(js_State *J)
-{
-	js_Ast *a = callexp(J);
-	if (!J->newline && accept(J, TK_INC)) return EXP1(POSTINC, a);
-	if (!J->newline && accept(J, TK_DEC)) return EXP1(POSTDEC, a);
-	return a;
-}
-
-static js_Ast *unary(js_State *J)
-{
-	if (accept(J, TK_DELETE)) return EXP1(DELETE, unary(J));
-	if (accept(J, TK_VOID)) return EXP1(VOID, unary(J));
-	if (accept(J, TK_TYPEOF)) return EXP1(TYPEOF, unary(J));
-	if (accept(J, TK_INC)) return EXP1(PREINC, unary(J));
-	if (accept(J, TK_DEC)) return EXP1(PREDEC, unary(J));
-	if (accept(J, '+')) return EXP1(POS, unary(J));
-	if (accept(J, '-')) return EXP1(NEG, unary(J));
-	if (accept(J, '~')) return EXP1(BITNOT, unary(J));
-	if (accept(J, '!')) return EXP1(LOGNOT, unary(J));
-	return postfix(J);
-}
-
-static js_Ast *multiplicative(js_State *J)
-{
-	js_Ast *a = unary(J);
-loop:
-	if (accept(J, '*')) { a = EXP2(MUL, a, unary(J)); goto loop; }
-	if (accept(J, '/')) { a = EXP2(DIV, a, unary(J)); goto loop; }
-	if (accept(J, '%')) { a = EXP2(MOD, a, unary(J)); goto loop; }
-	return a;
-}
-
-static js_Ast *additive(js_State *J)
-{
-	js_Ast *a = multiplicative(J);
-loop:
-	if (accept(J, '+')) { a = EXP2(ADD, a, multiplicative(J)); goto loop; }
-	if (accept(J, '-')) { a = EXP2(SUB, a, multiplicative(J)); goto loop; }
-	return a;
-}
-
-static js_Ast *shift(js_State *J)
-{
-	js_Ast *a = additive(J);
-loop:
-	if (accept(J, TK_SHL)) { a = EXP2(SHL, a, additive(J)); goto loop; }
-	if (accept(J, TK_SHR)) { a = EXP2(SHR, a, additive(J)); goto loop; }
-	if (accept(J, TK_USHR)) { a = EXP2(USHR, a, additive(J)); goto loop; }
-	return a;
-}
-
-static js_Ast *relational(js_State *J, int notin)
-{
-	js_Ast *a = shift(J);
-loop:
-	if (accept(J, '<')) { a = EXP2(LT, a, shift(J)); goto loop; }
-	if (accept(J, '>')) { a = EXP2(GT, a, shift(J)); goto loop; }
-	if (accept(J, TK_LE)) { a = EXP2(LE, a, shift(J)); goto loop; }
-	if (accept(J, TK_GE)) { a = EXP2(GE, a, shift(J)); goto loop; }
-	if (accept(J, TK_INSTANCEOF)) { a = EXP2(INSTANCEOF, a, shift(J)); goto loop; }
-	if (!notin && accept(J, TK_IN)) { a = EXP2(IN, a, shift(J)); goto loop; }
-	return a;
-}
-
-static js_Ast *equality(js_State *J, int notin)
-{
-	js_Ast *a = relational(J, notin);
-loop:
-	if (accept(J, TK_EQ)) { a = EXP2(EQ, a, relational(J, notin)); goto loop; }
-	if (accept(J, TK_NE)) { a = EXP2(NE, a, relational(J, notin)); goto loop; }
-	if (accept(J, TK_EQ3)) { a = EXP2(EQ3, a, relational(J, notin)); goto loop; }
-	if (accept(J, TK_NE3)) { a = EXP2(NE3, a, relational(J, notin)); goto loop; }
-	return a;
-}
-
-static js_Ast *bitand(js_State *J, int notin)
-{
-	js_Ast *a = equality(J, notin);
-	while (accept(J, '&'))
-		a = EXP2(BITAND, a, equality(J, notin));
-	return a;
-}
-
-static js_Ast *bitxor(js_State *J, int notin)
-{
-	js_Ast *a = bitand(J, notin);
-	while (accept(J, '^'))
-		a = EXP2(BITXOR, a, bitand(J, notin));
-	return a;
-}
-
-static js_Ast *bitor(js_State *J, int notin)
-{
-	js_Ast *a = bitxor(J, notin);
-	while (accept(J, '|'))
-		a = EXP2(BITOR, a, bitxor(J, notin));
-	return a;
-}
-
-static js_Ast *logand(js_State *J, int notin)
-{
-	js_Ast *a = bitor(J, notin);
-	while (accept(J, TK_AND))
-		a = EXP2(LOGAND, a, bitor(J, notin));
-	return a;
-}
-
-static js_Ast *logor(js_State *J, int notin)
-{
-	js_Ast *a = logand(J, notin);
-	while (accept(J, TK_OR))
-		a = EXP2(LOGOR, a, logand(J, notin));
-	return a;
-}
-
-static js_Ast *conditional(js_State *J, int notin)
-{
-	js_Ast *a, *b, *c;
-	a = logor(J, notin);
-	if (accept(J, '?')) {
-		b = assignment(J, notin);
-		expect(J, ':');
-		c = assignment(J, notin);
-		return EXP3(COND, a, b, c);
-	}
-	return a;
-}
-
-static js_Ast *assignment(js_State *J, int notin)
-{
-	js_Ast *a = conditional(J, notin);
-	if (accept(J, '=')) return EXP2(ASS, a, assignment(J, notin));
-	if (accept(J, TK_MUL_ASS)) return EXP2(ASS_MUL, a, assignment(J, notin));
-	if (accept(J, TK_DIV_ASS)) return EXP2(ASS_DIV, a, assignment(J, notin));
-	if (accept(J, TK_MOD_ASS)) return EXP2(ASS_MOD, a, assignment(J, notin));
-	if (accept(J, TK_ADD_ASS)) return EXP2(ASS_ADD, a, assignment(J, notin));
-	if (accept(J, TK_SUB_ASS)) return EXP2(ASS_SUB, a, assignment(J, notin));
-	if (accept(J, TK_SHL_ASS)) return EXP2(ASS_SHL, a, assignment(J, notin));
-	if (accept(J, TK_SHR_ASS)) return EXP2(ASS_SHR, a, assignment(J, notin));
-	if (accept(J, TK_USHR_ASS)) return EXP2(ASS_USHR, a, assignment(J, notin));
-	if (accept(J, TK_AND_ASS)) return EXP2(ASS_BITAND, a, assignment(J, notin));
-	if (accept(J, TK_XOR_ASS)) return EXP2(ASS_BITXOR, a, assignment(J, notin));
-	if (accept(J, TK_OR_ASS)) return EXP2(ASS_BITOR, a, assignment(J, notin));
-	return a;
-}
-
-static js_Ast *expression(js_State *J, int notin)
-{
-	js_Ast *a = assignment(J, notin);
-	while (accept(J, ','))
-		a = EXP2(COMMA, a, assignment(J, notin));
-	return a;
-}
-
-/* Statements */
-
-static js_Ast *vardec(js_State *J, int notin)
-{
-	js_Ast *a = identifier(J);
-	if (accept(J, '='))
-		return EXP2(VAR, a, assignment(J, notin));
-	return EXP1(VAR, a);
-}
-
-static js_Ast *vardeclist(js_State *J, int notin)
-{
-	js_Ast *head, *tail;
-	head = tail = LIST(vardec(J, notin));
-	while (accept(J, ','))
-		tail = tail->b = LIST(vardec(J, notin));
-	return head;
-}
-
-static js_Ast *statementlist(js_State *J)
-{
-	js_Ast *head, *tail;
-	if (J->lookahead == '}' || J->lookahead == TK_CASE || J->lookahead == TK_DEFAULT)
-		return NULL;
-	head = tail = LIST(statement(J));
-	while (J->lookahead != '}' && J->lookahead != TK_CASE && J->lookahead != TK_DEFAULT)
-		tail = tail->b = LIST(statement(J));
-	return head;
-}
-
-static js_Ast *caseclause(js_State *J)
-{
-	js_Ast *a, *b;
-
-	if (accept(J, TK_CASE)) {
-		a = expression(J, 0);
-		expect(J, ':');
-		b = statementlist(J);
-		return STM2(CASE, a, b);
-	}
-
-	if (accept(J, TK_DEFAULT)) {
-		expect(J, ':');
-		a = statementlist(J);
-		return STM1(DEFAULT, a);
-	}
-
-	jsP_error(J, "unexpected token in switch: %s (expected 'case' or 'default')", TOKSTR);
-	return NULL;
-}
-
-static js_Ast *caselist(js_State *J)
-{
-	js_Ast *head, *tail;
-	if (J->lookahead == '}')
-		return NULL;
-	head = tail = LIST(caseclause(J));
-	while (J->lookahead != '}')
-		tail = tail->b = LIST(caseclause(J));
-	return head;
-}
-
-static js_Ast *block(js_State *J)
-{
-	js_Ast *a;
-	expect(J, '{');
-	a = statementlist(J);
-	expect(J, '}');
-	return STM1(BLOCK, a);
-}
-
-static js_Ast *forexpression(js_State *J, int end)
-{
-	js_Ast *a = NULL;
-	if (J->lookahead != end)
-		a = expression(J, 0);
-	expect(J, end);
-	return a;
-}
-
-static js_Ast *forstatement(js_State *J)
-{
-	js_Ast *a, *b, *c, *d;
-	expect(J, '(');
-	if (accept(J, TK_VAR)) {
-		a = vardeclist(J, 1);
-		if (accept(J, ';')) {
-			b = forexpression(J, ';');
-			c = forexpression(J, ')');
-			d = statement(J);
-			return STM4(FOR_VAR, a, b, c, d);
-		}
-		if (accept(J, TK_IN)) {
-			b = expression(J, 0);
-			expect(J, ')');
-			c = statement(J);
-			return STM3(FOR_IN_VAR, a, b, c);
-		}
-		jsP_error(J, "unexpected token in for-var-statement: %s", TOKSTR);
-		return NULL;
-	}
-
-	if (J->lookahead != ';') {
-		a = expression(J, 1);
-	}
-	if (accept(J, ';')) {
-		b = forexpression(J, ';');
-		c = forexpression(J, ')');
-		d = statement(J);
-		return STM4(FOR, a, b, c, d);
-	}
-	if (accept(J, TK_IN)) {
-		b = expression(J, 0);
-		expect(J, ')');
-		c = statement(J);
-		return STM3(FOR_IN, a, b, c);
-	}
-	jsP_error(J, "unexpected token in for-statement: %s", TOKSTR);
-	return NULL;
-}
-
-static js_Ast *statement(js_State *J)
-{
-	js_Ast *a, *b, *c, *d;
-
-	if (J->lookahead == '{') {
-		return block(J);
-	}
-
-	if (accept(J, TK_VAR)) {
-		a = vardeclist(J, 0);
-		semicolon(J);
-		return STM1(VAR, a);
-	}
-
-	/* empty statement */
-	if (accept(J, ';')) {
-		return STM0(NOP);
-	}
-
-	if (accept(J, TK_IF)) {
-		expect(J, '(');
-		a = expression(J, 0);
-		expect(J, ')');
-		b = statement(J);
-		if (accept(J, TK_ELSE))
-			c = statement(J);
-		else
-			c = NULL;
-		return STM3(IF, a, b, c);
-	}
-
-	if (accept(J, TK_DO)) {
-		a = statement(J);
-		expect(J, TK_WHILE);
-		expect(J, '(');
-		b = expression(J, 0);
-		expect(J, ')');
-		semicolon(J);
-		return STM2(DO, a, b);
-	}
-
-	if (accept(J, TK_WHILE)) {
-		expect(J, '(');
-		a = expression(J, 0);
-		expect(J, ')');
-		b = statement(J);
-		return STM2(WHILE, a, b);
-	}
-
-	if (accept(J, TK_FOR)) {
-		return forstatement(J);
-	}
-
-	if (accept(J, TK_CONTINUE)) {
-		a = identifieropt(J);
-		semicolon(J);
-		return STM1(CONTINUE, a);
-	}
-
-	if (accept(J, TK_BREAK)) {
-		a = identifieropt(J);
-		semicolon(J);
-		return STM1(BREAK, a);
-	}
-
-	if (accept(J, TK_RETURN)) {
-		if (J->lookahead != ';' && J->lookahead != '}' && J->lookahead != 0)
-			a = expression(J, 0);
-		else
-			a = NULL;
-		semicolon(J);
-		return STM1(RETURN, a);
-	}
-
-	if (accept(J, TK_WITH)) {
-		expect(J, '(');
-		a = expression(J, 0);
-		expect(J, ')');
-		b = statement(J);
-		return STM2(WITH, a, b);
-	}
-
-	if (accept(J, TK_SWITCH)) {
-		expect(J, '(');
-		a = expression(J, 0);
-		expect(J, ')');
-		expect(J, '{');
-		b = caselist(J);
-		expect(J, '}');
-		return STM2(SWITCH, a, b);
-	}
-
-	if (accept(J, TK_THROW)) {
-		a = expression(J, 0);
-		semicolon(J);
-		return STM1(THROW, a);
-	}
-
-	if (accept(J, TK_TRY)) {
-		a = block(J);
-		b = c = d = NULL;
-		if (accept(J, TK_CATCH)) {
-			expect(J, '(');
-			b = identifier(J);
-			expect(J, ')');
-			c = block(J);
-		}
-		if (accept(J, TK_FINALLY)) {
-			d = block(J);
-		}
-		if (!b && !d)
-			jsP_error(J, "unexpected token in try: %s (expected 'catch' or 'finally')", TOKSTR);
-		return STM4(TRY, a, b, c, d);
-	}
-
-	if (accept(J, TK_DEBUGGER)) {
-		semicolon(J);
-		return STM0(DEBUGGER);
-	}
-
-	/* labelled statement or expression statement */
-	if (J->lookahead == TK_IDENTIFIER) {
-		a = expression(J, 0);
-		if (a->type == AST_IDENTIFIER && accept(J, ':')) {
-			b = statement(J);
-			return STM2(LABEL, a, b);
-		}
-		semicolon(J);
-		return a;
-	}
-
-	/* expression statement */
-	if (J->lookahead != TK_FUNCTION) {
-		a = expression(J, 0);
-		semicolon(J);
-		return a;
-	}
-
-	jsP_error(J, "unexpected token in statement: %s", TOKSTR);
-	return NULL;
-}
-
-/* Program */
-
-static js_Ast *chunkelement(js_State *J)
-{
-	js_Ast *a, *b, *c;
-	if (accept(J, TK_FUNCTION)) {
-		a = identifier(J);
-		expect(J, '(');
-		b = parameters(J);
-		expect(J, ')');
-		c = funcbody(J);
-		return STM3(FUNC, a, b, c);
-	}
-	return statement(J);
-}
-
-static js_Ast *chunklist(js_State *J)
-{
-	js_Ast *head, *tail;
-	if (J->lookahead == '}' || J->lookahead == 0)
-		return NULL;
-	head = tail = LIST(chunkelement(J));
-	while (J->lookahead != '}' && J->lookahead != 0)
-		tail = tail->b = LIST(chunkelement(J));
-	return head;
-}
-
-static js_Ast *funcbody(js_State *J)
-{
-	js_Ast *a;
-	expect(J, '{');
-	a = chunklist(J);
-	expect(J, '}');
-	return a;
-}
-
-int jsP_error(js_State *J, const char *fmt, ...)
-{
-	va_list ap;
-
-	fprintf(stderr, "syntax error: %s:%d: ", J->filename, J->line);
-	va_start(ap, fmt);
-	vfprintf(stderr, fmt, ap);
-	va_end(ap);
-	fprintf(stderr, "\n");
-
-	longjmp(J->jb, 1);
-	return 0;
-}
-
-int jsP_parse(js_State *J, const char *filename, const char *source)
-{
-	jsP_initlex(J, filename, source);
-
-	if (setjmp(J->jb)) {
-		jsP_freeast(J);
-		return 1;
-	}
-
-	next(J);
-	printblock(chunklist(J), 0);
-	putchar('\n');
-
-	// TODO: compile to bytecode
-
-	jsP_freeast(J);
-	return 0;
-}
--- a/js-parse.h
+++ /dev/null
@@ -1,68 +1,0 @@
-#ifndef js_parse_h
-#define js_parse_h
-
-enum {
-	TK_IDENTIFIER = 256,
-	TK_NUMBER,
-	TK_STRING,
-	TK_REGEXP,
-
-	/* multi-character punctuators */
-	TK_LE,
-	TK_GE,
-	TK_EQ,
-	TK_NE,
-	TK_EQ3,
-	TK_NE3,
-	TK_SHL,
-	TK_SHR,
-	TK_USHR,
-	TK_AND,
-	TK_OR,
-	TK_ADD_ASS,
-	TK_SUB_ASS,
-	TK_MUL_ASS,
-	TK_DIV_ASS,
-	TK_MOD_ASS,
-	TK_SHL_ASS,
-	TK_SHR_ASS,
-	TK_USHR_ASS,
-	TK_AND_ASS,
-	TK_OR_ASS,
-	TK_XOR_ASS,
-	TK_INC,
-	TK_DEC,
-
-	/* keywords */
-	TK_BREAK,
-	TK_CASE,
-	TK_CATCH,
-	TK_CONTINUE,
-	TK_DEBUGGER,
-	TK_DEFAULT,
-	TK_DELETE,
-	TK_DO,
-	TK_ELSE,
-	TK_FALSE,
-	TK_FINALLY,
-	TK_FOR,
-	TK_FUNCTION,
-	TK_IF,
-	TK_IN,
-	TK_INSTANCEOF,
-	TK_NEW,
-	TK_NULL,
-	TK_RETURN,
-	TK_SWITCH,
-	TK_THIS,
-	TK_THROW,
-	TK_TRUE,
-	TK_TRY,
-	TK_TYPEOF,
-	TK_VAR,
-	TK_VOID,
-	TK_WHILE,
-	TK_WITH,
-};
-
-#endif
--- a/js-state.c
+++ /dev/null
@@ -1,29 +1,0 @@
-#include "js.h"
-
-js_State *js_newstate(void)
-{
-	js_State *J = malloc(sizeof *J);
-	memset(J, 0, sizeof(*J));
-	return J;
-}
-
-void js_close(js_State *J)
-{
-	free(J->buf.text);
-	free(J);
-}
-
-int js_error(js_State *J, const char *fmt, ...)
-{
-	va_list ap;
-
-	fprintf(stderr, "error: ");
-
-	va_start(ap, fmt);
-	vfprintf(stderr, fmt, ap);
-	va_end(ap);
-
-	fprintf(stderr, "\n");
-
-	return 0;
-}
--- a/js-string.c
+++ /dev/null
@@ -1,107 +1,0 @@
-#include "js.h"
-
-/* Use an AA-tree to quickly look up interned strings. */
-
-struct js_StringNode
-{
-	const char *string;
-	js_StringNode *left, *right;
-	int level;
-};
-
-static js_StringNode sentinel = { "", &sentinel, &sentinel, 0 };
-
-static js_StringNode *makestringnode(const char *string, const char **out)
-{
-	js_StringNode *node = malloc(sizeof(js_StringNode));
-	node->string = *out = strdup(string);
-	node->left = node->right = &sentinel;
-	node->level = 1;
-	return node;
-}
-
-static const char *lookup(js_StringNode *node, const char *string)
-{
-	if (node && node != &sentinel) {
-		int c = strcmp(string, node->string);
-		if (c == 0)
-			return node->string;
-		else if (c < 0)
-			return lookup(node->left, string);
-		else
-			return lookup(node->right, string);
-	}
-	return NULL;
-}
-
-static js_StringNode *skew(js_StringNode *node)
-{
-	if (node->level != 0) {
-		if (node->left->level == node->level) {
-			js_StringNode *save = node;
-			node = node->left;
-			save->left = node->right;
-			node->right = save;
-		}
-		node->right = skew(node->right);
-	}
-	return node;
-}
-
-static js_StringNode *split(js_StringNode *node)
-{
-	if (node->level != 0 && node->right->right->level == node->level) {
-		js_StringNode *save = node;
-		node = node->right;
-		save->right = node->left;
-		node->left = save;
-		node->level++;
-		node->right = split(node->right);
-	}
-	return node;
-}
-
-static js_StringNode *insert(js_StringNode *node, const char *string, const char **out)
-{
-	if (node && node != &sentinel) {
-		int c = strcmp(string, node->string);
-		if (c < 0)
-			node->left = insert(node->left, string, out);
-		else
-			node->right = insert(node->right, string, out);
-		node = skew(node);
-		node = split(node);
-		return node;
-	} else {
-		return makestringnode(string, out);
-	}
-}
-
-static void printstringnode(js_StringNode *node, int level)
-{
-	int i;
-	if (node->left != &sentinel)
-		printstringnode(node->left, level + 1);
-	for (i = 0; i < level; i++)
-		putchar(' ');
-	printf("'%s' (%d)\n", node->string, node->level);
-	if (node->right != &sentinel)
-		printstringnode(node->right, level + 1);
-}
-
-void js_printstringtree(js_State *J)
-{
-	js_StringNode *root = J->strings;
-	printf("--- string dump ---\n");
-	if (root && root != &sentinel)
-		printstringnode(root, 0);
-	printf("---\n");
-}
-
-const char *js_intern(js_State *J, const char *s)
-{
-	const char *a = lookup(J->strings, s);
-	if (!a)
-		J->strings = insert(J->strings, s, &a);
-	return a;
-}
--- a/js.h
+++ b/js.h
@@ -24,42 +24,6 @@
 
 const char *js_intern(js_State *J, const char *s);
 
-/* private */
-
-typedef struct js_Ast js_Ast;
-
-void jsP_initlex(js_State *J, const char *filename, const char *source);
-int jsP_lex(js_State *J);
-const char *jsP_tokenstring(int token);
-int jsP_parse(js_State *J, const char *filename, const char *source);
-int jsP_error(js_State *J, const char *fmt, ...);
-
 void js_printstringtree(js_State *J);
-
-struct js_State
-{
-	jmp_buf jb; /* setjmp buffer for error handling in parser */
-
-	js_StringNode *strings;
-
-	/* input */
-	const char *filename;
-	const char *source;
-	int line;
-
-	/* lexer */
-	struct { char *text; size_t len, cap; } buf;
-	int lasttoken;
-	int newline;
-
-	/* parser */
-	int lookahead;
-	const char *text;
-	double number;
-	struct { char g, i, m; } flags;
-	js_Ast *ast; /* list of allocated nodes to free after parsing */
-
-	int strict;
-};
 
 #endif
--- /dev/null
+++ b/jsintern.c
@@ -1,0 +1,108 @@
+#include "js.h"
+#include "jsstate.h"
+
+/* Use an AA-tree to quickly look up interned strings. */
+
+struct js_StringNode
+{
+	const char *string;
+	js_StringNode *left, *right;
+	int level;
+};
+
+static js_StringNode sentinel = { "", &sentinel, &sentinel, 0 };
+
+static js_StringNode *makestringnode(const char *string, const char **out)
+{
+	js_StringNode *node = malloc(sizeof(js_StringNode));
+	node->string = *out = strdup(string);
+	node->left = node->right = &sentinel;
+	node->level = 1;
+	return node;
+}
+
+static const char *lookup(js_StringNode *node, const char *string)
+{
+	if (node && node != &sentinel) {
+		int c = strcmp(string, node->string);
+		if (c == 0)
+			return node->string;
+		else if (c < 0)
+			return lookup(node->left, string);
+		else
+			return lookup(node->right, string);
+	}
+	return NULL;
+}
+
+static js_StringNode *skew(js_StringNode *node)
+{
+	if (node->level != 0) {
+		if (node->left->level == node->level) {
+			js_StringNode *save = node;
+			node = node->left;
+			save->left = node->right;
+			node->right = save;
+		}
+		node->right = skew(node->right);
+	}
+	return node;
+}
+
+static js_StringNode *split(js_StringNode *node)
+{
+	if (node->level != 0 && node->right->right->level == node->level) {
+		js_StringNode *save = node;
+		node = node->right;
+		save->right = node->left;
+		node->left = save;
+		node->level++;
+		node->right = split(node->right);
+	}
+	return node;
+}
+
+static js_StringNode *insert(js_StringNode *node, const char *string, const char **out)
+{
+	if (node && node != &sentinel) {
+		int c = strcmp(string, node->string);
+		if (c < 0)
+			node->left = insert(node->left, string, out);
+		else
+			node->right = insert(node->right, string, out);
+		node = skew(node);
+		node = split(node);
+		return node;
+	} else {
+		return makestringnode(string, out);
+	}
+}
+
+static void printstringnode(js_StringNode *node, int level)
+{
+	int i;
+	if (node->left != &sentinel)
+		printstringnode(node->left, level + 1);
+	for (i = 0; i < level; i++)
+		putchar(' ');
+	printf("'%s' (%d)\n", node->string, node->level);
+	if (node->right != &sentinel)
+		printstringnode(node->right, level + 1);
+}
+
+void js_printstringtree(js_State *J)
+{
+	js_StringNode *root = J->strings;
+	printf("--- string dump ---\n");
+	if (root && root != &sentinel)
+		printstringnode(root, 0);
+	printf("---\n");
+}
+
+const char *js_intern(js_State *J, const char *s)
+{
+	const char *a = lookup(J->strings, s);
+	if (!a)
+		J->strings = insert(J->strings, s, &a);
+	return a;
+}
--- /dev/null
+++ b/jslex.c
@@ -1,0 +1,609 @@
+#include "js.h"
+#include "jsstate.h"
+#include "jslex.h"
+
+#define nelem(a) (sizeof (a) / sizeof (a)[0])
+
+static const char *tokenstring[] = {
+	"(end-of-file)",
+	"'\\x01'", "'\\x02'", "'\\x03'", "'\\x04'", "'\\x05'", "'\\x06'", "'\\x07'",
+	"'\\x08'", "'\\x09'", "'\\x0A'", "'\\x0B'", "'\\x0C'", "'\\x0D'", "'\\x0E'", "'\\x0F'",
+	"'\\x10'", "'\\x11'", "'\\x12'", "'\\x13'", "'\\x14'", "'\\x15'", "'\\x16'", "'\\x17'",
+	"'\\x18'", "'\\x19'", "'\\x1A'", "'\\x1B'", "'\\x1C'", "'\\x1D'", "'\\x1E'", "'\\x1F'",
+	"' '", "'!'", "'\"'", "'#'", "'$'", "'%'", "'&'", "'\\''",
+	"'('", "')'", "'*'", "'+'", "','", "'-'", "'.'", "'/'",
+	"'0'", "'1'", "'2'", "'3'", "'4'", "'5'", "'6'", "'7'",
+	"'8'", "'9'", "':'", "';'", "'<'", "'='", "'>'", "'?'",
+	"'@'", "'A'", "'B'", "'C'", "'D'", "'E'", "'F'", "'G'",
+	"'H'", "'I'", "'J'", "'K'", "'L'", "'M'", "'N'", "'O'",
+	"'P'", "'Q'", "'R'", "'S'", "'T'", "'U'", "'V'", "'W'",
+	"'X'", "'Y'", "'Z'", "'['", "'\'", "']'", "'^'", "'_'",
+	"'`'", "'a'", "'b'", "'c'", "'d'", "'e'", "'f'", "'g'",
+	"'h'", "'i'", "'j'", "'k'", "'l'", "'m'", "'n'", "'o'",
+	"'p'", "'q'", "'r'", "'s'", "'t'", "'u'", "'v'", "'w'",
+	"'x'", "'y'", "'z'", "'{'", "'|'", "'}'", "'~'", "'\\x7F'",
+
+	0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
+	0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
+	0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
+	0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
+	0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
+	0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
+	0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
+	0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
+
+	"(identifier)", "(number)", "(string)", "(regexp)",
+
+	"'<='", "'>='", "'=='", "'!='", "'==='", "'!=='",
+	"'<<'", "'>>'", "'>>>'", "'&&'", "'||'",
+	"'+='", "'-='", "'*='", "'/='", "'%='",
+	"'<<='", "'>>='", "'>>>='", "'&='", "'|='", "'^='",
+	"'++'", "'--'",
+
+	"'break'", "'case'", "'catch'", "'continue'", "'debugger'",
+	"'default'", "'delete'", "'do'", "'else'", "'false'", "'finally'", "'for'",
+	"'function'", "'if'", "'in'", "'instanceof'", "'new'", "'null'", "'return'",
+	"'switch'", "'this'", "'throw'", "'true'", "'try'", "'typeof'", "'var'",
+	"'void'", "'while'", "'with'",
+};
+
+const char *jsP_tokenstring(int token)
+{
+	if (token >= 0 && token < nelem(tokenstring))
+		if (tokenstring[token])
+			return tokenstring[token];
+	return "<unknown>";
+}
+
+static const char *keywords[] = {
+	"break", "case", "catch", "continue", "debugger", "default", "delete",
+	"do", "else", "false", "finally", "for", "function", "if", "in",
+	"instanceof", "new", "null", "return", "switch", "this", "throw",
+	"true", "try", "typeof", "var", "void", "while", "with",
+};
+
+static const char *futurewords[] = {
+	"class", "const", "enum", "export", "extends", "import", "super",
+};
+
+static const char *strictfuturewords[] = {
+	"implements", "interface", "let", "package", "private", "protected",
+	"public", "static", "yield",
+};
+
+static inline int findword(const char *s, const char **list, int num)
+{
+	int l = 0;
+	int r = num - 1;
+	while (l <= r) {
+		int m = (l + r) >> 1;
+		int c = strcmp(s, list[m]);
+		if (c < 0)
+			r = m - 1;
+		else if (c > 0)
+			l = m + 1;
+		else
+			return m;
+	}
+	return -1;
+}
+
+static inline int findkeyword(js_State *J, const char *s)
+{
+	int i = findword(s, keywords, nelem(keywords));
+	if (i >= 0) {
+		J->text = keywords[i];
+		return TK_BREAK + i; /* first keyword + i */
+	}
+
+	if (findword(s, futurewords, nelem(futurewords)) >= 0)
+		return jsP_error(J, "'%s' is a future reserved word", s);
+	if (J->strict && findword(s, strictfuturewords, nelem(strictfuturewords)) >= 0)
+		return jsP_error(J, "'%s' is a strict mode future reserved word", s);
+
+	J->text = js_intern(J, s);
+	return TK_IDENTIFIER;
+}
+
+#define GET() (*(*sp)++)
+#define UNGET() ((*sp)--)
+#define PEEK() (**sp)
+#define NEXT() ((*sp)++)
+#define NEXTPEEK() (NEXT(), PEEK())
+#define LOOK(x) (PEEK() == x ? (NEXT(), 1) : 0)
+
+static void textinit(js_State *J)
+{
+	if (!J->buf.text) {
+		J->buf.cap = 4096;
+		J->buf.text = malloc(J->buf.cap);
+	}
+	J->buf.len = 0;
+}
+
+static inline void textpush(js_State *J, int c)
+{
+	if (J->buf.len >= J->buf.cap) {
+		J->buf.cap = J->buf.cap * 2;
+		J->buf.text = realloc(J->buf.text, J->buf.cap);
+	}
+	J->buf.text[J->buf.len++] = c;
+}
+
+static inline char *textend(js_State *J)
+{
+	textpush(J, 0);
+	return J->buf.text;
+}
+
+static inline int iswhite(int c)
+{
+	return c == 0x9 || c == 0xB || c == 0xC || c == 0x20 || c == 0xA0 || c == 0xFEFF;
+}
+
+static inline int isnewline(c)
+{
+	return c == 0xA || c == 0xD || c == 0x2028 || c == 0x2029;
+}
+
+static inline int isidentifierstart(int c)
+{
+	return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '$' || c == '_';
+}
+
+static inline int isidentifierpart(int c)
+{
+	return (c >= '0' && c <= '9') || isidentifierstart(c);
+}
+
+static inline int isdec(int c)
+{
+	return (c >= '0' && c <= '9');
+}
+
+static inline int ishex(int c)
+{
+	return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
+}
+
+static inline int tohex(int c)
+{
+	if (c >= '0' && c <= '9')
+		return c - '0';
+	if (c >= 'a' && c <= 'f')
+		return c - 'a' + 0xA;
+	if (c >= 'A' && c <= 'F')
+		return c - 'A' + 0xA;
+	return 0;
+}
+
+static inline void lexlinecomment(const char **sp)
+{
+	int c = PEEK();
+	while (c && !isnewline(c)) {
+		c = NEXTPEEK();
+	}
+}
+
+static inline int lexcomment(const char **sp)
+{
+	while (1) {
+		int c = GET();
+		if (c == '*') {
+			while (c == '*')
+				c = GET();
+			if (c == '/')
+				return 0;
+		} else if (c == 0) {
+			return -1;
+		}
+	}
+}
+
+static inline double lexhex(const char **sp)
+{
+	double n = 0;
+	int c = PEEK();
+	while (ishex(c)) {
+		n = n * 16 + tohex(c);
+		c = NEXTPEEK();
+	}
+	return n;
+}
+
+static inline double lexinteger(const char **sp)
+{
+	double n = 0;
+	int c = PEEK();
+	while (isdec(c)) {
+		n = n * 10 + (c - '0');
+		c = NEXTPEEK();
+	}
+	return n;
+}
+
+static inline double lexfraction(const char **sp)
+{
+	double n = 0;
+	double d = 1;
+	int c = PEEK();
+	while (isdec(c)) {
+		n = n * 10 + (c - '0');
+		d = d * 10;
+		c = NEXTPEEK();
+	}
+	return n / d;
+}
+
+static inline double lexexponent(const char **sp)
+{
+	if (LOOK('e') || LOOK('E')) {
+		if (LOOK('-'))
+			return -lexinteger(sp);
+		else if (LOOK('+'))
+			return lexinteger(sp);
+		else
+			return lexinteger(sp);
+	}
+	return 0;
+}
+
+static inline int lexnumber(js_State *J, const char **sp)
+{
+	double n;
+
+	if ((*sp)[0] == '0' && ((*sp)[1] == 'x' || (*sp)[1] == 'X')) {
+		*sp += 2;
+		if (!ishex(PEEK()))
+			return jsP_error(J, "0x not followed by hexademical digit");
+		J->number = lexhex(sp);
+		return TK_NUMBER;
+	}
+
+	if ((*sp)[0] == '0' && isdec((*sp)[1]))
+		return jsP_error(J, "number with leading zero");
+
+	n = lexinteger(sp);
+	if (LOOK('.'))
+		n += lexfraction(sp);
+	n *= pow(10, lexexponent(sp));
+
+	if (isidentifierstart(PEEK()))
+		return jsP_error(J, "number with letter suffix");
+
+	J->number = n;
+	return TK_NUMBER;
+}
+
+static inline int lexescape(js_State *J, const char **sp)
+{
+	int c = GET();
+	int x = 0;
+
+	if (isnewline(c)) {
+		if (c == '\r' && PEEK() == '\n')
+			NEXT();
+		return 0;
+	}
+
+	switch (c) {
+	case 'u':
+		if (!ishex(PEEK())) return 1; else x |= NEXTPEEK() << 12;
+		if (!ishex(PEEK())) return 1; else x |= NEXTPEEK() << 8;
+		if (!ishex(PEEK())) return 1; else x |= NEXTPEEK() << 4;
+		if (!ishex(PEEK())) return 1; else x |= NEXTPEEK();
+		textpush(J, x);
+		break;
+	case 'x':
+		if (!ishex(PEEK())) return 1; else x |= NEXTPEEK() << 4;
+		if (!ishex(PEEK())) return 1; else x |= NEXTPEEK();
+		textpush(J, x);
+		break;
+	case '0': textpush(J, 0); break;
+	case '\\': textpush(J, '\\'); break;
+	case '\'': textpush(J, '\''); break;
+	case '"': textpush(J, '"'); break;
+	case 'b': textpush(J, '\b'); break;
+	case 'f': textpush(J, '\f'); break;
+	case 'n': textpush(J, '\n'); break;
+	case 'r': textpush(J, '\r'); break;
+	case 't': textpush(J, '\t'); break;
+	case 'v': textpush(J, '\v'); break;
+	default: textpush(J, c); break;
+	}
+	return 0;
+}
+
+static inline int lexstring(js_State *J, const char **sp, int q)
+{
+	const char *s;
+	int c = GET();
+
+	textinit(J);
+
+	while (c != q) {
+		if (c == 0 || isnewline(c))
+			return jsP_error(J, "string not terminated");
+		if (c == '\\') {
+			if (lexescape(J, sp))
+				return jsP_error(J, "malformed escape sequence");
+		} else {
+			textpush(J, c);
+		}
+		c = GET();
+	}
+
+	s = textend(J);
+
+	J->text = js_intern(J, s);
+	return TK_STRING;
+}
+
+/* the ugliest language wart ever... */
+static int isregexpcontext(int last)
+{
+	switch (last) {
+	case ']':
+	case ')':
+	case TK_IDENTIFIER:
+	case TK_NUMBER:
+	case TK_STRING:
+	case TK_FALSE:
+	case TK_NULL:
+	case TK_THIS:
+	case TK_TRUE:
+		return 0;
+	default:
+		return 1;
+	}
+}
+
+static int lexregexp(js_State *J, const char **sp)
+{
+	const char *s;
+	int c;
+
+	textinit(J);
+
+	/* regexp body */
+	c = GET();
+	while (c != '/') {
+		if (c == 0 || isnewline(c)) {
+			return jsP_error(J, "regular expression not terminated");
+		} else if (c == '\\') {
+			textpush(J, c);
+			c = GET();
+			if (c == 0 || isnewline(c))
+				return jsP_error(J, "regular expression not terminated");
+			textpush(J, c);
+			c = GET();
+		} else {
+			textpush(J, c);
+			c = GET();
+		}
+	}
+
+	s = textend(J);
+
+	/* regexp flags */
+	J->flags.g = J->flags.i = J->flags.m = 0;
+
+	c = PEEK();
+	while (isidentifierpart(c)) {
+		if (c == 'g') J->flags.g ++;
+		else if (c == 'i') J->flags.i ++;
+		else if (c == 'm') J->flags.m ++;
+		else return jsP_error(J, "illegal flag in regular expression: %c", c);
+		c = NEXTPEEK();
+	}
+
+	if (J->flags.g > 1 || J->flags.i > 1 || J->flags.m > 1)
+		return jsP_error(J, "duplicated flag in regular expression");
+
+	J->text = js_intern(J, s);
+	return TK_REGEXP;
+}
+
+/* simple "return [no Line Terminator here] ..." contexts */
+static inline int isnlthcontext(int last)
+{
+	switch (last) {
+	case TK_BREAK:
+	case TK_CONTINUE:
+	case TK_RETURN:
+	case TK_THROW:
+		return 1;
+	default:
+		return 0;
+	}
+}
+
+static int lex(js_State *J, const char **sp)
+{
+	J->newline = 0;
+
+	while (1) {
+		int c = GET();
+
+		while (iswhite(c))
+			c = GET();
+
+		if (isnewline(c)) {
+			/* consume CR LF as one unit */
+			if (c == '\r' && PEEK() == '\n')
+				NEXT();
+			J->line++;
+			J->newline = 1;
+			if (isnlthcontext(J->lasttoken))
+				return ';';
+			continue;
+		}
+
+		if (c == '/') {
+			if (LOOK('/')) {
+				lexlinecomment(sp);
+				continue;
+			} else if (LOOK('*')) {
+				if (lexcomment(sp))
+					return jsP_error(J, "multi-line comment not terminated");
+				continue;
+			} else if (isregexpcontext(J->lasttoken)) {
+				return lexregexp(J, sp);
+			} else if (LOOK('=')) {
+				return TK_DIV_ASS;
+			} else {
+				return '/';
+			}
+		}
+
+		if (isidentifierstart(c)) {
+			textinit(J);
+			textpush(J, c);
+
+			c = PEEK();
+			while (isidentifierpart(c)) {
+				textpush(J, c);
+				c = NEXTPEEK();
+			}
+
+			textend(J);
+
+			return findkeyword(J, J->buf.text);
+		}
+
+		if (c >= '0' && c <= '9') {
+			UNGET();
+			return lexnumber(J, sp);
+		}
+
+		switch (c) {
+		case '(':
+		case ')':
+		case ',':
+		case ':':
+		case ';':
+		case '?':
+		case '[':
+		case ']':
+		case '{':
+		case '}':
+		case '~':
+			return c;
+
+		case '\'':
+		case '"':
+			return lexstring(J, sp, c);
+
+		case '.':
+			if (isdec(PEEK())) {
+				UNGET();
+				return lexnumber(J, sp);
+			}
+			return '.';
+
+		case '<':
+			if (LOOK('<')) {
+				if (LOOK('='))
+					return TK_SHL_ASS;
+				return TK_SHL;
+			}
+			if (LOOK('='))
+				return TK_LE;
+			return '<';
+
+		case '>':
+			if (LOOK('>')) {
+				if (LOOK('>')) {
+					if (LOOK('='))
+						return TK_USHR_ASS;
+					return TK_USHR;
+				}
+				if (LOOK('='))
+					return TK_SHR_ASS;
+				return TK_SHR;
+			}
+			if (LOOK('='))
+				return TK_GE;
+			return '>';
+
+		case '=':
+			if (LOOK('=')) {
+				if (LOOK('='))
+					return TK_EQ3;
+				return TK_EQ;
+			}
+			return '=';
+
+		case '!':
+			if (LOOK('=')) {
+				if (LOOK('='))
+					return TK_NE3;
+				return TK_NE;
+			}
+			return '!';
+
+		case '+':
+			if (LOOK('+'))
+				return TK_INC;
+			if (LOOK('='))
+				return TK_ADD_ASS;
+			return '+';
+
+		case '-':
+			if (LOOK('-'))
+				return TK_DEC;
+			if (LOOK('='))
+				return TK_SUB_ASS;
+			return '-';
+
+		case '*':
+			if (LOOK('='))
+				return TK_MUL_ASS;
+			return '*';
+
+		case '%':
+			if (LOOK('='))
+				return TK_MOD_ASS;
+			return '%';
+
+		case '&':
+			if (LOOK('&'))
+				return TK_AND;
+			if (LOOK('='))
+				return TK_AND_ASS;
+			return '&';
+
+		case '|':
+			if (LOOK('|'))
+				return TK_OR;
+			if (LOOK('='))
+				return TK_OR_ASS;
+			return '|';
+
+		case '^':
+			if (LOOK('='))
+				return TK_XOR_ASS;
+			return '^';
+
+		case 0:
+			return 0; /* EOF */
+		}
+
+		if (c >= 0x20 && c <= 0x7E)
+			return jsP_error(J, "unexpected character: '%c'", c);
+		return jsP_error(J, "unexpected character: \\u%04X", c);
+	}
+}
+
+void jsP_initlex(js_State *J, const char *filename, const char *source)
+{
+	J->filename = filename;
+	J->source = source;
+	J->line = 1;
+	J->lasttoken = 0;
+}
+
+int jsP_lex(js_State *J)
+{
+	return J->lasttoken = lex(J, &J->source);
+}
--- /dev/null
+++ b/jslex.h
@@ -1,0 +1,73 @@
+#ifndef js_lex_h
+#define js_lex_h
+
+enum {
+	TK_IDENTIFIER = 256,
+	TK_NUMBER,
+	TK_STRING,
+	TK_REGEXP,
+
+	/* multi-character punctuators */
+	TK_LE,
+	TK_GE,
+	TK_EQ,
+	TK_NE,
+	TK_EQ3,
+	TK_NE3,
+	TK_SHL,
+	TK_SHR,
+	TK_USHR,
+	TK_AND,
+	TK_OR,
+	TK_ADD_ASS,
+	TK_SUB_ASS,
+	TK_MUL_ASS,
+	TK_DIV_ASS,
+	TK_MOD_ASS,
+	TK_SHL_ASS,
+	TK_SHR_ASS,
+	TK_USHR_ASS,
+	TK_AND_ASS,
+	TK_OR_ASS,
+	TK_XOR_ASS,
+	TK_INC,
+	TK_DEC,
+
+	/* keywords */
+	TK_BREAK,
+	TK_CASE,
+	TK_CATCH,
+	TK_CONTINUE,
+	TK_DEBUGGER,
+	TK_DEFAULT,
+	TK_DELETE,
+	TK_DO,
+	TK_ELSE,
+	TK_FALSE,
+	TK_FINALLY,
+	TK_FOR,
+	TK_FUNCTION,
+	TK_IF,
+	TK_IN,
+	TK_INSTANCEOF,
+	TK_NEW,
+	TK_NULL,
+	TK_RETURN,
+	TK_SWITCH,
+	TK_THIS,
+	TK_THROW,
+	TK_TRUE,
+	TK_TRY,
+	TK_TYPEOF,
+	TK_VAR,
+	TK_VOID,
+	TK_WHILE,
+	TK_WITH,
+};
+
+void jsP_initlex(js_State *J, const char *filename, const char *source);
+int jsP_lex(js_State *J);
+const char *jsP_tokenstring(int token);
+int jsP_error(js_State *J, const char *fmt, ...);
+
+#endif
--- /dev/null
+++ b/jsload.c
@@ -1,0 +1,48 @@
+#include "js.h"
+#include "jsparse.h"
+
+static int jsP_loadstring(js_State *J, const char *filename, const char *source)
+{
+	return jsP_parse(J, filename, source);
+}
+
+int js_loadstring(js_State *J, const char *source)
+{
+	return jsP_loadstring(J, "(string)", source);
+}
+
+int js_loadfile(js_State *J, const char *filename)
+{
+	FILE *f;
+	char *s;
+	int n, t;
+
+	f = fopen(filename, "r");
+	if (!f)
+		return js_error(J, "cannot open file: '%s'", filename);
+
+	fseek(f, 0, SEEK_END);
+	n = ftell(f);
+	fseek(f, 0, SEEK_SET);
+
+	s = malloc(n + 1); /* add space for string terminator */
+	if (!s) {
+		fclose(f);
+		return js_error(J, "cannot allocate storage for file contents: '%s'", filename);
+	}
+
+	t = fread(s, 1, n, f);
+	if (t != n) {
+		free(s);
+		fclose(f);
+		return js_error(J, "cannot read data from file: '%s'", filename);
+	}
+
+	s[n] = 0; /* zero-terminate string containing file data */
+
+	t = jsP_loadstring(J, filename, s);
+
+	free(s);
+	fclose(f);
+	return t;
+}
--- /dev/null
+++ b/jsparse.c
@@ -1,0 +1,761 @@
+#include "js.h"
+#include "jsstate.h"
+#include "jslex.h"
+#include "jsparse.h"
+
+#define LIST(h)		jsP_newnode(J, AST_LIST, h, 0, 0, 0);
+
+#define EXP0(x)		jsP_newnode(J, EXP_ ## x, 0, 0, 0, 0)
+#define EXP1(x,a)	jsP_newnode(J, EXP_ ## x, a, 0, 0, 0)
+#define EXP2(x,a,b)	jsP_newnode(J, EXP_ ## x, a, b, 0, 0)
+#define EXP3(x,a,b,c)	jsP_newnode(J, EXP_ ## x, a, b, c, 0)
+
+#define STM0(x)		jsP_newnode(J, STM_ ## x, 0, 0, 0, 0)
+#define STM1(x,a)	jsP_newnode(J, STM_ ## x, a, 0, 0, 0)
+#define STM2(x,a,b)	jsP_newnode(J, STM_ ## x, a, b, 0, 0)
+#define STM3(x,a,b,c)	jsP_newnode(J, STM_ ## x, a, b, c, 0)
+#define STM4(x,a,b,c,d)	jsP_newnode(J, STM_ ## x, a, b, c, d)
+
+#define TOKSTR		jsP_tokenstring(J->lookahead)
+
+static js_Ast *expression(js_State *J, int notin);
+static js_Ast *assignment(js_State *J, int notin);
+static js_Ast *memberexp(js_State *J);
+static js_Ast *statement(js_State *J);
+static js_Ast *funcbody(js_State *J);
+
+static inline void next(js_State *J)
+{
+	J->lookahead = jsP_lex(J);
+}
+
+static inline int accept(js_State *J, int t)
+{
+	if (J->lookahead == t) {
+		next(J);
+		return 1;
+	}
+	return 0;
+}
+
+static inline void expect(js_State *J, int t)
+{
+	if (accept(J, t))
+		return;
+	jsP_error(J, "unexpected token: %s (expected %s)", TOKSTR, jsP_tokenstring(t));
+}
+
+static void semicolon(js_State *J)
+{
+	if (J->lookahead == ';') {
+		next(J);
+		return;
+	}
+	if (J->newline || J->lookahead == '}' || J->lookahead == 0)
+		return;
+	jsP_error(J, "unexpected token: %s (expected ';')", TOKSTR);
+}
+
+/* Literals */
+
+static js_Ast *identifier(js_State *J)
+{
+	if (J->lookahead == TK_IDENTIFIER) {
+		js_Ast *a = jsP_newstrnode(J, AST_IDENTIFIER, J->text);
+		next(J);
+		return a;
+	}
+	jsP_error(J, "unexpected token: %s (expected identifier)", TOKSTR);
+	return NULL;
+}
+
+static js_Ast *identifieropt(js_State *J)
+{
+	if (J->lookahead == TK_IDENTIFIER)
+		return identifier(J);
+	return NULL;
+}
+
+static js_Ast *identifiername(js_State *J)
+{
+	if (J->lookahead == TK_IDENTIFIER || J->lookahead >= TK_BREAK) {
+		js_Ast *a = jsP_newstrnode(J, AST_IDENTIFIER, J->text);
+		next(J);
+		return a;
+	}
+	jsP_error(J, "unexpected token: %s (expected identifier or keyword)", TOKSTR);
+	return NULL;
+}
+
+static js_Ast *arrayelement(js_State *J)
+{
+	if (J->lookahead == ',')
+		return EXP0(NULL); /* TODO: should be 'undefined' */
+	return assignment(J, 0);
+}
+
+static js_Ast *arrayliteral(js_State *J)
+{
+	js_Ast *head, *tail;
+	if (J->lookahead == ']')
+		return NULL;
+	head = tail = LIST(arrayelement(J));
+	while (accept(J, ',')) {
+		if (J->lookahead != ']')
+			tail = tail->b = LIST(arrayelement(J));
+	}
+	return head;
+}
+
+static js_Ast *propname(js_State *J)
+{
+	js_Ast *name;
+	if (J->lookahead == TK_NUMBER) {
+		name = jsP_newnumnode(J, AST_NUMBER, J->number);
+		next(J);
+	} else if (J->lookahead == TK_STRING) {
+		name = jsP_newstrnode(J, AST_STRING, J->text);
+		next(J);
+	} else {
+		name = identifiername(J);
+	}
+	return name;
+}
+
+static js_Ast *propassign(js_State *J)
+{
+	js_Ast *name, *value, *arg, *body;
+
+	if (J->lookahead == TK_IDENTIFIER && !strcmp(J->text, "get")) {
+		next(J);
+		name = propname(J);
+		expect(J, '(');
+		expect(J, ')');
+		body = funcbody(J);
+		return EXP2(PROP_GET, name, body);
+	}
+
+	if (J->lookahead == TK_IDENTIFIER && !strcmp(J->text, "set")) {
+		next(J);
+		name = propname(J);
+		expect(J, '(');
+		arg = identifier(J);
+		expect(J, ')');
+		body = funcbody(J);
+		return EXP3(PROP_SET, name, arg, body);
+	}
+
+	name = propname(J);
+	expect(J, ':');
+	value = assignment(J, 0);
+	return EXP2(PROP_VAL, name, value);
+}
+
+static js_Ast *objectliteral(js_State *J)
+{
+	js_Ast *head, *tail;
+	if (J->lookahead == '}')
+		return NULL;
+	head = tail = LIST(propassign(J));
+	while (accept(J, ',')) {
+		if (J->lookahead == '}')
+			break;
+		tail = tail->b = LIST(propassign(J));
+	}
+	return head;
+}
+
+/* Expressions */
+
+static js_Ast *primary(js_State *J)
+{
+	js_Ast *a;
+
+	if (J->lookahead == TK_IDENTIFIER) {
+		a = jsP_newstrnode(J, AST_IDENTIFIER, J->text);
+		next(J);
+		return a;
+	}
+	if (J->lookahead == TK_STRING) {
+		a = jsP_newstrnode(J, AST_STRING, J->text);
+		next(J);
+		return a;
+	}
+	if (J->lookahead == TK_REGEXP) {
+		a = jsP_newstrnode(J, AST_REGEXP, J->text);
+		// TODO: flags
+		next(J);
+		return a;
+	}
+	if (J->lookahead == TK_NUMBER) {
+		a = jsP_newnumnode(J, AST_NUMBER, J->number);
+		next(J);
+		return a;
+	}
+
+	if (accept(J, TK_THIS)) return EXP0(THIS);
+	if (accept(J, TK_NULL)) return EXP0(NULL);
+	if (accept(J, TK_TRUE)) return EXP0(TRUE);
+	if (accept(J, TK_FALSE)) return EXP0(FALSE);
+	if (accept(J, '{')) { a = EXP1(OBJECT, objectliteral(J)); expect(J, '}'); return a; }
+	if (accept(J, '[')) { a = EXP1(ARRAY, arrayliteral(J)); expect(J, ']'); return a; }
+	if (accept(J, '(')) { a = expression(J, 0); expect(J, ')'); return a; }
+
+	jsP_error(J, "unexpected token in expression: %s", TOKSTR);
+	return NULL;
+}
+
+static js_Ast *arguments(js_State *J)
+{
+	js_Ast *head, *tail;
+	if (J->lookahead == ')')
+		return NULL;
+	head = tail = LIST(assignment(J, 0));
+	while (accept(J, ',')) {
+		tail = tail->b = LIST(assignment(J, 0));
+	}
+	return head;
+}
+
+static js_Ast *parameters(js_State *J)
+{
+	js_Ast *head, *tail;
+	if (J->lookahead == ')')
+		return NULL;
+	head = tail = LIST(identifier(J));
+	while (accept(J, ',')) {
+		tail = tail->b = LIST(identifier(J));
+	}
+	return head;
+}
+
+static js_Ast *newexp(js_State *J)
+{
+	js_Ast *a, *b, *c;
+
+	if (accept(J, TK_NEW)) {
+		a = memberexp(J);
+		if (accept(J, '(')) {
+			b = arguments(J);
+			expect(J, ')');
+			return EXP2(NEW, a, b);
+		}
+		return EXP1(NEW, a);
+	}
+
+	if (accept(J, TK_FUNCTION)) {
+		a = identifieropt(J);
+		expect(J, '(');
+		b = parameters(J);
+		expect(J, ')');
+		c = funcbody(J);
+		return EXP3(FUNC, a, b, c);
+	}
+
+	return primary(J);
+}
+
+static js_Ast *memberexp(js_State *J)
+{
+	js_Ast *a = newexp(J);
+loop:
+	if (accept(J, '.')) { a = EXP2(MEMBER, a, identifiername(J)); goto loop; }
+	if (accept(J, '[')) { a = EXP2(INDEX, a, expression(J, 0)); expect(J, ']'); goto loop; }
+	return a;
+}
+
+static js_Ast *callexp(js_State *J)
+{
+	js_Ast *a = newexp(J);
+loop:
+	if (accept(J, '.')) { a = EXP2(MEMBER, a, identifiername(J)); goto loop; }
+	if (accept(J, '[')) { a = EXP2(INDEX, a, expression(J, 0)); expect(J, ']'); goto loop; }
+	if (accept(J, '(')) { a = EXP2(CALL, a, arguments(J)); expect(J, ')'); goto loop; }
+	return a;
+}
+
+static js_Ast *postfix(js_State *J)
+{
+	js_Ast *a = callexp(J);
+	if (!J->newline && accept(J, TK_INC)) return EXP1(POSTINC, a);
+	if (!J->newline && accept(J, TK_DEC)) return EXP1(POSTDEC, a);
+	return a;
+}
+
+static js_Ast *unary(js_State *J)
+{
+	if (accept(J, TK_DELETE)) return EXP1(DELETE, unary(J));
+	if (accept(J, TK_VOID)) return EXP1(VOID, unary(J));
+	if (accept(J, TK_TYPEOF)) return EXP1(TYPEOF, unary(J));
+	if (accept(J, TK_INC)) return EXP1(PREINC, unary(J));
+	if (accept(J, TK_DEC)) return EXP1(PREDEC, unary(J));
+	if (accept(J, '+')) return EXP1(POS, unary(J));
+	if (accept(J, '-')) return EXP1(NEG, unary(J));
+	if (accept(J, '~')) return EXP1(BITNOT, unary(J));
+	if (accept(J, '!')) return EXP1(LOGNOT, unary(J));
+	return postfix(J);
+}
+
+static js_Ast *multiplicative(js_State *J)
+{
+	js_Ast *a = unary(J);
+loop:
+	if (accept(J, '*')) { a = EXP2(MUL, a, unary(J)); goto loop; }
+	if (accept(J, '/')) { a = EXP2(DIV, a, unary(J)); goto loop; }
+	if (accept(J, '%')) { a = EXP2(MOD, a, unary(J)); goto loop; }
+	return a;
+}
+
+static js_Ast *additive(js_State *J)
+{
+	js_Ast *a = multiplicative(J);
+loop:
+	if (accept(J, '+')) { a = EXP2(ADD, a, multiplicative(J)); goto loop; }
+	if (accept(J, '-')) { a = EXP2(SUB, a, multiplicative(J)); goto loop; }
+	return a;
+}
+
+static js_Ast *shift(js_State *J)
+{
+	js_Ast *a = additive(J);
+loop:
+	if (accept(J, TK_SHL)) { a = EXP2(SHL, a, additive(J)); goto loop; }
+	if (accept(J, TK_SHR)) { a = EXP2(SHR, a, additive(J)); goto loop; }
+	if (accept(J, TK_USHR)) { a = EXP2(USHR, a, additive(J)); goto loop; }
+	return a;
+}
+
+static js_Ast *relational(js_State *J, int notin)
+{
+	js_Ast *a = shift(J);
+loop:
+	if (accept(J, '<')) { a = EXP2(LT, a, shift(J)); goto loop; }
+	if (accept(J, '>')) { a = EXP2(GT, a, shift(J)); goto loop; }
+	if (accept(J, TK_LE)) { a = EXP2(LE, a, shift(J)); goto loop; }
+	if (accept(J, TK_GE)) { a = EXP2(GE, a, shift(J)); goto loop; }
+	if (accept(J, TK_INSTANCEOF)) { a = EXP2(INSTANCEOF, a, shift(J)); goto loop; }
+	if (!notin && accept(J, TK_IN)) { a = EXP2(IN, a, shift(J)); goto loop; }
+	return a;
+}
+
+static js_Ast *equality(js_State *J, int notin)
+{
+	js_Ast *a = relational(J, notin);
+loop:
+	if (accept(J, TK_EQ)) { a = EXP2(EQ, a, relational(J, notin)); goto loop; }
+	if (accept(J, TK_NE)) { a = EXP2(NE, a, relational(J, notin)); goto loop; }
+	if (accept(J, TK_EQ3)) { a = EXP2(EQ3, a, relational(J, notin)); goto loop; }
+	if (accept(J, TK_NE3)) { a = EXP2(NE3, a, relational(J, notin)); goto loop; }
+	return a;
+}
+
+static js_Ast *bitand(js_State *J, int notin)
+{
+	js_Ast *a = equality(J, notin);
+	while (accept(J, '&'))
+		a = EXP2(BITAND, a, equality(J, notin));
+	return a;
+}
+
+static js_Ast *bitxor(js_State *J, int notin)
+{
+	js_Ast *a = bitand(J, notin);
+	while (accept(J, '^'))
+		a = EXP2(BITXOR, a, bitand(J, notin));
+	return a;
+}
+
+static js_Ast *bitor(js_State *J, int notin)
+{
+	js_Ast *a = bitxor(J, notin);
+	while (accept(J, '|'))
+		a = EXP2(BITOR, a, bitxor(J, notin));
+	return a;
+}
+
+static js_Ast *logand(js_State *J, int notin)
+{
+	js_Ast *a = bitor(J, notin);
+	while (accept(J, TK_AND))
+		a = EXP2(LOGAND, a, bitor(J, notin));
+	return a;
+}
+
+static js_Ast *logor(js_State *J, int notin)
+{
+	js_Ast *a = logand(J, notin);
+	while (accept(J, TK_OR))
+		a = EXP2(LOGOR, a, logand(J, notin));
+	return a;
+}
+
+static js_Ast *conditional(js_State *J, int notin)
+{
+	js_Ast *a, *b, *c;
+	a = logor(J, notin);
+	if (accept(J, '?')) {
+		b = assignment(J, notin);
+		expect(J, ':');
+		c = assignment(J, notin);
+		return EXP3(COND, a, b, c);
+	}
+	return a;
+}
+
+static js_Ast *assignment(js_State *J, int notin)
+{
+	js_Ast *a = conditional(J, notin);
+	if (accept(J, '=')) return EXP2(ASS, a, assignment(J, notin));
+	if (accept(J, TK_MUL_ASS)) return EXP2(ASS_MUL, a, assignment(J, notin));
+	if (accept(J, TK_DIV_ASS)) return EXP2(ASS_DIV, a, assignment(J, notin));
+	if (accept(J, TK_MOD_ASS)) return EXP2(ASS_MOD, a, assignment(J, notin));
+	if (accept(J, TK_ADD_ASS)) return EXP2(ASS_ADD, a, assignment(J, notin));
+	if (accept(J, TK_SUB_ASS)) return EXP2(ASS_SUB, a, assignment(J, notin));
+	if (accept(J, TK_SHL_ASS)) return EXP2(ASS_SHL, a, assignment(J, notin));
+	if (accept(J, TK_SHR_ASS)) return EXP2(ASS_SHR, a, assignment(J, notin));
+	if (accept(J, TK_USHR_ASS)) return EXP2(ASS_USHR, a, assignment(J, notin));
+	if (accept(J, TK_AND_ASS)) return EXP2(ASS_BITAND, a, assignment(J, notin));
+	if (accept(J, TK_XOR_ASS)) return EXP2(ASS_BITXOR, a, assignment(J, notin));
+	if (accept(J, TK_OR_ASS)) return EXP2(ASS_BITOR, a, assignment(J, notin));
+	return a;
+}
+
+static js_Ast *expression(js_State *J, int notin)
+{
+	js_Ast *a = assignment(J, notin);
+	while (accept(J, ','))
+		a = EXP2(COMMA, a, assignment(J, notin));
+	return a;
+}
+
+/* Statements */
+
+static js_Ast *vardec(js_State *J, int notin)
+{
+	js_Ast *a = identifier(J);
+	if (accept(J, '='))
+		return EXP2(VAR, a, assignment(J, notin));
+	return EXP1(VAR, a);
+}
+
+static js_Ast *vardeclist(js_State *J, int notin)
+{
+	js_Ast *head, *tail;
+	head = tail = LIST(vardec(J, notin));
+	while (accept(J, ','))
+		tail = tail->b = LIST(vardec(J, notin));
+	return head;
+}
+
+static js_Ast *statementlist(js_State *J)
+{
+	js_Ast *head, *tail;
+	if (J->lookahead == '}' || J->lookahead == TK_CASE || J->lookahead == TK_DEFAULT)
+		return NULL;
+	head = tail = LIST(statement(J));
+	while (J->lookahead != '}' && J->lookahead != TK_CASE && J->lookahead != TK_DEFAULT)
+		tail = tail->b = LIST(statement(J));
+	return head;
+}
+
+static js_Ast *caseclause(js_State *J)
+{
+	js_Ast *a, *b;
+
+	if (accept(J, TK_CASE)) {
+		a = expression(J, 0);
+		expect(J, ':');
+		b = statementlist(J);
+		return STM2(CASE, a, b);
+	}
+
+	if (accept(J, TK_DEFAULT)) {
+		expect(J, ':');
+		a = statementlist(J);
+		return STM1(DEFAULT, a);
+	}
+
+	jsP_error(J, "unexpected token in switch: %s (expected 'case' or 'default')", TOKSTR);
+	return NULL;
+}
+
+static js_Ast *caselist(js_State *J)
+{
+	js_Ast *head, *tail;
+	if (J->lookahead == '}')
+		return NULL;
+	head = tail = LIST(caseclause(J));
+	while (J->lookahead != '}')
+		tail = tail->b = LIST(caseclause(J));
+	return head;
+}
+
+static js_Ast *block(js_State *J)
+{
+	js_Ast *a;
+	expect(J, '{');
+	a = statementlist(J);
+	expect(J, '}');
+	return STM1(BLOCK, a);
+}
+
+static js_Ast *forexpression(js_State *J, int end)
+{
+	js_Ast *a = NULL;
+	if (J->lookahead != end)
+		a = expression(J, 0);
+	expect(J, end);
+	return a;
+}
+
+static js_Ast *forstatement(js_State *J)
+{
+	js_Ast *a, *b, *c, *d;
+	expect(J, '(');
+	if (accept(J, TK_VAR)) {
+		a = vardeclist(J, 1);
+		if (accept(J, ';')) {
+			b = forexpression(J, ';');
+			c = forexpression(J, ')');
+			d = statement(J);
+			return STM4(FOR_VAR, a, b, c, d);
+		}
+		if (accept(J, TK_IN)) {
+			b = expression(J, 0);
+			expect(J, ')');
+			c = statement(J);
+			return STM3(FOR_IN_VAR, a, b, c);
+		}
+		jsP_error(J, "unexpected token in for-var-statement: %s", TOKSTR);
+		return NULL;
+	}
+
+	if (J->lookahead != ';') {
+		a = expression(J, 1);
+	}
+	if (accept(J, ';')) {
+		b = forexpression(J, ';');
+		c = forexpression(J, ')');
+		d = statement(J);
+		return STM4(FOR, a, b, c, d);
+	}
+	if (accept(J, TK_IN)) {
+		b = expression(J, 0);
+		expect(J, ')');
+		c = statement(J);
+		return STM3(FOR_IN, a, b, c);
+	}
+	jsP_error(J, "unexpected token in for-statement: %s", TOKSTR);
+	return NULL;
+}
+
+static js_Ast *statement(js_State *J)
+{
+	js_Ast *a, *b, *c, *d;
+
+	if (J->lookahead == '{') {
+		return block(J);
+	}
+
+	if (accept(J, TK_VAR)) {
+		a = vardeclist(J, 0);
+		semicolon(J);
+		return STM1(VAR, a);
+	}
+
+	/* empty statement */
+	if (accept(J, ';')) {
+		return STM0(NOP);
+	}
+
+	if (accept(J, TK_IF)) {
+		expect(J, '(');
+		a = expression(J, 0);
+		expect(J, ')');
+		b = statement(J);
+		if (accept(J, TK_ELSE))
+			c = statement(J);
+		else
+			c = NULL;
+		return STM3(IF, a, b, c);
+	}
+
+	if (accept(J, TK_DO)) {
+		a = statement(J);
+		expect(J, TK_WHILE);
+		expect(J, '(');
+		b = expression(J, 0);
+		expect(J, ')');
+		semicolon(J);
+		return STM2(DO, a, b);
+	}
+
+	if (accept(J, TK_WHILE)) {
+		expect(J, '(');
+		a = expression(J, 0);
+		expect(J, ')');
+		b = statement(J);
+		return STM2(WHILE, a, b);
+	}
+
+	if (accept(J, TK_FOR)) {
+		return forstatement(J);
+	}
+
+	if (accept(J, TK_CONTINUE)) {
+		a = identifieropt(J);
+		semicolon(J);
+		return STM1(CONTINUE, a);
+	}
+
+	if (accept(J, TK_BREAK)) {
+		a = identifieropt(J);
+		semicolon(J);
+		return STM1(BREAK, a);
+	}
+
+	if (accept(J, TK_RETURN)) {
+		if (J->lookahead != ';' && J->lookahead != '}' && J->lookahead != 0)
+			a = expression(J, 0);
+		else
+			a = NULL;
+		semicolon(J);
+		return STM1(RETURN, a);
+	}
+
+	if (accept(J, TK_WITH)) {
+		expect(J, '(');
+		a = expression(J, 0);
+		expect(J, ')');
+		b = statement(J);
+		return STM2(WITH, a, b);
+	}
+
+	if (accept(J, TK_SWITCH)) {
+		expect(J, '(');
+		a = expression(J, 0);
+		expect(J, ')');
+		expect(J, '{');
+		b = caselist(J);
+		expect(J, '}');
+		return STM2(SWITCH, a, b);
+	}
+
+	if (accept(J, TK_THROW)) {
+		a = expression(J, 0);
+		semicolon(J);
+		return STM1(THROW, a);
+	}
+
+	if (accept(J, TK_TRY)) {
+		a = block(J);
+		b = c = d = NULL;
+		if (accept(J, TK_CATCH)) {
+			expect(J, '(');
+			b = identifier(J);
+			expect(J, ')');
+			c = block(J);
+		}
+		if (accept(J, TK_FINALLY)) {
+			d = block(J);
+		}
+		if (!b && !d)
+			jsP_error(J, "unexpected token in try: %s (expected 'catch' or 'finally')", TOKSTR);
+		return STM4(TRY, a, b, c, d);
+	}
+
+	if (accept(J, TK_DEBUGGER)) {
+		semicolon(J);
+		return STM0(DEBUGGER);
+	}
+
+	/* labelled statement or expression statement */
+	if (J->lookahead == TK_IDENTIFIER) {
+		a = expression(J, 0);
+		if (a->type == AST_IDENTIFIER && accept(J, ':')) {
+			b = statement(J);
+			return STM2(LABEL, a, b);
+		}
+		semicolon(J);
+		return a;
+	}
+
+	/* expression statement */
+	if (J->lookahead != TK_FUNCTION) {
+		a = expression(J, 0);
+		semicolon(J);
+		return a;
+	}
+
+	jsP_error(J, "unexpected token in statement: %s", TOKSTR);
+	return NULL;
+}
+
+/* Program */
+
+static js_Ast *chunkelement(js_State *J)
+{
+	js_Ast *a, *b, *c;
+	if (accept(J, TK_FUNCTION)) {
+		a = identifier(J);
+		expect(J, '(');
+		b = parameters(J);
+		expect(J, ')');
+		c = funcbody(J);
+		return STM3(FUNC, a, b, c);
+	}
+	return statement(J);
+}
+
+static js_Ast *chunklist(js_State *J)
+{
+	js_Ast *head, *tail;
+	if (J->lookahead == '}' || J->lookahead == 0)
+		return NULL;
+	head = tail = LIST(chunkelement(J));
+	while (J->lookahead != '}' && J->lookahead != 0)
+		tail = tail->b = LIST(chunkelement(J));
+	return head;
+}
+
+static js_Ast *funcbody(js_State *J)
+{
+	js_Ast *a;
+	expect(J, '{');
+	a = chunklist(J);
+	expect(J, '}');
+	return a;
+}
+
+int jsP_error(js_State *J, const char *fmt, ...)
+{
+	va_list ap;
+
+	fprintf(stderr, "syntax error: %s:%d: ", J->filename, J->line);
+	va_start(ap, fmt);
+	vfprintf(stderr, fmt, ap);
+	va_end(ap);
+	fprintf(stderr, "\n");
+
+	longjmp(J->jb, 1);
+	return 0;
+}
+
+int jsP_parse(js_State *J, const char *filename, const char *source)
+{
+	jsP_initlex(J, filename, source);
+
+	if (setjmp(J->jb)) {
+		jsP_freeast(J);
+		return 1;
+	}
+
+	next(J);
+	printblock(chunklist(J), 0);
+	putchar('\n');
+
+	// TODO: compile to bytecode
+
+	jsP_freeast(J);
+	return 0;
+}
--- /dev/null
+++ b/jsparse.h
@@ -1,0 +1,134 @@
+#ifndef js_parse_h
+#define js_parse_h
+
+typedef struct js_Ast js_Ast;
+
+struct js_Ast
+{
+	int type;
+	int line;
+	js_Ast *a, *b, *c, *d;
+	double n;
+	const char *s;
+	js_Ast *next; /* next in alloc list */
+};
+
+enum
+{
+	AST_LIST,
+
+	AST_IDENTIFIER,
+	AST_NUMBER,
+	AST_STRING,
+	AST_REGEXP,
+
+	/* literals */
+	EXP_NULL,
+	EXP_TRUE,
+	EXP_FALSE,
+	EXP_THIS,
+
+	EXP_ARRAY,
+	EXP_OBJECT,
+	EXP_PROP_VAL,
+	EXP_PROP_GET,
+	EXP_PROP_SET,
+
+	/* expressions */
+	EXP_INDEX,
+	EXP_MEMBER,
+	EXP_CALL,
+	EXP_NEW,
+	EXP_FUNC, /* function expression */
+
+	EXP_DELETE,
+	EXP_VOID,
+	EXP_TYPEOF,
+	EXP_PREINC,
+	EXP_PREDEC,
+	EXP_POSTINC,
+	EXP_POSTDEC,
+	EXP_POS,
+	EXP_NEG,
+	EXP_BITNOT,
+	EXP_LOGNOT,
+	EXP_LOGOR,
+
+	EXP_LOGAND,
+	EXP_BITOR,
+	EXP_BITXOR,
+	EXP_BITAND,
+	EXP_EQ,
+	EXP_NE,
+	EXP_EQ3,
+	EXP_NE3,
+	EXP_LT,
+	EXP_GT,
+	EXP_LE,
+	EXP_GE,
+	EXP_INSTANCEOF,
+	EXP_IN,
+	EXP_SHL,
+	EXP_SHR,
+	EXP_USHR,
+	EXP_ADD,
+	EXP_SUB,
+	EXP_MUL,
+	EXP_DIV,
+	EXP_MOD,
+
+	EXP_COND,
+
+	EXP_ASS,
+	EXP_ASS_MUL,
+	EXP_ASS_DIV,
+	EXP_ASS_MOD,
+	EXP_ASS_ADD,
+	EXP_ASS_SUB,
+	EXP_ASS_SHL,
+	EXP_ASS_SHR,
+	EXP_ASS_USHR,
+	EXP_ASS_BITAND,
+	EXP_ASS_BITXOR,
+	EXP_ASS_BITOR,
+
+	EXP_COMMA,
+
+	EXP_VAR, /* var initializer */
+
+	/* statements */
+	STM_BLOCK,
+	STM_FUNC, /* function declaration */
+	STM_NOP,
+	STM_VAR,
+	STM_IF,
+	STM_DO,
+	STM_WHILE,
+	STM_FOR,
+	STM_FOR_VAR,
+	STM_FOR_IN,
+	STM_FOR_IN_VAR,
+	STM_CONTINUE,
+	STM_BREAK,
+	STM_RETURN,
+	STM_WITH,
+	STM_SWITCH,
+	STM_THROW,
+	STM_TRY,
+	STM_LABEL,
+	STM_CASE,
+	STM_DEFAULT,
+	STM_DEBUGGER,
+};
+
+int jsP_parse(js_State *J, const char *filename, const char *source);
+
+js_Ast *jsP_newnode(js_State *J, int type, js_Ast *a, js_Ast *b, js_Ast *c, js_Ast *d);
+js_Ast *jsP_newstrnode(js_State *J, int type, const char *s);
+js_Ast *jsP_newnumnode(js_State *J, int type, double n);
+void jsP_freeast(js_State *J);
+
+void printast(js_Ast *n, int level);
+void printblock(js_Ast *n, int level);
+
+#endif
--- /dev/null
+++ b/jspretty.c
@@ -1,0 +1,555 @@
+#include "js.h"
+#include "jsparse.h"
+#include "jsstate.h"
+
+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));
+
+	node->type = type;
+	node->line = J->line;
+	node->a = a;
+	node->b = b;
+	node->c = c;
+	node->d = d;
+	node->n = 0;
+	node->s = NULL;
+
+	node->next = J->ast;
+	J->ast = node;
+
+	return node;
+}
+
+js_Ast *jsP_newstrnode(js_State *J, int type, const char *s)
+{
+	js_Ast *node = jsP_newnode(J, type, 0, 0, 0, 0);
+	node->s = s;
+	return node;
+}
+
+js_Ast *jsP_newnumnode(js_State *J, int type, double n)
+{
+	js_Ast *node = jsP_newnode(J, type, 0, 0, 0, 0);
+	node->n = n;
+	return node;
+}
+
+void jsP_freeast(js_State *J)
+{
+	js_Ast *node = J->ast;
+	while (node) {
+		js_Ast *next = node->next;
+		free(node);
+		node = next;
+	}
+	J->ast = NULL;
+}
+
+static const char *strast(int type)
+{
+	switch (type) {
+	case AST_LIST: return "LIST";
+	case AST_IDENTIFIER: return "IDENTIFIER";
+	case AST_NUMBER: return "NUMBER";
+	case AST_STRING: return "STRING";
+	case AST_REGEXP: return "REGEXP";
+	case EXP_VAR: return "VAR";
+	case EXP_NULL: return "NULL";
+	case EXP_TRUE: return "TRUE";
+	case EXP_FALSE: return "FALSE";
+	case EXP_THIS: return "THIS";
+	case EXP_ARRAY: return "ARRAY";
+	case EXP_OBJECT: return "OBJECT";
+	case EXP_PROP_VAL: return "PROP_VAL";
+	case EXP_PROP_GET: return "PROP_GET";
+	case EXP_PROP_SET: return "PROP_SET";
+	case EXP_INDEX: return "INDEX";
+	case EXP_MEMBER: return "MEMBER";
+	case EXP_NEW: return "new";
+	case EXP_CALL: return "CALL";
+	case EXP_FUNC: return "function";
+	case EXP_COND: return "?:";
+	case EXP_COMMA: return ",";
+	case EXP_DELETE: return "delete";
+	case EXP_VOID: return "void";
+	case EXP_TYPEOF: return "typeof";
+	case EXP_PREINC: return "PRE++";
+	case EXP_PREDEC: return "PRE--";
+	case EXP_POSTINC: return "POST++";
+	case EXP_POSTDEC: return "POST--";
+	case EXP_POS: return "+";
+	case EXP_NEG: return "-";
+	case EXP_BITNOT: return "~";
+	case EXP_LOGNOT: return "!";
+	case EXP_LOGOR: return "||";
+	case EXP_LOGAND: return "&&";
+	case EXP_BITOR: return "|";
+	case EXP_BITXOR: return "^";
+	case EXP_BITAND: return "&";
+	case EXP_EQ: return "==";
+	case EXP_NE: return "!=";
+	case EXP_EQ3: return "===";
+	case EXP_NE3: return "!==";
+	case EXP_LT: return "<";
+	case EXP_GT: return ">";
+	case EXP_LE: return "<=";
+	case EXP_GE: return ">=";
+	case EXP_INSTANCEOF: return "instanceof";
+	case EXP_IN: return "in";
+	case EXP_SHL: return "<<";
+	case EXP_SHR: return ">>";
+	case EXP_USHR: return ">>>";
+	case EXP_ADD: return "+";
+	case EXP_SUB: return "-";
+	case EXP_MUL: return "*";
+	case EXP_DIV: return "/";
+	case EXP_MOD: return "%";
+	case EXP_ASS: return "=";
+	case EXP_ASS_MUL: return "*=";
+	case EXP_ASS_DIV: return "/=";
+	case EXP_ASS_MOD: return "%=";
+	case EXP_ASS_ADD: return "+=";
+	case EXP_ASS_SUB: return "-=";
+	case EXP_ASS_SHL: return "<<=";
+	case EXP_ASS_SHR: return ">>=";
+	case EXP_ASS_USHR: return ">>>=";
+	case EXP_ASS_BITAND: return "&=";
+	case EXP_ASS_BITXOR: return "^=";
+	case EXP_ASS_BITOR: return "|=";
+	case STM_BLOCK: return "BLOCK";
+	case STM_FUNC: return "function-decl";
+	case STM_NOP: return "NOP";
+	case STM_VAR: return "var";
+	case STM_IF: return "if";
+	case STM_DO: return "do-while";
+	case STM_WHILE: return "while";
+	case STM_FOR: return "for";
+	case STM_FOR_VAR: return "for_var";
+	case STM_FOR_IN: return "for_in";
+	case STM_FOR_IN_VAR: return "for_in_var";
+	case STM_CONTINUE: return "continue";
+	case STM_BREAK: return "break";
+	case STM_RETURN: return "return";
+	case STM_WITH: return "with";
+	case STM_SWITCH: return "switch";
+	case STM_THROW: return "throw";
+	case STM_TRY: return "try";
+	case STM_LABEL: return "label";
+	case STM_CASE: return "case";
+	case STM_DEFAULT: return "default";
+	case STM_DEBUGGER: return "debugger";
+	default: return "(unknown)";
+	}
+}
+
+static void indent(int level)
+{
+	while (level--)
+		putchar('\t');
+}
+
+static void printlist(js_Ast *n, int level, const char *sep)
+{
+	while (n) {
+		printast(n->a, level);
+		n = n->b;
+		if (n)
+			fputs(sep, stdout);
+	}
+}
+
+void printblock(js_Ast *n, int level)
+{
+	while (n) {
+		indent(level);
+		printast(n->a, level);
+		if (n->a->type < STM_BLOCK) // expression
+			putchar(';');
+		n = n->b;
+		if (n)
+			putchar('\n');
+	}
+}
+
+static void printstm(js_Ast *n, int level)
+{
+	if (n->type == STM_BLOCK) {
+		printf(" {\n");
+		printblock(n->a, level + 1);
+		putchar('\n');
+		indent(level);
+		printf("}");
+	} else {
+		putchar('\n');
+		indent(level + 1);
+		printast(n, level + 1);
+		if (n->type < STM_BLOCK) // expression
+			putchar(';');
+	}
+}
+
+static void printunary(int level, js_Ast *n, const char *pre, const char *suf)
+{
+	printf(pre);
+	printast(n, level);
+	printf(suf);
+}
+
+static void printbinary(int level, js_Ast *a, js_Ast *b, const char *op)
+{
+	printf("(");
+	printast(a, level);
+	printf(" %s ", op);
+	printast(b, level);
+	printf(")");
+}
+
+void printast(js_Ast *n, int level)
+{
+	switch (n->type) {
+	case AST_IDENTIFIER: printf("%s", n->s); return;
+	case AST_NUMBER: printf("%g", n->n); return;
+	case AST_STRING: printf("'%s'", n->s); return;
+	case AST_REGEXP: printf("/%s/", n->s); return;
+	case AST_LIST:
+		putchar('[');
+		printlist(n, level, " ");
+		putchar(']');
+		break;
+
+	case STM_BLOCK:
+		putchar('{');
+		putchar('\n');
+		printblock(n->a, level + 1);
+		putchar('\n');
+		indent(level);
+		putchar('}');
+		break;
+
+	case STM_FOR:
+		printf("for (");
+		printast(n->a, level); printf("; ");
+		printast(n->b, level); printf("; ");
+		printast(n->c, level); printf(")");
+		printstm(n->d, level);
+		break;
+	case STM_FOR_VAR:
+		printf("for (var ");
+		printlist(n->a, level, ", "); printf("; ");
+		printast(n->b, level); printf("; ");
+		printast(n->c, level); printf(")");
+		printstm(n->d, level);
+		break;
+	case STM_FOR_IN:
+		printf("for (");
+		printast(n->a, level); printf(" in ");
+		printast(n->b, level); printf(")");
+		printstm(n->c, level);
+		break;
+	case STM_FOR_IN_VAR:
+		printf("for (var ");
+		printlist(n->a, level, ", "); printf(" in ");
+		printast(n->b, level); printf(")");
+		printstm(n->c, level);
+		break;
+
+	case STM_NOP:
+		putchar(';');
+		break;
+
+	case STM_VAR:
+		printf("var ");
+		printlist(n->a, level, ", ");
+		putchar(';');
+		break;
+
+	case EXP_VAR:
+		printast(n->a, level);
+		if (n->b) {
+			printf(" = ");
+			printast(n->b, level);
+		}
+		break;
+
+	case STM_IF:
+		printf("if (");
+		printast(n->a, level);
+		printf(")");
+		printstm(n->b, level);
+		if (n->c) {
+			putchar('\n');
+			indent(level);
+			printf("else");
+			printstm(n->c, level);
+		}
+		break;
+
+	case STM_DO:
+		printf("do");
+		printstm(n->a, level);
+		if (n->a->type == STM_BLOCK) {
+			putchar(' ');
+		} else {
+			putchar('\n');
+			indent(level);
+		}
+		printf("while (");
+		printast(n->b, level);
+		printf(");");
+		break;
+
+	case STM_WHILE:
+		printf("while (");
+		printast(n->a, level);
+		printf(")");
+		printstm(n->b, level);
+		break;
+
+	case STM_CONTINUE:
+		if (n->a) {
+			printf("continue ");
+			printast(n->a, level);
+			printf(";");
+		} else {
+			printf("continue;");
+		}
+		break;
+
+	case STM_BREAK:
+		if (n->a) {
+			printf("break ");
+			printast(n->a, level);
+			printf(";");
+		} else {
+			printf("break;");
+		}
+		break;
+
+	case STM_RETURN:
+		if (n->a) {
+			printf("return ");
+			printast(n->a, level);
+			printf(";");
+		} else {
+			printf("return;");
+		}
+		break;
+
+	case STM_THROW:
+		printf("throw ");
+		printast(n->a, level);
+		printf(";");
+		break;
+
+	case STM_SWITCH:
+		printf("switch (");
+		printast(n->a, level);
+		printf(") {\n");
+		printblock(n->b, level);
+		putchar('\n');
+		indent(level);
+		printf("}");
+		break;
+
+	case STM_CASE:
+		printf("case ");
+		printast(n->a, level);
+		printf(":");
+		if (n->b) {
+			printf("\n");
+			printblock(n->b, level + 1);
+		}
+		break;
+
+	case STM_DEFAULT:
+		printf("default:");
+		if (n->a) {
+			printf("\n");
+			printblock(n->a, level + 1);
+		}
+		break;
+
+	case STM_LABEL:
+		printast(n->a, level);
+		printf(":");
+		printstm(n->b, level - 1);
+		break;
+
+	case STM_WITH:
+		printf("with (");
+		printast(n->a, level);
+		printf(")");
+		printstm(n->b, level);
+		break;
+
+	case STM_TRY:
+		printf("try");
+		printstm(n->a, level);
+		if (n->b && n->c) {
+			printf(" catch (");
+			printast(n->b, level);
+			printf(")");
+			printstm(n->c, level);
+		}
+		if (n->d) {
+			printf(" finally");
+			printstm(n->d, level);
+		}
+		break;
+
+	case STM_DEBUGGER:
+		printf("debugger");
+		break;
+
+	case STM_FUNC:
+		printf("function ");
+		printast(n->a, level);
+		printf("(");
+		printlist(n->b, level, ", ");
+		printf(")\n");
+		indent(level);
+		printf("{\n");
+		printblock(n->c, level + 1);
+		printf("\n");
+		indent(level);
+		printf("}");
+		break;
+
+	case EXP_FUNC:
+		printf("(function ");
+		if (n->a)
+			printast(n->a, level);
+		printf("(");
+		printlist(n->b, level, ", ");
+		printf(") {\n");
+		printblock(n->c, level + 1);
+		printf("\n");
+		indent(level);
+		printf("})");
+		break;
+
+	case EXP_OBJECT:
+		printf("{ ");
+		printlist(n->a, level, ", ");
+		printf(" }");
+		break;
+
+	case EXP_PROP_VAL:
+		printast(n->a, level);
+		printf(": ");
+		printast(n->b, level);
+		break;
+
+	case EXP_ARRAY:
+		printf("[ ");
+		printlist(n->a, level, ", ");
+		printf(" ]");
+		break;
+
+	case EXP_NEW:
+		printf("(new ");
+		printast(n->a, level);
+		printf("(");
+		printlist(n->b, level, ", ");
+		printf("))");
+		break;
+
+	case EXP_CALL:
+		printf("(");
+		printast(n->a, level);
+		printf("(");
+		printlist(n->b, level, ", ");
+		printf("))");
+		break;
+
+	case EXP_MEMBER:
+		printf("(");
+		printast(n->a, level);
+		printf(".");
+		printast(n->b, level);
+		printf(")");
+		break;
+
+	case EXP_INDEX:
+		printf("(");
+		printast(n->a, level);
+		printf("[");
+		printast(n->b, level);
+		printf("])");
+		break;
+
+	case EXP_COND:
+		printf("(");
+		printast(n->a, level);
+		printf(" ? ");
+		printast(n->b, level);
+		printf(" : ");
+		printast(n->c, level);
+		printf(")");
+		break;
+
+	case EXP_NULL: printf("null"); break;
+	case EXP_TRUE: printf("true"); break;
+	case EXP_FALSE: printf("false"); break;
+	case EXP_THIS: printf("this"); break;
+
+	case EXP_DELETE:	printunary(level, n->a, "(delete ", ")"); break;
+	case EXP_VOID:		printunary(level, n->a, "(void ", ")"); break;
+	case EXP_TYPEOF:	printunary(level, n->a, "(typeof ", ")"); break;
+	case EXP_PREINC:	printunary(level, n->a, "(++", ")"); break;
+	case EXP_PREDEC:	printunary(level, n->a, "(--", ")"); break;
+	case EXP_POSTINC:	printunary(level, n->a, "(", "++)"); break;
+	case EXP_POSTDEC:	printunary(level, n->a, "(", "--)"); break;
+	case EXP_POS:		printunary(level, n->a, "(+", ")"); break;
+	case EXP_NEG:		printunary(level, n->a, "(-", ")"); break;
+	case EXP_BITNOT:	printunary(level, n->a, "(~", ")"); break;
+	case EXP_LOGNOT:	printunary(level, n->a, "(!", ")"); break;
+
+	case EXP_COMMA:		printbinary(level, n->a, n->b, ","); break;
+	case EXP_LOGOR:		printbinary(level, n->a, n->b, "||"); break;
+	case EXP_LOGAND:	printbinary(level, n->a, n->b, "&&"); break;
+	case EXP_BITOR:		printbinary(level, n->a, n->b, "|"); break;
+	case EXP_BITXOR:	printbinary(level, n->a, n->b, "^"); break;
+	case EXP_BITAND:	printbinary(level, n->a, n->b, "&"); break;
+	case EXP_EQ:		printbinary(level, n->a, n->b, "=="); break;
+	case EXP_NE:		printbinary(level, n->a, n->b, "!="); break;
+	case EXP_EQ3:		printbinary(level, n->a, n->b, "==="); break;
+	case EXP_NE3:		printbinary(level, n->a, n->b, "!=="); break;
+	case EXP_LT:		printbinary(level, n->a, n->b, "<"); break;
+	case EXP_GT:		printbinary(level, n->a, n->b, ">"); break;
+	case EXP_LE:		printbinary(level, n->a, n->b, "<="); break;
+	case EXP_GE:		printbinary(level, n->a, n->b, ">="); break;
+	case EXP_INSTANCEOF:	printbinary(level, n->a, n->b, "instanceof"); break;
+	case EXP_IN:		printbinary(level, n->a, n->b, "in"); break;
+	case EXP_SHL:		printbinary(level, n->a, n->b, "<<"); break;
+	case EXP_SHR:		printbinary(level, n->a, n->b, ">>"); break;
+	case EXP_USHR:		printbinary(level, n->a, n->b, ">>>"); break;
+	case EXP_ADD:		printbinary(level, n->a, n->b, "+"); break;
+	case EXP_SUB:		printbinary(level, n->a, n->b, "-"); break;
+	case EXP_MUL:		printbinary(level, n->a, n->b, "*"); break;
+	case EXP_DIV:		printbinary(level, n->a, n->b, "/"); break;
+	case EXP_MOD:		printbinary(level, n->a, n->b, "%"); break;
+	case EXP_ASS:		printbinary(level, n->a, n->b, "="); break;
+	case EXP_ASS_MUL:	printbinary(level, n->a, n->b, "*="); break;
+	case EXP_ASS_DIV:	printbinary(level, n->a, n->b, "/="); break;
+	case EXP_ASS_MOD:	printbinary(level, n->a, n->b, "%="); break;
+	case EXP_ASS_ADD:	printbinary(level, n->a, n->b, "+="); break;
+	case EXP_ASS_SUB:	printbinary(level, n->a, n->b, "-="); break;
+	case EXP_ASS_SHL:	printbinary(level, n->a, n->b, "<<="); break;
+	case EXP_ASS_SHR:	printbinary(level, n->a, n->b, ">>="); break;
+	case EXP_ASS_USHR:	printbinary(level, n->a, n->b, ">>>="); break;
+	case EXP_ASS_BITAND:	printbinary(level, n->a, n->b, "&="); break;
+	case EXP_ASS_BITXOR:	printbinary(level, n->a, n->b, "^="); break;
+	case EXP_ASS_BITOR:	printbinary(level, n->a, n->b, "|="); break;
+
+	default:
+		printf("(%s", strast(n->type));
+		if (n->a) { putchar(' '); printast(n->a, level); }
+		if (n->b) { putchar(' '); printast(n->b, level); }
+		if (n->c) { putchar(' '); printast(n->c, level); }
+		if (n->d) { putchar(' '); printast(n->d, level); }
+		putchar(')');
+		break;
+	}
+}
--- /dev/null
+++ b/jsstate.c
@@ -1,0 +1,30 @@
+#include "js.h"
+#include "jsstate.h"
+
+js_State *js_newstate(void)
+{
+	js_State *J = malloc(sizeof *J);
+	memset(J, 0, sizeof(*J));
+	return J;
+}
+
+void js_close(js_State *J)
+{
+	free(J->buf.text);
+	free(J);
+}
+
+int js_error(js_State *J, const char *fmt, ...)
+{
+	va_list ap;
+
+	fprintf(stderr, "error: ");
+
+	va_start(ap, fmt);
+	vfprintf(stderr, fmt, ap);
+	va_end(ap);
+
+	fprintf(stderr, "\n");
+
+	return 0;
+}
--- /dev/null
+++ b/jsstate.h
@@ -1,0 +1,32 @@
+#ifndef js_state_h
+#define js_state_h
+
+typedef struct js_Ast js_Ast;
+
+struct js_State
+{
+	jmp_buf jb; /* setjmp buffer for error handling in parser */
+
+	js_StringNode *strings;
+
+	/* input */
+	const char *filename;
+	const char *source;
+	int line;
+
+	/* lexer */
+	struct { char *text; size_t len, cap; } buf;
+	int lasttoken;
+	int newline;
+
+	/* parser */
+	int lookahead;
+	const char *text;
+	double number;
+	struct { char g, i, m; } flags;
+	js_Ast *ast; /* list of allocated nodes to free after parsing */
+
+	int strict;
+};
+
+#endif