shithub: libmujs

Download patch

ref: dd149b98bd600ce2673d9123bacbafa6b58080af
parent: d28346577753a99119c9a19b7020e7edc91c44be
author: Tor Andersson <tor.andersson@artifex.com>
date: Fri Dec 2 09:54:27 EST 2022

Split debug printing of bytecode etc into "pp" tool.

--- a/Makefile
+++ b/Makefile
@@ -63,16 +63,17 @@
 static: $(OUT)/libmujs.a
 shared: $(OUT)/libmujs.$(SO_EXT)
 
-astnames.h: jsparse.h
-	grep -E '(AST|EXP|STM)_' jsparse.h | sed 's/^[^A-Z]*\(AST_\)*/"/;s/,.*/",/' | tr A-Z a-z > $@
+astnames.h: jsi.h
+	grep -E '\<(AST|EXP|STM)_' jsi.h | sed 's/^[^A-Z]*\(AST_\)*/"/;s/,.*/",/' | tr A-Z a-z > $@
 
-opnames.h: jscompile.h
-	grep -E 'OP_' jscompile.h | sed 's/^[^A-Z]*OP_/"/;s/,.*/",/' | tr A-Z a-z > $@
+opnames.h: jsi.h
+	grep -E '\<OP_' jsi.h | sed 's/^[^A-Z]*OP_/"/;s/,.*/",/' | tr A-Z a-z > $@
 
+
 one.c: $(SRCS)
 	ls $(SRCS) | awk '{print "#include \""$$1"\""}' > $@
 
-jsdump.c: astnames.h opnames.h
+pp.c: astnames.h opnames.h
 
 $(OUT)/%.o: %.c $(HDRS)
 	@ mkdir -p $(@D)
--- a/jscompile.c
+++ b/jscompile.c
@@ -775,7 +775,7 @@
 		break;
 
 	default:
-		jsC_error(J, exp, "unknown expression: (%s)", jsP_aststring(exp->type));
+		jsC_error(J, exp, "unknown expression type");
 	}
 }
 
--- a/jsdump.c
+++ /dev/null
@@ -1,939 +1,0 @@
-#include "jsi.h"
-
-#include "utf.h"
-
-#include <assert.h>
-
-static const char *astname[] = {
-#include "astnames.h"
-NULL
-};
-
-static const char *opname[] = {
-#include "opnames.h"
-NULL
-};
-
-static int minify = 0;
-
-const char *jsP_aststring(enum js_AstType type)
-{
-	if (type < nelem(astname)-1)
-		return astname[type];
-	return "<unknown>";
-}
-
-const char *jsC_opcodestring(enum js_OpCode opcode)
-{
-	if (opcode < nelem(opname)-1)
-		return opname[opcode];
-	return "<unknown>";
-}
-
-static int prec(enum js_AstType type)
-{
-	switch (type) {
-	case AST_IDENTIFIER:
-	case EXP_IDENTIFIER:
-	case EXP_NUMBER:
-	case EXP_STRING:
-	case EXP_REGEXP:
-	case EXP_UNDEF:
-	case EXP_NULL:
-	case EXP_TRUE:
-	case EXP_FALSE:
-	case EXP_THIS:
-	case EXP_ARRAY:
-	case EXP_OBJECT:
-		return 170;
-
-	case EXP_FUN:
-	case EXP_INDEX:
-	case EXP_MEMBER:
-	case EXP_CALL:
-	case EXP_NEW:
-		return 160;
-
-	case EXP_POSTINC:
-	case EXP_POSTDEC:
-		return 150;
-
-	case EXP_DELETE:
-	case EXP_VOID:
-	case EXP_TYPEOF:
-	case EXP_PREINC:
-	case EXP_PREDEC:
-	case EXP_POS:
-	case EXP_NEG:
-	case EXP_BITNOT:
-	case EXP_LOGNOT:
-		return 140;
-
-	case EXP_MOD:
-	case EXP_DIV:
-	case EXP_MUL:
-		return 130;
-
-	case EXP_SUB:
-	case EXP_ADD:
-		return 120;
-
-	case EXP_USHR:
-	case EXP_SHR:
-	case EXP_SHL:
-		return 110;
-
-	case EXP_IN:
-	case EXP_INSTANCEOF:
-	case EXP_GE:
-	case EXP_LE:
-	case EXP_GT:
-	case EXP_LT:
-		return 100;
-
-	case EXP_STRICTNE:
-	case EXP_STRICTEQ:
-	case EXP_NE:
-	case EXP_EQ:
-		return 90;
-
-	case EXP_BITAND: return 80;
-	case EXP_BITXOR: return 70;
-	case EXP_BITOR: return 60;
-	case EXP_LOGAND: return 50;
-	case EXP_LOGOR: return 40;
-
-	case EXP_COND:
-		return 30;
-
-	case EXP_ASS:
-	case EXP_ASS_MUL:
-	case EXP_ASS_DIV:
-	case EXP_ASS_MOD:
-	case EXP_ASS_ADD:
-	case EXP_ASS_SUB:
-	case EXP_ASS_SHL:
-	case EXP_ASS_SHR:
-	case EXP_ASS_USHR:
-	case EXP_ASS_BITAND:
-	case EXP_ASS_BITXOR:
-	case EXP_ASS_BITOR:
-		return 20;
-
-#define COMMA 15
-
-	case EXP_COMMA:
-		return 10;
-
-	default:
-		return 0;
-	}
-}
-
-static void pc(int c)
-{
-	putchar(c);
-}
-
-static void ps(const char *s)
-{
-	fputs(s, stdout);
-}
-
-static void pn(int n)
-{
-	printf("%d", n);
-}
-
-static void in(int d)
-{
-	if (minify < 1)
-		while (d-- > 0)
-			putchar('\t');
-}
-
-static void nl(void)
-{
-	if (minify < 2)
-		putchar('\n');
-}
-
-static void sp(void)
-{
-	if (minify < 1)
-		putchar(' ');
-}
-
-static void comma(void)
-{
-	putchar(',');
-	sp();
-}
-
-/* Pretty-printed Javascript syntax */
-
-static void pstmlist(int d, js_Ast *list);
-static void pexpi(int d, int i, js_Ast *exp);
-static void pstm(int d, js_Ast *stm);
-static void slist(int d, js_Ast *list);
-static void sblock(int d, js_Ast *list);
-
-static void pargs(int d, js_Ast *list)
-{
-	while (list) {
-		assert(list->type == AST_LIST);
-		pexpi(d, COMMA, list->a);
-		list = list->b;
-		if (list)
-			comma();
-	}
-}
-
-static void parray(int d, js_Ast *list)
-{
-	pc('[');
-	while (list) {
-		assert(list->type == AST_LIST);
-		pexpi(d, COMMA, list->a);
-		list = list->b;
-		if (list)
-			comma();
-	}
-	pc(']');
-}
-
-static void pobject(int d, js_Ast *list)
-{
-	pc('{');
-	if (list) {
-		nl();
-		in(d+1);
-	}
-	while (list) {
-		js_Ast *kv = list->a;
-		assert(list->type == AST_LIST);
-		switch (kv->type) {
-		default: break;
-		case EXP_PROP_VAL:
-			pexpi(d+1, COMMA, kv->a);
-			pc(':'); sp();
-			pexpi(d+1, COMMA, kv->b);
-			break;
-		case EXP_PROP_GET:
-			ps("get ");
-			pexpi(d+1, COMMA, kv->a);
-			ps("()"); sp(); pc('{'); nl();
-			pstmlist(d+1, kv->c);
-			in(d+1); pc('}');
-			break;
-		case EXP_PROP_SET:
-			ps("set ");
-			pexpi(d+1, COMMA, kv->a);
-			pc('(');
-			pargs(d+1, kv->b);
-			pc(')'); sp(); pc('{'); nl();
-			pstmlist(d+1, kv->c);
-			in(d+1); pc('}');
-			break;
-		}
-		list = list->b;
-		if (list) {
-			pc(',');
-			nl();
-			in(d+1);
-		} else {
-			nl();
-			in(d);
-		}
-	}
-	pc('}');
-}
-
-static void pstr(const char *s)
-{
-	static const char *HEX = "0123456789ABCDEF";
-	Rune c;
-	pc(minify ? '\'' : '"');
-	while (*s) {
-		s += chartorune(&c, s);
-		switch (c) {
-		case '\'': ps("\\'"); break;
-		case '"': ps("\\\""); break;
-		case '\\': ps("\\\\"); break;
-		case '\b': ps("\\b"); break;
-		case '\f': ps("\\f"); break;
-		case '\n': ps("\\n"); break;
-		case '\r': ps("\\r"); break;
-		case '\t': ps("\\t"); break;
-		default:
-			if (c < ' ' || c > 127) {
-				ps("\\u");
-				pc(HEX[(c>>12)&15]);
-				pc(HEX[(c>>8)&15]);
-				pc(HEX[(c>>4)&15]);
-				pc(HEX[c&15]);
-			} else {
-				pc(c); break;
-			}
-		}
-	}
-	pc(minify ? '\'' : '"');
-}
-
-static void pregexp(const char *prog, int flags)
-{
-	pc('/');
-	while (*prog) {
-		if (*prog == '/')
-			pc('\\');
-		pc(*prog);
-		++prog;
-	}
-	pc('/');
-	if (flags & JS_REGEXP_G) pc('g');
-	if (flags & JS_REGEXP_I) pc('i');
-	if (flags & JS_REGEXP_M) pc('m');
-}
-
-static void pbin(int d, int p, js_Ast *exp, const char *op)
-{
-	pexpi(d, p, exp->a);
-	sp();
-	ps(op);
-	sp();
-	pexpi(d, p, exp->b);
-}
-
-static void puna(int d, int p, js_Ast *exp, const char *pre, const char *suf)
-{
-	ps(pre);
-	pexpi(d, p, exp->a);
-	ps(suf);
-}
-
-static void pexpi(int d, int p, js_Ast *exp)
-{
-	int tp, paren;
-
-	if (!exp) return;
-
-	tp = prec(exp->type);
-	paren = 0;
-	if (tp < p) {
-		pc('(');
-		paren = 1;
-	}
-	p = tp;
-
-	switch (exp->type) {
-	case AST_IDENTIFIER: ps(exp->string); break;
-	case EXP_IDENTIFIER: ps(exp->string); break;
-	case EXP_NUMBER: printf("%.9g", exp->number); break;
-	case EXP_STRING: pstr(exp->string); break;
-	case EXP_REGEXP: pregexp(exp->string, exp->number); break;
-
-	case EXP_UNDEF: break;
-	case EXP_NULL: ps("null"); break;
-	case EXP_TRUE: ps("true"); break;
-	case EXP_FALSE: ps("false"); break;
-	case EXP_THIS: ps("this"); break;
-
-	case EXP_OBJECT: pobject(d, exp->a); break;
-	case EXP_ARRAY: parray(d, exp->a); break;
-
-	case EXP_DELETE: puna(d, p, exp, "delete ", ""); break;
-	case EXP_VOID: puna(d, p, exp, "void ", ""); break;
-	case EXP_TYPEOF: puna(d, p, exp, "typeof ", ""); break;
-	case EXP_PREINC: puna(d, p, exp, "++", ""); break;
-	case EXP_PREDEC: puna(d, p, exp, "--", ""); break;
-	case EXP_POSTINC: puna(d, p, exp, "", "++"); break;
-	case EXP_POSTDEC: puna(d, p, exp, "", "--"); break;
-	case EXP_POS: puna(d, p, exp, "+", ""); break;
-	case EXP_NEG: puna(d, p, exp, "-", ""); break;
-	case EXP_BITNOT: puna(d, p, exp, "~", ""); break;
-	case EXP_LOGNOT: puna(d, p, exp, "!", ""); break;
-
-	case EXP_LOGOR: pbin(d, p, exp, "||"); break;
-	case EXP_LOGAND: pbin(d, p, exp, "&&"); break;
-	case EXP_BITOR: pbin(d, p, exp, "|"); break;
-	case EXP_BITXOR: pbin(d, p, exp, "^"); break;
-	case EXP_BITAND: pbin(d, p, exp, "&"); break;
-	case EXP_EQ: pbin(d, p, exp, "=="); break;
-	case EXP_NE: pbin(d, p, exp, "!="); break;
-	case EXP_STRICTEQ: pbin(d, p, exp, "==="); break;
-	case EXP_STRICTNE: pbin(d, p, exp, "!=="); break;
-	case EXP_LT: pbin(d, p, exp, "<"); break;
-	case EXP_GT: pbin(d, p, exp, ">"); break;
-	case EXP_LE: pbin(d, p, exp, "<="); break;
-	case EXP_GE: pbin(d, p, exp, ">="); break;
-	case EXP_IN: pbin(d, p, exp, "in"); break;
-	case EXP_SHL: pbin(d, p, exp, "<<"); break;
-	case EXP_SHR: pbin(d, p, exp, ">>"); break;
-	case EXP_USHR: pbin(d, p, exp, ">>>"); break;
-	case EXP_ADD: pbin(d, p, exp, "+"); break;
-	case EXP_SUB: pbin(d, p, exp, "-"); break;
-	case EXP_MUL: pbin(d, p, exp, "*"); break;
-	case EXP_DIV: pbin(d, p, exp, "/"); break;
-	case EXP_MOD: pbin(d, p, exp, "%"); break;
-	case EXP_ASS: pbin(d, p, exp, "="); break;
-	case EXP_ASS_MUL: pbin(d, p, exp, "*="); break;
-	case EXP_ASS_DIV: pbin(d, p, exp, "/="); break;
-	case EXP_ASS_MOD: pbin(d, p, exp, "%="); break;
-	case EXP_ASS_ADD: pbin(d, p, exp, "+="); break;
-	case EXP_ASS_SUB: pbin(d, p, exp, "-="); break;
-	case EXP_ASS_SHL: pbin(d, p, exp, "<<="); break;
-	case EXP_ASS_SHR: pbin(d, p, exp, ">>="); break;
-	case EXP_ASS_USHR: pbin(d, p, exp, ">>>="); break;
-	case EXP_ASS_BITAND: pbin(d, p, exp, "&="); break;
-	case EXP_ASS_BITXOR: pbin(d, p, exp, "^="); break;
-	case EXP_ASS_BITOR: pbin(d, p, exp, "|="); break;
-
-	case EXP_INSTANCEOF:
-		pexpi(d, p, exp->a);
-		ps(" instanceof ");
-		pexpi(d, p, exp->b);
-		break;
-
-	case EXP_COMMA:
-		pexpi(d, p, exp->a);
-		pc(','); sp();
-		pexpi(d, p, exp->b);
-		break;
-
-	case EXP_COND:
-		pexpi(d, p, exp->a);
-		sp(); pc('?'); sp();
-		pexpi(d, p, exp->b);
-		sp(); pc(':'); sp();
-		pexpi(d, p, exp->c);
-		break;
-
-	case EXP_INDEX:
-		pexpi(d, p, exp->a);
-		pc('[');
-		pexpi(d, 0, exp->b);
-		pc(']');
-		break;
-
-	case EXP_MEMBER:
-		pexpi(d, p, exp->a);
-		pc('.');
-		pexpi(d, 0, exp->b);
-		break;
-
-	case EXP_CALL:
-		pexpi(d, p, exp->a);
-		pc('(');
-		pargs(d, exp->b);
-		pc(')');
-		break;
-
-	case EXP_NEW:
-		ps("new ");
-		pexpi(d, p, exp->a);
-		pc('(');
-		pargs(d, exp->b);
-		pc(')');
-		break;
-
-	case EXP_FUN:
-		if (p == 0) pc('(');
-		ps("function ");
-		pexpi(d, 0, exp->a);
-		pc('(');
-		pargs(d, exp->b);
-		pc(')'); sp(); pc('{'); nl();
-		pstmlist(d, exp->c);
-		in(d); pc('}');
-		if (p == 0) pc(')');
-		break;
-
-	default:
-		ps("<UNKNOWN>");
-		break;
-	}
-
-	if (paren) pc(')');
-}
-
-static void pexp(int d, js_Ast *exp)
-{
-	pexpi(d, 0, exp);
-}
-
-static void pvar(int d, js_Ast *var)
-{
-	assert(var->type == EXP_VAR);
-	pexp(d, var->a);
-	if (var->b) {
-		sp(); pc('='); sp();
-		pexp(d, var->b);
-	}
-}
-
-static void pvarlist(int d, js_Ast *list)
-{
-	while (list) {
-		assert(list->type == AST_LIST);
-		pvar(d, list->a);
-		list = list->b;
-		if (list)
-			comma();
-	}
-}
-
-static void pblock(int d, js_Ast *block)
-{
-	assert(block->type == STM_BLOCK);
-	pc('{'); nl();
-	pstmlist(d, block->a);
-	in(d); pc('}');
-}
-
-static void pstmh(int d, js_Ast *stm)
-{
-	if (stm->type == STM_BLOCK) {
-		sp();
-		pblock(d, stm);
-	} else {
-		nl();
-		pstm(d+1, stm);
-	}
-}
-
-static void pcaselist(int d, js_Ast *list)
-{
-	while (list) {
-		js_Ast *stm = list->a;
-		if (stm->type == STM_CASE) {
-			in(d); ps("case "); pexp(d, stm->a); pc(':'); nl();
-			pstmlist(d, stm->b);
-		}
-		if (stm->type == STM_DEFAULT) {
-			in(d); ps("default:"); nl();
-			pstmlist(d, stm->a);
-		}
-		list = list->b;
-	}
-}
-
-static void pstm(int d, js_Ast *stm)
-{
-	if (stm->type == STM_BLOCK) {
-		pblock(d, stm);
-		return;
-	}
-
-	in(d);
-
-	switch (stm->type) {
-	case AST_FUNDEC:
-		ps("function ");
-		pexp(d, stm->a);
-		pc('(');
-		pargs(d, stm->b);
-		pc(')'); sp(); pc('{'); nl();
-		pstmlist(d, stm->c);
-		in(d); pc('}');
-		break;
-
-	case STM_EMPTY:
-		pc(';');
-		break;
-
-	case STM_VAR:
-		ps("var ");
-		pvarlist(d, stm->a);
-		pc(';');
-		break;
-
-	case STM_IF:
-		ps("if"); sp(); pc('('); pexp(d, stm->a); pc(')');
-		pstmh(d, stm->b);
-		if (stm->c) {
-			nl(); in(d); ps("else");
-			pstmh(d, stm->c);
-		}
-		break;
-
-	case STM_DO:
-		ps("do");
-		pstmh(d, stm->a);
-		nl();
-		in(d); ps("while"); sp(); pc('('); pexp(d, stm->b); pc(')'); pc(';');
-		break;
-
-	case STM_WHILE:
-		ps("while"); sp(); pc('('); pexp(d, stm->a); pc(')');
-		pstmh(d, stm->b);
-		break;
-
-	case STM_FOR:
-		ps("for"); sp(); pc('(');
-		pexp(d, stm->a); pc(';'); sp();
-		pexp(d, stm->b); pc(';'); sp();
-		pexp(d, stm->c); pc(')');
-		pstmh(d, stm->d);
-		break;
-	case STM_FOR_VAR:
-		ps("for"); sp(); ps("(var ");
-		pvarlist(d, stm->a); pc(';'); sp();
-		pexp(d, stm->b); pc(';'); sp();
-		pexp(d, stm->c); pc(')');
-		pstmh(d, stm->d);
-		break;
-	case STM_FOR_IN:
-		ps("for"); sp(); pc('(');
-		pexp(d, stm->a); ps(" in ");
-		pexp(d, stm->b); pc(')');
-		pstmh(d, stm->c);
-		break;
-	case STM_FOR_IN_VAR:
-		ps("for"); sp(); ps("(var ");
-		pvarlist(d, stm->a); ps(" in ");
-		pexp(d, stm->b); pc(')');
-		pstmh(d, stm->c);
-		break;
-
-	case STM_CONTINUE:
-		ps("continue");
-		if (stm->a) {
-			pc(' '); pexp(d, stm->a);
-		}
-		pc(';');
-		break;
-
-	case STM_BREAK:
-		ps("break");
-		if (stm->a) {
-			pc(' '); pexp(d, stm->a);
-		}
-		pc(';');
-		break;
-
-	case STM_RETURN:
-		ps("return");
-		if (stm->a) {
-			pc(' '); pexp(d, stm->a);
-		}
-		pc(';');
-		break;
-
-	case STM_WITH:
-		ps("with"); sp(); pc('('); pexp(d, stm->a); pc(')');
-		pstmh(d, stm->b);
-		break;
-
-	case STM_SWITCH:
-		ps("switch"); sp(); pc('(');
-		pexp(d, stm->a);
-		pc(')'); sp(); pc('{'); nl();
-		pcaselist(d, stm->b);
-		in(d); pc('}');
-		break;
-
-	case STM_THROW:
-		ps("throw "); pexp(d, stm->a); pc(';');
-		break;
-
-	case STM_TRY:
-		ps("try");
-		if (minify && stm->a->type != STM_BLOCK)
-			pc(' ');
-		pstmh(d, stm->a);
-		if (stm->b && stm->c) {
-			nl(); in(d); ps("catch"); sp(); pc('('); pexp(d, stm->b); pc(')');
-			pstmh(d, stm->c);
-		}
-		if (stm->d) {
-			nl(); in(d); ps("finally");
-			pstmh(d, stm->d);
-		}
-		break;
-
-	case STM_LABEL:
-		pexp(d, stm->a); pc(':'); sp(); pstm(d, stm->b);
-		break;
-
-	case STM_DEBUGGER:
-		ps("debugger");
-		pc(';');
-		break;
-
-	default:
-		pexp(d, stm);
-		pc(';');
-	}
-}
-
-static void pstmlist(int d, js_Ast *list)
-{
-	while (list) {
-		assert(list->type == AST_LIST);
-		pstm(d+1, list->a);
-		nl();
-		list = list->b;
-	}
-}
-
-void jsP_dumpsyntax(js_State *J, js_Ast *prog, int dominify)
-{
-	minify = dominify;
-	if (prog) {
-		if (prog->type == AST_LIST)
-			pstmlist(-1, prog);
-		else {
-			pstm(0, prog);
-			nl();
-		}
-	}
-	if (minify > 1)
-		putchar('\n');
-}
-
-/* S-expression list representation */
-
-static void snode(int d, js_Ast *node)
-{
-	void (*afun)(int,js_Ast*) = snode;
-	void (*bfun)(int,js_Ast*) = snode;
-	void (*cfun)(int,js_Ast*) = snode;
-	void (*dfun)(int,js_Ast*) = snode;
-
-	if (!node) {
-		return;
-	}
-
-	if (node->type == AST_LIST) {
-		slist(d, node);
-		return;
-	}
-
-	pc('(');
-	ps(astname[node->type]);
-	pc(':');
-	pn(node->line);
-	switch (node->type) {
-	default: break;
-	case AST_IDENTIFIER: pc(' '); ps(node->string); break;
-	case EXP_IDENTIFIER: pc(' '); ps(node->string); break;
-	case EXP_STRING: pc(' '); pstr(node->string); break;
-	case EXP_REGEXP: pc(' '); pregexp(node->string, node->number); break;
-	case EXP_NUMBER: printf(" %.9g", node->number); break;
-	case STM_BLOCK: afun = sblock; break;
-	case AST_FUNDEC: case EXP_FUN: cfun = sblock; break;
-	case EXP_PROP_GET: cfun = sblock; break;
-	case EXP_PROP_SET: cfun = sblock; break;
-	case STM_SWITCH: bfun = sblock; break;
-	case STM_CASE: bfun = sblock; break;
-	case STM_DEFAULT: afun = sblock; break;
-	}
-	if (node->a) { pc(' '); afun(d, node->a); }
-	if (node->b) { pc(' '); bfun(d, node->b); }
-	if (node->c) { pc(' '); cfun(d, node->c); }
-	if (node->d) { pc(' '); dfun(d, node->d); }
-	pc(')');
-}
-
-static void slist(int d, js_Ast *list)
-{
-	pc('[');
-	while (list) {
-		assert(list->type == AST_LIST);
-		snode(d, list->a);
-		list = list->b;
-		if (list)
-			pc(' ');
-	}
-	pc(']');
-}
-
-static void sblock(int d, js_Ast *list)
-{
-	ps("[\n");
-	in(d+1);
-	while (list) {
-		assert(list->type == AST_LIST);
-		snode(d+1, list->a);
-		list = list->b;
-		if (list) {
-			nl();
-			in(d+1);
-		}
-	}
-	nl(); in(d); pc(']');
-}
-
-void jsP_dumplist(js_State *J, js_Ast *prog)
-{
-	minify = 0;
-	if (prog) {
-		if (prog->type == AST_LIST)
-			sblock(0, prog);
-		else
-			snode(0, prog);
-		nl();
-	}
-}
-
-/* Compiled code */
-
-void jsC_dumpfunction(js_State *J, js_Function *F)
-{
-	js_Instruction *p = F->code;
-	js_Instruction *end = F->code + F->codelen;
-	char *s;
-	double n;
-	int i;
-
-	minify = 0;
-
-	printf("%s(%d)\n", F->name, F->numparams);
-	if (F->strict) printf("\tstrict\n");
-	if (F->lightweight) printf("\tlightweight\n");
-	if (F->arguments) printf("\targuments\n");
-	printf("\tsource %s:%d\n", F->filename, F->line);
-	for (i = 0; i < F->funlen; ++i)
-		printf("\tfunction %d %s\n", i, F->funtab[i]->name);
-	for (i = 0; i < F->varlen; ++i)
-		printf("\tlocal %d %s\n", i + 1, F->vartab[i]);
-
-	printf("{\n");
-	while (p < end) {
-		int ln = *p++;
-		int c = *p++;
-
-		printf("%5d(%3d): ", (int)(p - F->code) - 2, ln);
-		ps(opname[c]);
-
-		switch (c) {
-		case OP_INTEGER:
-			printf(" %ld", (long)((*p++) - 32768));
-			break;
-		case OP_NUMBER:
-			memcpy(&n, p, sizeof(n));
-			p += sizeof(n) / sizeof(*p);
-			printf(" %.9g", n);
-			break;
-		case OP_STRING:
-			memcpy(&s, p, sizeof(s));
-			p += sizeof(s) / sizeof(*p);
-			pc(' ');
-			pstr(s);
-			break;
-		case OP_NEWREGEXP:
-			pc(' ');
-			memcpy(&s, p, sizeof(s));
-			p += sizeof(s) / sizeof(*p);
-			pregexp(s, *p++);
-			break;
-
-		case OP_GETVAR:
-		case OP_HASVAR:
-		case OP_SETVAR:
-		case OP_DELVAR:
-		case OP_GETPROP_S:
-		case OP_SETPROP_S:
-		case OP_DELPROP_S:
-		case OP_CATCH:
-			memcpy(&s, p, sizeof(s));
-			p += sizeof(s) / sizeof(*p);
-			pc(' ');
-			ps(s);
-			break;
-
-		case OP_GETLOCAL:
-		case OP_SETLOCAL:
-		case OP_DELLOCAL:
-			printf(" %s", F->vartab[*p++ - 1]);
-			break;
-
-		case OP_CLOSURE:
-		case OP_CALL:
-		case OP_NEW:
-		case OP_JUMP:
-		case OP_JTRUE:
-		case OP_JFALSE:
-		case OP_JCASE:
-		case OP_TRY:
-			printf(" %ld", (long)*p++);
-			break;
-		}
-
-		nl();
-	}
-	printf("}\n");
-
-	for (i = 0; i < F->funlen; ++i) {
-		if (F->funtab[i] != F) {
-			printf("function %d ", i);
-			jsC_dumpfunction(J, F->funtab[i]);
-		}
-	}
-}
-
-/* Runtime values */
-
-void js_dumpvalue(js_State *J, js_Value v)
-{
-	minify = 0;
-	switch (v.type) {
-	case JS_TUNDEFINED: printf("undefined"); break;
-	case JS_TNULL: printf("null"); break;
-	case JS_TBOOLEAN: printf(v.u.boolean ? "true" : "false"); break;
-	case JS_TNUMBER: printf("%.9g", v.u.number); break;
-	case JS_TSHRSTR: printf("'%s'", v.u.shrstr); break;
-	case JS_TLITSTR: printf("'%s'", v.u.litstr); break;
-	case JS_TMEMSTR: printf("'%s'", v.u.memstr->p); break;
-	case JS_TOBJECT:
-		if (v.u.object == J->G) {
-			printf("[Global]");
-			break;
-		}
-		switch (v.u.object->type) {
-		case JS_COBJECT: printf("[Object %p]", (void*)v.u.object); break;
-		case JS_CARRAY: printf("[Array %p]", (void*)v.u.object); break;
-		case JS_CFUNCTION:
-			printf("[Function %p, %s, %s:%d]",
-				(void*)v.u.object,
-				v.u.object->u.f.function->name,
-				v.u.object->u.f.function->filename,
-				v.u.object->u.f.function->line);
-			break;
-		case JS_CSCRIPT: printf("[Script %s]", v.u.object->u.f.function->filename); break;
-		case JS_CCFUNCTION: printf("[CFunction %s]", v.u.object->u.c.name); break;
-		case JS_CBOOLEAN: printf("[Boolean %d]", v.u.object->u.boolean); break;
-		case JS_CNUMBER: printf("[Number %g]", v.u.object->u.number); break;
-		case JS_CSTRING: printf("[String'%s']", v.u.object->u.s.string); break;
-		case JS_CERROR: printf("[Error]"); break;
-		case JS_CARGUMENTS: printf("[Arguments %p]", (void*)v.u.object); break;
-		case JS_CITERATOR: printf("[Iterator %p]", (void*)v.u.object); break;
-		case JS_CUSERDATA:
-			printf("[Userdata %s %p]", v.u.object->u.user.tag, v.u.object->u.user.data);
-			break;
-		default: printf("[Object %p]", (void*)v.u.object); break;
-		}
-		break;
-	}
-}
-
-static void js_dumpproperty(js_State *J, js_Property *node)
-{
-	minify = 0;
-	if (node->left->level)
-		js_dumpproperty(J, node->left);
-	printf("\t%s: ", node->name);
-	js_dumpvalue(J, node->value);
-	printf(",\n");
-	if (node->right->level)
-		js_dumpproperty(J, node->right);
-}
-
-void js_dumpobject(js_State *J, js_Object *obj)
-{
-	minify = 0;
-	printf("{\n");
-	if (obj->properties->level)
-		js_dumpproperty(J, obj->properties);
-	printf("}\n");
-}
--- a/jsi.h
+++ b/jsi.h
@@ -483,10 +483,6 @@
 void jsV_unflattenarray(js_State *J, js_Object *obj);
 void jsV_growarray(js_State *J, js_Object *obj);
 
-/* jsdump.c */
-void js_dumpobject(js_State *J, js_Object *obj);
-void js_dumpvalue(js_State *J, js_Value v);
-
 /* Lexer */
 
 enum
@@ -706,10 +702,6 @@
 js_Ast *jsP_parse(js_State *J, const char *filename, const char *source);
 void jsP_freeparse(js_State *J);
 
-const char *jsP_aststring(enum js_AstType type);
-void jsP_dumpsyntax(js_State *J, js_Ast *prog, int minify);
-void jsP_dumplist(js_State *J, js_Ast *prog);
-
 /* Compiler */
 
 enum js_OpCode
@@ -846,8 +838,6 @@
 
 js_Function *jsC_compilefunction(js_State *J, js_Ast *prog);
 js_Function *jsC_compilescript(js_State *J, js_Ast *prog, int default_strict);
-const char *jsC_opcodestring(enum js_OpCode opcode);
-void jsC_dumpfunction(js_State *J, js_Function *fun);
 
 /* Builtins */
 
--- a/jsrun.c
+++ b/jsrun.c
@@ -1455,29 +1455,50 @@
 
 /* Main interpreter loop */
 
-static void jsR_dumpstack(js_State *J)
+static void js_dumpvalue(js_State *J, js_Value v)
 {
-	int i;
-	printf("stack {\n");
-	for (i = 0; i < TOP; ++i) {
-		putchar(i == BOT ? '>' : ' ');
-		printf("%4d: ", i);
-		js_dumpvalue(J, STACK[i]);
-		putchar('\n');
+	switch (v.type) {
+	case JS_TUNDEFINED: printf("undefined"); break;
+	case JS_TNULL: printf("null"); break;
+	case JS_TBOOLEAN: printf(v.u.boolean ? "true" : "false"); break;
+	case JS_TNUMBER: printf("%.9g", v.u.number); break;
+	case JS_TSHRSTR: printf("'%s'", v.u.shrstr); break;
+	case JS_TLITSTR: printf("'%s'", v.u.litstr); break;
+	case JS_TMEMSTR: printf("'%s'", v.u.memstr->p); break;
+	case JS_TOBJECT:
+		if (v.u.object == J->G) {
+			printf("[Global]");
+			break;
+		}
+		switch (v.u.object->type) {
+		case JS_COBJECT: printf("[Object %p]", (void*)v.u.object); break;
+		case JS_CARRAY: printf("[Array %p]", (void*)v.u.object); break;
+		case JS_CFUNCTION:
+			printf("[Function %p, %s, %s:%d]",
+				(void*)v.u.object,
+				v.u.object->u.f.function->name,
+				v.u.object->u.f.function->filename,
+				v.u.object->u.f.function->line);
+			break;
+		case JS_CSCRIPT: printf("[Script %s]", v.u.object->u.f.function->filename); break;
+		case JS_CCFUNCTION: printf("[CFunction %s]", v.u.object->u.c.name); break;
+		case JS_CBOOLEAN: printf("[Boolean %d]", v.u.object->u.boolean); break;
+		case JS_CNUMBER: printf("[Number %g]", v.u.object->u.number); break;
+		case JS_CSTRING: printf("[String'%s']", v.u.object->u.s.string); break;
+		case JS_CERROR: printf("[Error]"); break;
+		case JS_CARGUMENTS: printf("[Arguments %p]", (void*)v.u.object); break;
+		case JS_CITERATOR: printf("[Iterator %p]", (void*)v.u.object); break;
+		case JS_CUSERDATA:
+			printf("[Userdata %s %p]", v.u.object->u.user.tag, v.u.object->u.user.data);
+			break;
+		default: printf("[Object %p]", (void*)v.u.object); break;
+		}
+		break;
 	}
-	printf("}\n");
 }
 
-static void jsR_dumpenvironment(js_State *J, js_Environment *E, int d)
+static void js_stacktrace(js_State *J)
 {
-	printf("scope %d ", d);
-	js_dumpobject(J, E->variables);
-	if (E->outer)
-		jsR_dumpenvironment(J, E->outer, d+1);
-}
-
-void js_stacktrace(js_State *J)
-{
 	int n;
 	printf("stack trace:\n");
 	for (n = J->tracetop; n >= 0; --n) {
@@ -1494,15 +1515,22 @@
 	}
 }
 
-void js_trap(js_State *J, int pc)
+static void js_dumpstack(js_State *J)
 {
-	if (pc > 0) {
-		js_Function *F = STACK[BOT-1].u.object->u.f.function;
-		printf("trap at %d in function ", pc);
-		jsC_dumpfunction(J, F);
+	int i;
+	printf("stack {\n");
+	for (i = 0; i < TOP; ++i) {
+		putchar(i == BOT ? '>' : ' ');
+		printf("%4d: ", i);
+		js_dumpvalue(J, STACK[i]);
+		putchar('\n');
 	}
-	jsR_dumpstack(J);
-	jsR_dumpenvironment(J, J->E, 0);
+	printf("}\n");
+}
+
+void js_trap(js_State *J, int pc)
+{
+	js_dumpstack(J);
 	js_stacktrace(J);
 }
 
--- a/one.c
+++ b/one.c
@@ -4,7 +4,6 @@
 #include "jscompile.c"
 #include "jsdate.c"
 #include "jsdtoa.c"
-#include "jsdump.c"
 #include "jserror.c"
 #include "jsfunction.c"
 #include "jsgc.c"
--- a/pp.c
+++ b/pp.c
@@ -6,26 +6,885 @@
  */
 
 #include <stdio.h>
+#include <assert.h>
 
 #include "jsi.h"
+#include "utf.h"
 
-static void js_ppstring(js_State *J, const char *filename, const char *source, int minify)
+static const char *astname[] = {
+#include "astnames.h"
+NULL
+};
+
+static const char *opname[] = {
+#include "opnames.h"
+NULL
+};
+
+static int format = 0;
+static int minify = 0;
+
+static void pc(int c)
 {
+	putchar(c);
+}
+
+static void ps(const char *s)
+{
+	fputs(s, stdout);
+}
+
+static void in(int d)
+{
+	if (minify < 1)
+		while (d-- > 0)
+			putchar('\t');
+}
+
+static void nl(void)
+{
+	if (minify < 2)
+		putchar('\n');
+}
+
+static void sp(void)
+{
+	if (minify < 1)
+		putchar(' ');
+}
+
+static void comma(void)
+{
+	putchar(',');
+	sp();
+}
+
+static void pstr(const char *s)
+{
+	static const char *HEX = "0123456789ABCDEF";
+	Rune c;
+	pc(minify ? '\'' : '"');
+	while (*s) {
+		s += chartorune(&c, s);
+		switch (c) {
+		case '\'': ps("\\'"); break;
+		case '"': ps("\\\""); break;
+		case '\\': ps("\\\\"); break;
+		case '\b': ps("\\b"); break;
+		case '\f': ps("\\f"); break;
+		case '\n': ps("\\n"); break;
+		case '\r': ps("\\r"); break;
+		case '\t': ps("\\t"); break;
+		default:
+			if (c < ' ' || c > 127) {
+				ps("\\u");
+				pc(HEX[(c>>12)&15]);
+				pc(HEX[(c>>8)&15]);
+				pc(HEX[(c>>4)&15]);
+				pc(HEX[c&15]);
+			} else {
+				pc(c); break;
+			}
+		}
+	}
+	pc(minify ? '\'' : '"');
+}
+
+static void pregexp(const char *prog, int flags)
+{
+	pc('/');
+	while (*prog) {
+		if (*prog == '/')
+			pc('\\');
+		pc(*prog);
+		++prog;
+	}
+	pc('/');
+	if (flags & JS_REGEXP_G) pc('g');
+	if (flags & JS_REGEXP_I) pc('i');
+	if (flags & JS_REGEXP_M) pc('m');
+}
+
+/* Bytecode */
+
+static void jsC_dumpfunction(js_State *J, js_Function *F)
+{
+	js_Instruction *p = F->code;
+	js_Instruction *end = F->code + F->codelen;
+	char *s;
+	double n;
+	int i;
+
+	printf("%s(%d)\n", F->name, F->numparams);
+	if (F->strict) printf("\tstrict\n");
+	if (F->lightweight) printf("\tlightweight\n");
+	if (F->arguments) printf("\targuments\n");
+	printf("\tsource %s:%d\n", F->filename, F->line);
+	for (i = 0; i < F->funlen; ++i)
+		printf("\tfunction %d %s\n", i, F->funtab[i]->name);
+	for (i = 0; i < F->varlen; ++i)
+		printf("\tlocal %d %s\n", i + 1, F->vartab[i]);
+
+	printf("{\n");
+	while (p < end) {
+		int ln = *p++;
+		int c = *p++;
+
+		printf("%5d(%3d): ", (int)(p - F->code) - 2, ln);
+		ps(opname[c]);
+
+		switch (c) {
+		case OP_INTEGER:
+			printf(" %ld", (long)((*p++) - 32768));
+			break;
+		case OP_NUMBER:
+			memcpy(&n, p, sizeof(n));
+			p += sizeof(n) / sizeof(*p);
+			printf(" %.9g", n);
+			break;
+		case OP_STRING:
+			memcpy(&s, p, sizeof(s));
+			p += sizeof(s) / sizeof(*p);
+			pc(' ');
+			pstr(s);
+			break;
+		case OP_NEWREGEXP:
+			pc(' ');
+			memcpy(&s, p, sizeof(s));
+			p += sizeof(s) / sizeof(*p);
+			pregexp(s, *p++);
+			break;
+
+		case OP_GETVAR:
+		case OP_HASVAR:
+		case OP_SETVAR:
+		case OP_DELVAR:
+		case OP_GETPROP_S:
+		case OP_SETPROP_S:
+		case OP_DELPROP_S:
+		case OP_CATCH:
+			memcpy(&s, p, sizeof(s));
+			p += sizeof(s) / sizeof(*p);
+			pc(' ');
+			ps(s);
+			break;
+
+		case OP_GETLOCAL:
+		case OP_SETLOCAL:
+		case OP_DELLOCAL:
+			printf(" %s", F->vartab[*p++ - 1]);
+			break;
+
+		case OP_CLOSURE:
+		case OP_CALL:
+		case OP_NEW:
+		case OP_JUMP:
+		case OP_JTRUE:
+		case OP_JFALSE:
+		case OP_JCASE:
+		case OP_TRY:
+			printf(" %ld", (long)*p++);
+			break;
+		}
+
+		putchar('\n');
+	}
+	printf("}\n");
+
+	for (i = 0; i < F->funlen; ++i) {
+		if (F->funtab[i] != F) {
+			printf("function %d ", i);
+			jsC_dumpfunction(J, F->funtab[i]);
+		}
+	}
+}
+
+/* Pretty-printed Javascript syntax */
+
+static int prec(enum js_AstType type)
+{
+	switch (type) {
+	case AST_IDENTIFIER:
+	case EXP_IDENTIFIER:
+	case EXP_NUMBER:
+	case EXP_STRING:
+	case EXP_REGEXP:
+	case EXP_UNDEF:
+	case EXP_NULL:
+	case EXP_TRUE:
+	case EXP_FALSE:
+	case EXP_THIS:
+	case EXP_ARRAY:
+	case EXP_OBJECT:
+		return 170;
+
+	case EXP_FUN:
+	case EXP_INDEX:
+	case EXP_MEMBER:
+	case EXP_CALL:
+	case EXP_NEW:
+		return 160;
+
+	case EXP_POSTINC:
+	case EXP_POSTDEC:
+		return 150;
+
+	case EXP_DELETE:
+	case EXP_VOID:
+	case EXP_TYPEOF:
+	case EXP_PREINC:
+	case EXP_PREDEC:
+	case EXP_POS:
+	case EXP_NEG:
+	case EXP_BITNOT:
+	case EXP_LOGNOT:
+		return 140;
+
+	case EXP_MOD:
+	case EXP_DIV:
+	case EXP_MUL:
+		return 130;
+
+	case EXP_SUB:
+	case EXP_ADD:
+		return 120;
+
+	case EXP_USHR:
+	case EXP_SHR:
+	case EXP_SHL:
+		return 110;
+
+	case EXP_IN:
+	case EXP_INSTANCEOF:
+	case EXP_GE:
+	case EXP_LE:
+	case EXP_GT:
+	case EXP_LT:
+		return 100;
+
+	case EXP_STRICTNE:
+	case EXP_STRICTEQ:
+	case EXP_NE:
+	case EXP_EQ:
+		return 90;
+
+	case EXP_BITAND: return 80;
+	case EXP_BITXOR: return 70;
+	case EXP_BITOR: return 60;
+	case EXP_LOGAND: return 50;
+	case EXP_LOGOR: return 40;
+
+	case EXP_COND:
+		return 30;
+
+	case EXP_ASS:
+	case EXP_ASS_MUL:
+	case EXP_ASS_DIV:
+	case EXP_ASS_MOD:
+	case EXP_ASS_ADD:
+	case EXP_ASS_SUB:
+	case EXP_ASS_SHL:
+	case EXP_ASS_SHR:
+	case EXP_ASS_USHR:
+	case EXP_ASS_BITAND:
+	case EXP_ASS_BITXOR:
+	case EXP_ASS_BITOR:
+		return 20;
+
+#define COMMA 15
+
+	case EXP_COMMA:
+		return 10;
+
+	default:
+		return 0;
+	}
+}
+
+static void pstmlist(int d, js_Ast *list);
+static void pexpi(int d, int i, js_Ast *exp);
+static void pstm(int d, js_Ast *stm);
+static void slist(int d, js_Ast *list);
+static void sblock(int d, js_Ast *list);
+
+static void pargs(int d, js_Ast *list)
+{
+	while (list) {
+		assert(list->type == AST_LIST);
+		pexpi(d, COMMA, list->a);
+		list = list->b;
+		if (list)
+			comma();
+	}
+}
+
+static void parray(int d, js_Ast *list)
+{
+	pc('[');
+	while (list) {
+		assert(list->type == AST_LIST);
+		pexpi(d, COMMA, list->a);
+		list = list->b;
+		if (list)
+			comma();
+	}
+	pc(']');
+}
+
+static void pobject(int d, js_Ast *list)
+{
+	pc('{');
+	if (list) {
+		nl();
+		in(d+1);
+	}
+	while (list) {
+		js_Ast *kv = list->a;
+		assert(list->type == AST_LIST);
+		switch (kv->type) {
+		default: break;
+		case EXP_PROP_VAL:
+			pexpi(d+1, COMMA, kv->a);
+			pc(':'); sp();
+			pexpi(d+1, COMMA, kv->b);
+			break;
+		case EXP_PROP_GET:
+			ps("get ");
+			pexpi(d+1, COMMA, kv->a);
+			ps("()"); sp(); pc('{'); nl();
+			pstmlist(d+1, kv->c);
+			in(d+1); pc('}');
+			break;
+		case EXP_PROP_SET:
+			ps("set ");
+			pexpi(d+1, COMMA, kv->a);
+			pc('(');
+			pargs(d+1, kv->b);
+			pc(')'); sp(); pc('{'); nl();
+			pstmlist(d+1, kv->c);
+			in(d+1); pc('}');
+			break;
+		}
+		list = list->b;
+		if (list) {
+			pc(',');
+			nl();
+			in(d+1);
+		} else {
+			nl();
+			in(d);
+		}
+	}
+	pc('}');
+}
+
+static void pbin(int d, int p, js_Ast *exp, const char *op)
+{
+	pexpi(d, p, exp->a);
+	sp();
+	ps(op);
+	sp();
+	pexpi(d, p, exp->b);
+}
+
+static void puna(int d, int p, js_Ast *exp, const char *pre, const char *suf)
+{
+	ps(pre);
+	pexpi(d, p, exp->a);
+	ps(suf);
+}
+
+static void pexpi(int d, int p, js_Ast *exp)
+{
+	int tp, paren;
+
+	if (!exp) return;
+
+	tp = prec(exp->type);
+	paren = 0;
+	if (tp < p) {
+		pc('(');
+		paren = 1;
+	}
+	p = tp;
+
+	switch (exp->type) {
+	case AST_IDENTIFIER: ps(exp->string); break;
+	case EXP_IDENTIFIER: ps(exp->string); break;
+	case EXP_NUMBER: printf("%.9g", exp->number); break;
+	case EXP_STRING: pstr(exp->string); break;
+	case EXP_REGEXP: pregexp(exp->string, exp->number); break;
+
+	case EXP_UNDEF: break;
+	case EXP_NULL: ps("null"); break;
+	case EXP_TRUE: ps("true"); break;
+	case EXP_FALSE: ps("false"); break;
+	case EXP_THIS: ps("this"); break;
+
+	case EXP_OBJECT: pobject(d, exp->a); break;
+	case EXP_ARRAY: parray(d, exp->a); break;
+
+	case EXP_DELETE: puna(d, p, exp, "delete ", ""); break;
+	case EXP_VOID: puna(d, p, exp, "void ", ""); break;
+	case EXP_TYPEOF: puna(d, p, exp, "typeof ", ""); break;
+	case EXP_PREINC: puna(d, p, exp, "++", ""); break;
+	case EXP_PREDEC: puna(d, p, exp, "--", ""); break;
+	case EXP_POSTINC: puna(d, p, exp, "", "++"); break;
+	case EXP_POSTDEC: puna(d, p, exp, "", "--"); break;
+	case EXP_POS: puna(d, p, exp, "+", ""); break;
+	case EXP_NEG: puna(d, p, exp, "-", ""); break;
+	case EXP_BITNOT: puna(d, p, exp, "~", ""); break;
+	case EXP_LOGNOT: puna(d, p, exp, "!", ""); break;
+
+	case EXP_LOGOR: pbin(d, p, exp, "||"); break;
+	case EXP_LOGAND: pbin(d, p, exp, "&&"); break;
+	case EXP_BITOR: pbin(d, p, exp, "|"); break;
+	case EXP_BITXOR: pbin(d, p, exp, "^"); break;
+	case EXP_BITAND: pbin(d, p, exp, "&"); break;
+	case EXP_EQ: pbin(d, p, exp, "=="); break;
+	case EXP_NE: pbin(d, p, exp, "!="); break;
+	case EXP_STRICTEQ: pbin(d, p, exp, "==="); break;
+	case EXP_STRICTNE: pbin(d, p, exp, "!=="); break;
+	case EXP_LT: pbin(d, p, exp, "<"); break;
+	case EXP_GT: pbin(d, p, exp, ">"); break;
+	case EXP_LE: pbin(d, p, exp, "<="); break;
+	case EXP_GE: pbin(d, p, exp, ">="); break;
+	case EXP_IN: pbin(d, p, exp, "in"); break;
+	case EXP_SHL: pbin(d, p, exp, "<<"); break;
+	case EXP_SHR: pbin(d, p, exp, ">>"); break;
+	case EXP_USHR: pbin(d, p, exp, ">>>"); break;
+	case EXP_ADD: pbin(d, p, exp, "+"); break;
+	case EXP_SUB: pbin(d, p, exp, "-"); break;
+	case EXP_MUL: pbin(d, p, exp, "*"); break;
+	case EXP_DIV: pbin(d, p, exp, "/"); break;
+	case EXP_MOD: pbin(d, p, exp, "%"); break;
+	case EXP_ASS: pbin(d, p, exp, "="); break;
+	case EXP_ASS_MUL: pbin(d, p, exp, "*="); break;
+	case EXP_ASS_DIV: pbin(d, p, exp, "/="); break;
+	case EXP_ASS_MOD: pbin(d, p, exp, "%="); break;
+	case EXP_ASS_ADD: pbin(d, p, exp, "+="); break;
+	case EXP_ASS_SUB: pbin(d, p, exp, "-="); break;
+	case EXP_ASS_SHL: pbin(d, p, exp, "<<="); break;
+	case EXP_ASS_SHR: pbin(d, p, exp, ">>="); break;
+	case EXP_ASS_USHR: pbin(d, p, exp, ">>>="); break;
+	case EXP_ASS_BITAND: pbin(d, p, exp, "&="); break;
+	case EXP_ASS_BITXOR: pbin(d, p, exp, "^="); break;
+	case EXP_ASS_BITOR: pbin(d, p, exp, "|="); break;
+
+	case EXP_INSTANCEOF:
+		pexpi(d, p, exp->a);
+		ps(" instanceof ");
+		pexpi(d, p, exp->b);
+		break;
+
+	case EXP_COMMA:
+		pexpi(d, p, exp->a);
+		pc(','); sp();
+		pexpi(d, p, exp->b);
+		break;
+
+	case EXP_COND:
+		pexpi(d, p, exp->a);
+		sp(); pc('?'); sp();
+		pexpi(d, p, exp->b);
+		sp(); pc(':'); sp();
+		pexpi(d, p, exp->c);
+		break;
+
+	case EXP_INDEX:
+		pexpi(d, p, exp->a);
+		pc('[');
+		pexpi(d, 0, exp->b);
+		pc(']');
+		break;
+
+	case EXP_MEMBER:
+		pexpi(d, p, exp->a);
+		pc('.');
+		pexpi(d, 0, exp->b);
+		break;
+
+	case EXP_CALL:
+		pexpi(d, p, exp->a);
+		pc('(');
+		pargs(d, exp->b);
+		pc(')');
+		break;
+
+	case EXP_NEW:
+		ps("new ");
+		pexpi(d, p, exp->a);
+		pc('(');
+		pargs(d, exp->b);
+		pc(')');
+		break;
+
+	case EXP_FUN:
+		if (p == 0) pc('(');
+		ps("function ");
+		pexpi(d, 0, exp->a);
+		pc('(');
+		pargs(d, exp->b);
+		pc(')'); sp(); pc('{'); nl();
+		pstmlist(d, exp->c);
+		in(d); pc('}');
+		if (p == 0) pc(')');
+		break;
+
+	default:
+		ps("<UNKNOWN>");
+		break;
+	}
+
+	if (paren) pc(')');
+}
+
+static void pexp(int d, js_Ast *exp)
+{
+	pexpi(d, 0, exp);
+}
+
+static void pvar(int d, js_Ast *var)
+{
+	assert(var->type == EXP_VAR);
+	pexp(d, var->a);
+	if (var->b) {
+		sp(); pc('='); sp();
+		pexp(d, var->b);
+	}
+}
+
+static void pvarlist(int d, js_Ast *list)
+{
+	while (list) {
+		assert(list->type == AST_LIST);
+		pvar(d, list->a);
+		list = list->b;
+		if (list)
+			comma();
+	}
+}
+
+static void pblock(int d, js_Ast *block)
+{
+	assert(block->type == STM_BLOCK);
+	pc('{'); nl();
+	pstmlist(d, block->a);
+	in(d); pc('}');
+}
+
+static void pstmh(int d, js_Ast *stm)
+{
+	if (stm->type == STM_BLOCK) {
+		sp();
+		pblock(d, stm);
+	} else {
+		nl();
+		pstm(d+1, stm);
+	}
+}
+
+static void pcaselist(int d, js_Ast *list)
+{
+	while (list) {
+		js_Ast *stm = list->a;
+		if (stm->type == STM_CASE) {
+			in(d); ps("case "); pexp(d, stm->a); pc(':'); nl();
+			pstmlist(d, stm->b);
+		}
+		if (stm->type == STM_DEFAULT) {
+			in(d); ps("default:"); nl();
+			pstmlist(d, stm->a);
+		}
+		list = list->b;
+	}
+}
+
+static void pstm(int d, js_Ast *stm)
+{
+	if (stm->type == STM_BLOCK) {
+		pblock(d, stm);
+		return;
+	}
+
+	in(d);
+
+	switch (stm->type) {
+	case AST_FUNDEC:
+		ps("function ");
+		pexp(d, stm->a);
+		pc('(');
+		pargs(d, stm->b);
+		pc(')'); sp(); pc('{'); nl();
+		pstmlist(d, stm->c);
+		in(d); pc('}');
+		break;
+
+	case STM_EMPTY:
+		pc(';');
+		break;
+
+	case STM_VAR:
+		ps("var ");
+		pvarlist(d, stm->a);
+		pc(';');
+		break;
+
+	case STM_IF:
+		ps("if"); sp(); pc('('); pexp(d, stm->a); pc(')');
+		pstmh(d, stm->b);
+		if (stm->c) {
+			nl(); in(d); ps("else");
+			pstmh(d, stm->c);
+		}
+		break;
+
+	case STM_DO:
+		ps("do");
+		pstmh(d, stm->a);
+		nl();
+		in(d); ps("while"); sp(); pc('('); pexp(d, stm->b); pc(')'); pc(';');
+		break;
+
+	case STM_WHILE:
+		ps("while"); sp(); pc('('); pexp(d, stm->a); pc(')');
+		pstmh(d, stm->b);
+		break;
+
+	case STM_FOR:
+		ps("for"); sp(); pc('(');
+		pexp(d, stm->a); pc(';'); sp();
+		pexp(d, stm->b); pc(';'); sp();
+		pexp(d, stm->c); pc(')');
+		pstmh(d, stm->d);
+		break;
+	case STM_FOR_VAR:
+		ps("for"); sp(); ps("(var ");
+		pvarlist(d, stm->a); pc(';'); sp();
+		pexp(d, stm->b); pc(';'); sp();
+		pexp(d, stm->c); pc(')');
+		pstmh(d, stm->d);
+		break;
+	case STM_FOR_IN:
+		ps("for"); sp(); pc('(');
+		pexp(d, stm->a); ps(" in ");
+		pexp(d, stm->b); pc(')');
+		pstmh(d, stm->c);
+		break;
+	case STM_FOR_IN_VAR:
+		ps("for"); sp(); ps("(var ");
+		pvarlist(d, stm->a); ps(" in ");
+		pexp(d, stm->b); pc(')');
+		pstmh(d, stm->c);
+		break;
+
+	case STM_CONTINUE:
+		ps("continue");
+		if (stm->a) {
+			pc(' '); pexp(d, stm->a);
+		}
+		pc(';');
+		break;
+
+	case STM_BREAK:
+		ps("break");
+		if (stm->a) {
+			pc(' '); pexp(d, stm->a);
+		}
+		pc(';');
+		break;
+
+	case STM_RETURN:
+		ps("return");
+		if (stm->a) {
+			pc(' '); pexp(d, stm->a);
+		}
+		pc(';');
+		break;
+
+	case STM_WITH:
+		ps("with"); sp(); pc('('); pexp(d, stm->a); pc(')');
+		pstmh(d, stm->b);
+		break;
+
+	case STM_SWITCH:
+		ps("switch"); sp(); pc('(');
+		pexp(d, stm->a);
+		pc(')'); sp(); pc('{'); nl();
+		pcaselist(d, stm->b);
+		in(d); pc('}');
+		break;
+
+	case STM_THROW:
+		ps("throw "); pexp(d, stm->a); pc(';');
+		break;
+
+	case STM_TRY:
+		ps("try");
+		if (minify && stm->a->type != STM_BLOCK)
+			pc(' ');
+		pstmh(d, stm->a);
+		if (stm->b && stm->c) {
+			nl(); in(d); ps("catch"); sp(); pc('('); pexp(d, stm->b); pc(')');
+			pstmh(d, stm->c);
+		}
+		if (stm->d) {
+			nl(); in(d); ps("finally");
+			pstmh(d, stm->d);
+		}
+		break;
+
+	case STM_LABEL:
+		pexp(d, stm->a); pc(':'); sp(); pstm(d, stm->b);
+		break;
+
+	case STM_DEBUGGER:
+		ps("debugger");
+		pc(';');
+		break;
+
+	default:
+		pexp(d, stm);
+		pc(';');
+	}
+}
+
+static void pstmlist(int d, js_Ast *list)
+{
+	while (list) {
+		assert(list->type == AST_LIST);
+		pstm(d+1, list->a);
+		nl();
+		list = list->b;
+	}
+}
+
+static void jsP_dumpsyntax(js_State *J, js_Ast *prog)
+{
+	if (prog) {
+		if (prog->type == AST_LIST)
+			pstmlist(-1, prog);
+		else {
+			pstm(0, prog);
+			nl();
+		}
+	}
+	if (minify > 1)
+		putchar('\n');
+}
+
+/* S-expression list representation */
+
+static void snode(int d, js_Ast *node)
+{
+	void (*afun)(int,js_Ast*) = snode;
+	void (*bfun)(int,js_Ast*) = snode;
+	void (*cfun)(int,js_Ast*) = snode;
+	void (*dfun)(int,js_Ast*) = snode;
+
+	if (!node) {
+		return;
+	}
+
+	if (node->type == AST_LIST) {
+		slist(d, node);
+		return;
+	}
+
+	pc('(');
+	ps(astname[node->type]);
+	switch (node->type) {
+	default: break;
+	case AST_IDENTIFIER: pc(' '); ps(node->string); break;
+	case EXP_IDENTIFIER: pc(' '); ps(node->string); break;
+	case EXP_STRING: pc(' '); pstr(node->string); break;
+	case EXP_REGEXP: pc(' '); pregexp(node->string, node->number); break;
+	case EXP_NUMBER: printf(" %.9g", node->number); break;
+	case STM_BLOCK: afun = sblock; break;
+	case AST_FUNDEC: case EXP_FUN: cfun = sblock; break;
+	case EXP_PROP_GET: cfun = sblock; break;
+	case EXP_PROP_SET: cfun = sblock; break;
+	case STM_SWITCH: bfun = sblock; break;
+	case STM_CASE: bfun = sblock; break;
+	case STM_DEFAULT: afun = sblock; break;
+	}
+	if (node->a) { pc(' '); afun(d, node->a); }
+	if (node->b) { pc(' '); bfun(d, node->b); }
+	if (node->c) { pc(' '); cfun(d, node->c); }
+	if (node->d) { pc(' '); dfun(d, node->d); }
+	pc(')');
+}
+
+static void slist(int d, js_Ast *list)
+{
+	pc('[');
+	while (list) {
+		assert(list->type == AST_LIST);
+		snode(d, list->a);
+		list = list->b;
+		if (list)
+			pc(' ');
+	}
+	pc(']');
+}
+
+static void sblock(int d, js_Ast *list)
+{
+	ps("[\n");
+	in(d+1);
+	while (list) {
+		assert(list->type == AST_LIST);
+		snode(d+1, list->a);
+		list = list->b;
+		if (list) {
+			nl();
+			in(d+1);
+		}
+	}
+	nl(); in(d); pc(']');
+}
+
+static void jsP_dumplist(js_State *J, js_Ast *prog)
+{
+	if (prog) {
+		if (prog->type == AST_LIST)
+			sblock(0, prog);
+		else
+			snode(0, prog);
+		nl();
+	}
+}
+
+static void js_ppstring(js_State *J, const char *filename, const char *source)
+{
 	js_Ast *P;
+	js_Function *F;
+
 	if (js_try(J)) {
 		jsP_freeparse(J);
 		js_throw(J);
 	}
+
 	P = jsP_parse(J, filename, source);
-	if (minify > 2)
+	F = jsC_compilescript(J, P, J->default_strict);
+
+	switch (format) {
+	case 0:
+		jsP_dumpsyntax(J, P);
+		break;
+	case 1:
 		jsP_dumplist(J, P);
-	else
-		jsP_dumpsyntax(J, P, minify);
+		break;
+	case 2:
+		jsC_dumpfunction(J, F);
+		break;
+	}
+
 	jsP_freeparse(J);
 	js_endtry(J);
 }
 
-void js_ppfile(js_State *J, const char *filename, int minify)
+static void js_ppfile(js_State *J, const char *filename)
 {
 	FILE * volatile f = NULL;
 	char * volatile s = NULL;
@@ -67,7 +926,7 @@
 
 	s[n] = 0; /* zero-terminate string containing file data */
 
-	js_ppstring(J, filename, s, minify);
+	js_ppstring(J, filename, s);
 
 	js_endtry(J);
 	js_free(J, s);
@@ -74,7 +933,7 @@
 	fclose(f);
 }
 
-static void js_tryppfile(js_State *J, const char *file, int minify)
+static void js_tryppfile(js_State *J, const char *file)
 {
 	if (js_try(J)) {
 		js_report(J, js_trystring(J, -1, "Error"));
@@ -81,7 +940,7 @@
 		js_pop(J, 1);
 		return;
 	}
-	js_ppfile(J, file, minify);
+	js_ppfile(J, file);
 	js_endtry(J);
 }
 
@@ -89,20 +948,29 @@
 main(int argc, char **argv)
 {
 	js_State *J;
-	int minify = 0;
 	int i;
 
+	if (argc < 2) {
+		fprintf(stderr, "usage: mujs-pp [-m | -mm | -s | -c] input.js\n");
+		fprintf(stderr, "  -m\tminify output\n");
+		fprintf(stderr, "  -mm\tminify output more\n");
+		fprintf(stderr, "  -s\tprint syntax tree\n");
+		fprintf(stderr, "  -c\tprint bytecode\n");
+	}
+
 	J = js_newstate(NULL, NULL, 0);
 
 	for (i = 1; i < argc; ++i) {
 		if (!strcmp(argv[i], "-m"))
-			minify = 1;
+			format = 0, minify = 1;
 		else if (!strcmp(argv[i], "-mm"))
-			minify = 2;
+			format = 0, minify = 2;
 		else if (!strcmp(argv[i], "-s"))
-			minify = 3;
+			format = 1, minify = 0;
+		else if (!strcmp(argv[i], "-c"))
+			format = 2, minify = 0;
 		else
-			js_tryppfile(J, argv[i], minify);
+			js_tryppfile(J, argv[i]);
 	}
 
 	js_gc(J, 0);