shithub: libmujs

Download patch

ref: d04cbf3cc7ece02439e24f671af19c07e7745ae2
parent: 25821e6d74fab5fcc200fe5e818362e03e114428
author: Tor Andersson <tor.andersson@artifex.com>
date: Mon Apr 23 07:46:18 EDT 2018

Add pretty-printing command line tool.

--- a/Makefile
+++ b/Makefile
@@ -8,9 +8,9 @@
 libdir ?= $(prefix)/lib
 
 ifeq "$(wildcard .git)" ".git"
-VERSION := $(shell git describe --tags --always)
+  VERSION := $(shell git describe --tags --always)
 else
-VERSION := $(shell basename $$PWD | sed -e s,^mujs-,,)
+  VERSION := $(shell basename $$PWD | sed -e s,^mujs-,,)
 endif
 
 # Compiler flags for various configurations:
@@ -18,22 +18,22 @@
 CFLAGS := -std=c99 -pedantic -Wall -Wextra -Wno-unused-parameter
 
 ifeq "$(CC)" "clang"
-CFLAGS += -Wunreachable-code
+  CFLAGS += -Wunreachable-code
 endif
 
 ifeq "$(shell uname)" "Linux"
-CFLAGS += -ffunction-sections -fdata-sections
-LDFLAGS += -Wl,--gc-sections
+  CFLAGS += -ffunction-sections -fdata-sections
+  LDFLAGS += -Wl,--gc-sections
 endif
 
 ifeq "$(build)" "debug"
-CFLAGS += -g
+  CFLAGS += -g
 else ifeq "$(build)" "sanitize"
-CFLAGS += -pipe -g -fsanitize=address -fno-omit-frame-pointer
-LDFLAGS += -fsanitize=address
+  CFLAGS += -pipe -g -fsanitize=address -fno-omit-frame-pointer
+  LDFLAGS += -fsanitize=address
 else
-CFLAGS += -Os
-LDFLAGS += -Wl,-s
+  CFLAGS += -Os
+  LDFLAGS += -Wl,-s
 endif
 
 CFLAGS += $(XCFLAGS)
@@ -46,7 +46,7 @@
 HDRS := $(wildcard js*.h mujs.h utf.h regexp.h)
 
 default: static
-static: $(OUT) $(OUT)/mujs $(OUT)/libmujs.a $(OUT)/mujs.pc
+static: $(OUT)/mujs-pp $(OUT)/mujs $(OUT)/libmujs.a $(OUT)/mujs.pc
 shared: static $(OUT)/libmujs.so
 
 astnames.h: jsparse.h
@@ -60,22 +60,28 @@
 
 jsdump.c: astnames.h opnames.h
 
-$(OUT):
-	mkdir -p $(OUT)
-
-$(OUT)/main.o: main.c $(HDRS)
+$(OUT)/%.o: %.c $(HDRS)
+	@ mkdir -p $(dir $@)
 	$(CC) $(CFLAGS) -o $@ -c $<
 
 $(OUT)/libmujs.o: one.c $(HDRS)
+	@ mkdir -p $(dir $@)
 	$(CC) $(CFLAGS) -o $@ -c $<
 
 $(OUT)/libmujs.a: $(OUT)/libmujs.o
+	@ mkdir -p $(dir $@)
 	$(AR) cru $@ $^
 
 $(OUT)/libmujs.so: one.c $(HDRS)
+	@ mkdir -p $(dir $@)
 	$(CC) $(CFLAGS) -fPIC -shared -o $@ $< -lm
 
 $(OUT)/mujs: $(OUT)/libmujs.o $(OUT)/main.o
+	@ mkdir -p $(dir $@)
+	$(CC) $(LDFLAGS) -o $@ $^ -lm
+
+$(OUT)/mujs-pp: $(OUT)/libmujs.o $(OUT)/pp.o
+	@ mkdir -p $(dir $@)
 	$(CC) $(LDFLAGS) -o $@ $^ -lm
 
 $(OUT)/mujs.pc:
--- a/jsdump.c
+++ b/jsdump.c
@@ -17,6 +17,8 @@
 NULL
 };
 
+static int minify = 0;
+
 const char *jsP_aststring(enum js_AstType type)
 {
 	if (type < nelem(astname)-1)
@@ -34,6 +36,7 @@
 static int prec(enum js_AstType type)
 {
 	switch (type) {
+	case AST_IDENTIFIER:
 	case EXP_IDENTIFIER:
 	case EXP_NUMBER:
 	case EXP_STRING:
@@ -142,15 +145,29 @@
 
 static void in(int d)
 {
-	while (d-- > 0)
-		putchar('\t');
+	if (minify < 1)
+		while (d-- > 0)
+			putchar('\t');
 }
 
 static void nl(void)
 {
-	putchar('\n');
+	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);
@@ -166,26 +183,30 @@
 		pexpi(d, COMMA, list->a);
 		list = list->b;
 		if (list)
-			ps(", ");
+			comma();
 	}
 }
 
 static void parray(int d, js_Ast *list)
 {
-	ps("[");
+	pc('[');
 	while (list) {
 		assert(list->type == AST_LIST);
 		pexpi(d, COMMA, list->a);
 		list = list->b;
 		if (list)
-			ps(", ");
+			comma();
 	}
-	ps("]");
+	pc(']');
 }
 
 static void pobject(int d, js_Ast *list)
 {
-	ps("{");
+	pc('{');
+	if (list) {
+		nl();
+		in(d+1);
+	}
 	while (list) {
 		js_Ast *kv = list->a;
 		assert(list->type == AST_LIST);
@@ -192,32 +213,38 @@
 		switch (kv->type) {
 		default: break;
 		case EXP_PROP_VAL:
-			pexpi(d, COMMA, kv->a);
-			ps(": ");
-			pexpi(d, COMMA, kv->b);
+			pexpi(d+1, COMMA, kv->a);
+			pc(':'); sp();
+			pexpi(d+1, COMMA, kv->b);
 			break;
 		case EXP_PROP_GET:
 			ps("get ");
-			pexpi(d, COMMA, kv->a);
-			ps("() {\n");
-			pstmlist(d, kv->c);
-			in(d); ps("}");
+			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, COMMA, kv->a);
-			ps("(");
-			pargs(d, kv->b);
-			ps(") {\n");
-			pstmlist(d, kv->c);
-			in(d); ps("}");
+			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)
-			ps(", ");
+		if (list) {
+			pc(',');
+			nl();
+			in(d+1);
+		} else {
+			nl();
+			in(d);
+		}
 	}
-	ps("}");
+	pc('}');
 }
 
 static void pstr(const char *s)
@@ -224,10 +251,11 @@
 {
 	static const char *HEX = "0123456789ABCDEF";
 	Rune c;
-	pc('"');
+	pc(minify ? '\'' : '"');
 	while (*s) {
 		s += chartorune(&c, s);
 		switch (c) {
+		case '\'': ps("\\'"); break;
 		case '"': ps("\\\""); break;
 		case '\\': ps("\\\\"); break;
 		case '\b': ps("\\b"); break;
@@ -247,7 +275,7 @@
 			}
 		}
 	}
-	pc('"');
+	pc(minify ? '\'' : '"');
 }
 
 static void pregexp(const char *prog, int flags)
@@ -263,7 +291,9 @@
 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);
 }
 
@@ -316,49 +346,58 @@
 	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_INSTANCEOF: pbin(d, p, exp, " instanceof "); 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_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_COMMA: 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);
-		ps(" ? ");
+		sp(); pc('?'); sp();
 		pexpi(d, p, exp->b);
-		ps(" : ");
+		sp(); pc(':'); sp();
 		pexpi(d, p, exp->c);
 		break;
 
@@ -396,7 +435,7 @@
 		pexpi(d, 0, exp->a);
 		pc('(');
 		pargs(d, exp->b);
-		ps(") {\n");
+		pc(')'); sp(); pc('{'); nl();
 		pstmlist(d, exp->c);
 		in(d); pc('}');
 		if (p == 0) pc(')');
@@ -420,7 +459,7 @@
 	assert(var->type == EXP_VAR);
 	pexp(d, var->a);
 	if (var->b) {
-		ps(" = ");
+		sp(); pc('='); sp();
 		pexp(d, var->b);
 	}
 }
@@ -432,7 +471,7 @@
 		pvar(d, list->a);
 		list = list->b;
 		if (list)
-			ps(", ");
+			comma();
 	}
 }
 
@@ -439,7 +478,7 @@
 static void pblock(int d, js_Ast *block)
 {
 	assert(block->type == STM_BLOCK);
-	ps(" {\n");
+	pc('{'); nl();
 	pstmlist(d, block->a);
 	in(d); pc('}');
 }
@@ -446,9 +485,10 @@
 
 static void pstmh(int d, js_Ast *stm)
 {
-	if (stm->type == STM_BLOCK)
+	if (stm->type == STM_BLOCK) {
+		sp();
 		pblock(d, stm);
-	else {
+	} else {
 		nl();
 		pstm(d+1, stm);
 	}
@@ -459,11 +499,11 @@
 	while (list) {
 		js_Ast *stm = list->a;
 		if (stm->type == STM_CASE) {
-			in(d); ps("case "); pexp(d, stm->a); ps(":\n");
+			in(d); ps("case "); pexp(d, stm->a); pc(':'); nl();
 			pstmlist(d, stm->b);
 		}
 		if (stm->type == STM_DEFAULT) {
-			in(d); ps("default:\n");
+			in(d); ps("default:"); nl();
 			pstmlist(d, stm->a);
 		}
 		list = list->b;
@@ -485,9 +525,9 @@
 		pexp(d, stm->a);
 		pc('(');
 		pargs(d, stm->b);
-		ps(") {\n");
+		pc(')'); sp(); pc('{'); nl();
 		pstmlist(d, stm->c);
-		in(d); ps("}");
+		in(d); pc('}');
 		break;
 
 	case STM_EMPTY:
@@ -497,11 +537,11 @@
 	case STM_VAR:
 		ps("var ");
 		pvarlist(d, stm->a);
-		ps(";");
+		pc(';');
 		break;
 
 	case STM_IF:
-		ps("if ("); pexp(d, stm->a); ps(")");
+		ps("if"); sp(); pc('('); pexp(d, stm->a); pc(')');
 		pstmh(d, stm->b);
 		if (stm->c) {
 			nl(); in(d); ps("else");
@@ -513,87 +553,89 @@
 		ps("do");
 		pstmh(d, stm->a);
 		nl();
-		in(d); ps("while ("); pexp(d, stm->b); ps(");");
+		in(d); ps("while"); sp(); pc('('); pexp(d, stm->b); pc(')'); pc(';');
 		break;
 
 	case STM_WHILE:
-		ps("while ("); pexp(d, stm->a); ps(")");
+		ps("while"); sp(); pc('('); pexp(d, stm->a); pc(')');
 		pstmh(d, stm->b);
 		break;
 
 	case STM_FOR:
-		ps("for (");
-		pexp(d, stm->a); ps("; ");
-		pexp(d, stm->b); ps("; ");
-		pexp(d, stm->c); ps(")");
+		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 (var ");
-		pvarlist(d, stm->a); ps("; ");
-		pexp(d, stm->b); ps("; ");
-		pexp(d, stm->c); ps(")");
+		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 (");
+		ps("for"); sp(); pc('(');
 		pexp(d, stm->a); ps(" in ");
-		pexp(d, stm->b); ps(")");
+		pexp(d, stm->b); pc(')');
 		pstmh(d, stm->c);
 		break;
 	case STM_FOR_IN_VAR:
-		ps("for (var ");
+		ps("for"); sp(); ps("(var ");
 		pvarlist(d, stm->a); ps(" in ");
-		pexp(d, stm->b); ps(")");
+		pexp(d, stm->b); pc(')');
 		pstmh(d, stm->c);
 		break;
 
 	case STM_CONTINUE:
+		ps("continue");
 		if (stm->a) {
-			ps("continue "); pexp(d, stm->a); ps(";");
-		} else {
-			ps("continue;");
+			pc(' '); pexp(d, stm->a);
 		}
+		pc(';');
 		break;
 
 	case STM_BREAK:
+		ps("break");
 		if (stm->a) {
-			ps("break "); pexp(d, stm->a); ps(";");
-		} else {
-			ps("break;");
+			pc(' '); pexp(d, stm->a);
 		}
+		pc(';');
 		break;
 
 	case STM_RETURN:
+		ps("return");
 		if (stm->a) {
-			ps("return "); pexp(d, stm->a); ps(";");
-		} else {
-			ps("return;");
+			pc(' '); pexp(d, stm->a);
 		}
+		pc(';');
 		break;
 
 	case STM_WITH:
-		ps("with ("); pexp(d, stm->a); ps(")");
+		ps("with"); sp(); pc('('); pexp(d, stm->a); pc(')');
 		pstmh(d, stm->b);
 		break;
 
 	case STM_SWITCH:
-		ps("switch (");
+		ps("switch"); sp(); pc('(');
 		pexp(d, stm->a);
-		ps(") {\n");
+		pc(')'); sp(); pc('{'); nl();
 		pcaselist(d, stm->b);
-		in(d); ps("}");
+		in(d); pc('}');
 		break;
 
 	case STM_THROW:
-		ps("throw "); pexp(d, stm->a); ps(";");
+		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 ("); pexp(d, stm->b); ps(")");
+			nl(); in(d); ps("catch"); sp(); pc('('); pexp(d, stm->b); pc(')');
 			pstmh(d, stm->c);
 		}
 		if (stm->d) {
@@ -603,15 +645,17 @@
 		break;
 
 	case STM_LABEL:
-		pexp(d, stm->a); ps(": "); pstm(d, stm->b);
+		pexp(d, stm->a); pc(':'); sp(); pstm(d, stm->b);
 		break;
 
 	case STM_DEBUGGER:
-		ps("debugger;");
+		ps("debugger");
+		pc(';');
 		break;
 
 	default:
-		pexp(d, stm); pc(';');
+		pexp(d, stm);
+		pc(';');
 	}
 }
 
@@ -625,8 +669,9 @@
 	}
 }
 
-void jsP_dumpsyntax(js_State *J, js_Ast *prog)
+void jsP_dumpsyntax(js_State *J, js_Ast *prog, int dominify)
 {
+	minify = dominify;
 	if (prog->type == AST_LIST)
 		pstmlist(-1, prog);
 	else {
@@ -633,6 +678,8 @@
 		pstm(0, prog);
 		nl();
 	}
+	if (minify > 1)
+		putchar('\n');
 }
 
 /* S-expression list representation */
@@ -708,6 +755,7 @@
 
 void jsP_dumplist(js_State *J, js_Ast *prog)
 {
+	minify = 0;
 	if (prog->type == AST_LIST)
 		sblock(0, prog);
 	else
@@ -723,6 +771,8 @@
 	js_Instruction *end = F->code + F->codelen;
 	int i;
 
+	minify = 0;
+
 	printf("%s(%d)\n", F->name, F->numparams);
 	if (F->lightweight) printf("\tlightweight\n");
 	if (F->arguments) printf("\targuments\n");
@@ -802,6 +852,7 @@
 
 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;
@@ -843,6 +894,7 @@
 
 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);
@@ -854,6 +906,7 @@
 
 void js_dumpobject(js_State *J, js_Object *obj)
 {
+	minify = 0;
 	printf("{\n");
 	if (obj->properties->level)
 		js_dumpproperty(J, obj->properties);
--- a/jsparse.h
+++ b/jsparse.h
@@ -140,7 +140,7 @@
 void jsP_freeparse(js_State *J);
 
 const char *jsP_aststring(enum js_AstType type);
-void jsP_dumpsyntax(js_State *J, js_Ast *prog);
+void jsP_dumpsyntax(js_State *J, js_Ast *prog, int minify);
 void jsP_dumplist(js_State *J, js_Ast *prog);
 
 #endif
--- /dev/null
+++ b/pp.c
@@ -1,0 +1,115 @@
+/* Pretty-print input source by emitting parse tree back as syntax.
+ * with no flags: pretty-printed source
+ * with -m: minified source with line breaks
+ * with -mm: minified source without line breaks
+ * with -s: s-expression syntax tree
+ */
+
+#include <stdio.h>
+
+#include "jsi.h"
+#include "jsparse.h"
+
+static void js_ppstring(js_State *J, const char *filename, const char *source, int minify)
+{
+	js_Ast *P;
+	if (js_try(J)) {
+		jsP_freeparse(J);
+		js_throw(J);
+	}
+	P = jsP_parse(J, filename, source);
+	if (minify > 2)
+		jsP_dumplist(J, P);
+	else
+		jsP_dumpsyntax(J, P, minify);
+	jsP_freeparse(J);
+	js_endtry(J);
+}
+
+void js_ppfile(js_State *J, const char *filename, int minify)
+{
+	FILE *f;
+	char *s;
+	int n, t;
+
+	f = fopen(filename, "rb");
+	if (!f) {
+		js_error(J, "cannot open file: '%s'", filename);
+	}
+
+	if (fseek(f, 0, SEEK_END) < 0) {
+		fclose(f);
+		js_error(J, "cannot seek in file: '%s'", filename);
+	}
+
+	n = ftell(f);
+	if (n < 0) {
+		fclose(f);
+		js_error(J, "cannot tell in file: '%s'", filename);
+	}
+
+	if (fseek(f, 0, SEEK_SET) < 0) {
+		fclose(f);
+		js_error(J, "cannot seek in file: '%s'", filename);
+	}
+
+	s = js_malloc(J, n + 1); /* add space for string terminator */
+	if (!s) {
+		fclose(f);
+		js_error(J, "cannot allocate storage for file contents: '%s'", filename);
+	}
+
+	t = fread(s, 1, (size_t)n, f);
+	if (t != n) {
+		js_free(J, s);
+		fclose(f);
+		js_error(J, "cannot read data from file: '%s'", filename);
+	}
+
+	s[n] = 0; /* zero-terminate string containing file data */
+
+	if (js_try(J)) {
+		js_free(J, s);
+		fclose(f);
+		js_throw(J);
+	}
+
+	js_ppstring(J, filename, s, minify);
+
+	js_free(J, s);
+	fclose(f);
+	js_endtry(J);
+}
+
+int
+main(int argc, char **argv)
+{
+	js_State *J;
+	int minify = 0;
+	int i;
+
+	J = js_newstate(NULL, NULL, 0);
+
+	for (i = 1; i < argc; ++i) {
+		if (!strcmp(argv[i], "-m"))
+			minify = 1;
+		else if (!strcmp(argv[i], "-mm"))
+			minify = 2;
+		else if (!strcmp(argv[i], "-s"))
+			minify = 3;
+		else {
+			if (js_try(J)) {
+				js_report(J, js_trystring(J, -1, "Error"));
+				js_pop(J, 1);
+				continue;
+			}
+			js_ppfile(J, argv[i], minify);
+			js_endtry(J);
+		}
+	}
+
+	js_gc(J, 0);
+	js_freestate(J);
+
+	return 0;
+}