aboutsummaryrefslogtreecommitdiff
path: root/lib/output.js
diff options
context:
space:
mode:
authorMihai Bazon <mihai@bazon.net>2012-08-17 15:59:42 +0300
committerMihai Bazon <mihai@bazon.net>2012-08-17 15:59:42 +0300
commit13f7b119bb7e2184dcd623e167f507b31e73725a (patch)
tree63f144da8e583624588c0bde6e330dfac93335eb /lib/output.js
parentc7c163b82e72ef8fcd5b899ffd3e003adfaf80ee (diff)
downloadtracifyjs-13f7b119bb7e2184dcd623e167f507b31e73725a.tar.gz
tracifyjs-13f7b119bb7e2184dcd623e167f507b31e73725a.zip
code generator finally seems to work properly
Diffstat (limited to 'lib/output.js')
-rw-r--r--lib/output.js445
1 files changed, 339 insertions, 106 deletions
diff --git a/lib/output.js b/lib/output.js
index c581fa87..85003290 100644
--- a/lib/output.js
+++ b/lib/output.js
@@ -8,7 +8,8 @@ function OutputStream(options) {
ascii_only : false,
inline_script : false,
width : 80,
- beautify : true
+ beautify : true,
+ scope_style : "negate"
});
function noop() {};
@@ -63,11 +64,8 @@ function OutputStream(options) {
return name;
};
- function make_indent(line) {
- if (line == null)
- line = "";
- line = repeat_string(" ", options.indent_start + indentation) + line;
- return line;
+ function make_indent(back) {
+ return repeat_string(" ", options.indent_start + indentation - back * options.indent_level);
};
function last_char() {
@@ -77,11 +75,14 @@ function OutputStream(options) {
/* -----[ beautification/minification ]----- */
var might_need_space = false;
+ var might_need_semicolon = false;
+ var last = null;
function print(str) {
+ last = str;
str = String(str);
+ var ch = str.charAt(0);
if (might_need_space) {
- var ch = str.charAt(0);
if ((is_identifier_char(last_char())
&& (is_identifier_char(ch) || ch == "\\"))
||
@@ -91,8 +92,16 @@ function OutputStream(options) {
current_col++;
current_pos++;
}
+ might_need_space = false;
+ }
+ if (might_need_semicolon) {
+ if (";{}".indexOf(ch) < 0 && !/[;]$/.test(OUTPUT)) {
+ OUTPUT += ";";
+ current_col++;
+ current_pos++;
+ }
+ might_need_semicolon = false;
}
- might_need_space = false;
var a = str.split(/\r?\n/), n = a.length;
current_line += n;
if (n == 1) {
@@ -110,9 +119,9 @@ function OutputStream(options) {
might_need_space = true;
};
- var indent = options.beautify ? function() {
+ var indent = options.beautify ? function(half) {
if (options.beautify) {
- print(make_indent());
+ print(make_indent(half ? 0.5 : 0));
}
} : noop;
@@ -129,6 +138,12 @@ function OutputStream(options) {
print("\n");
} : noop;
+ var semicolon = options.beautify ? function() {
+ print(";");
+ } : function() {
+ might_need_semicolon = true;
+ };
+
function next_indent() {
return indentation + options.indent_level;
};
@@ -156,15 +171,12 @@ function OutputStream(options) {
function with_square(cont) {
print("[");
- var ret = with_indent(current_col, cont);
+ //var ret = with_indent(current_col, cont);
+ var ret = cont();
print("]");
return ret;
};
- function semicolon() {
- print(";");
- };
-
function comma() {
print(",");
space();
@@ -184,6 +196,7 @@ function OutputStream(options) {
space : space,
comma : comma,
colon : colon,
+ last : function() { return last },
semicolon : semicolon,
print_name : function(name) { print(make_name(name)) },
print_string : function(str) { print(encode_string(str)) },
@@ -191,57 +204,170 @@ function OutputStream(options) {
with_block : with_block,
with_parens : with_parens,
with_square : with_square,
- options : function() { return options },
+ options : function(opt) { return options[opt] },
line : function() { return current_line },
col : function() { return current_col },
pos : function() { return current_pos },
push_node : function(node) { stack.push(node) },
pop_node : function() { return stack.pop() },
stack : function() { return stack },
- parent : function() { return stack[stack.length - 2] }
+ parent : function(n) {
+ return stack[stack.length - 2 - (n || 0)];
+ }
};
};
/* -----[ code generators ]----- */
-(function(DEFPRINT){
+(function(){
+
+ /* -----[ utils ]----- */
+
+ function DEFPRINT(nodetype, generator) {
+ nodetype.DEFMETHOD("print", function(stream){
+ var self = this;
+ stream.push_node(self);
+ if (self.needs_parens(stream)) {
+ stream.with_parens(function(){
+ generator(self, stream);
+ });
+ } else {
+ generator(self, stream);
+ }
+ stream.pop_node();
+ });
+ };
+
+ function PARENS(nodetype, func) {
+ nodetype.DEFMETHOD("needs_parens", func);
+ };
+
+ /* -----[ PARENTHESES ]----- */
+
+ PARENS(AST_Node, function(){
+ return false;
+ });
+
+ // a function expression needs parens around it when it's provably
+ // the first token to appear in a statement.
+ PARENS(AST_Lambda, function(output){
+ return first_in_statement(output);
+ });
+
+ // same goes for an object literal, because otherwise it would be
+ // interpreted as a block of code.
+ PARENS(AST_Object, function(output){
+ return first_in_statement(output);
+ });
+
+ // Defun inherits from Lambda, but we don't want parens here.
+ PARENS(AST_Defun, function(){
+ return false;
+ });
+
+ PARENS(AST_Seq, function(output){
+ var p = output.parent();
+ return p instanceof AST_Call
+ || p instanceof AST_Binary
+ || p instanceof AST_VarDef
+ || p instanceof AST_Dot
+ || p instanceof AST_Array
+ || p instanceof AST_ObjectProperty
+ || p instanceof AST_Conditional;
+ });
+
+ PARENS(AST_Binary, function(output){
+ var p = output.parent();
+ if (p instanceof AST_Call && p.expression === this)
+ return true;
+ if (p instanceof AST_Unary)
+ return true;
+ if (p instanceof AST_PropAccess && p.expression === this)
+ return true;
+ if (p instanceof AST_Binary) {
+ var po = p.operator, pp = PRECEDENCE[po];
+ var so = this.operator, sp = PRECEDENCE[so];
+ if (pp > sp
+ || (pp == sp
+ && this === p.right
+ && !(so == po &&
+ (so == "*" ||
+ so == "&&" ||
+ so == "||")))) {
+ return true;
+ }
+ }
+ if (this.operator == "in") {
+ // the “NoIn” stuff :-\
+ // UglifyJS 1.3.3 misses this one.
+ if ((p instanceof AST_For || p instanceof AST_ForIn) && p.init === this)
+ return true;
+ if (p instanceof AST_VarDef) {
+ var v = output.parent(1), p2 = output.parent(2);
+ if ((p2 instanceof AST_For || p2 instanceof AST_ForIn) && p2.init === v)
+ return true;
+ }
+ }
+ });
+
+ PARENS(AST_New, function(output){
+ var p = output.parent();
+ if (p instanceof AST_Dot && no_constructor_parens(this, output))
+ return true;
+ });
+
+ function assign_and_conditional_paren_rules(output) {
+ var p = output.parent();
+ if (p instanceof AST_Unary)
+ return true;
+ if (p instanceof AST_Binary && !(p instanceof AST_Assign))
+ return true;
+ if (p instanceof AST_Call && p.expression === this)
+ return true;
+ if (p instanceof AST_Conditional && p.condition === this)
+ return true;
+ if (p instanceof AST_PropAccess && p.expression === this)
+ return true;
+ };
+
+ PARENS(AST_Conditional, assign_and_conditional_paren_rules);
+ PARENS(AST_Assign, assign_and_conditional_paren_rules);
+
+ /* -----[ PRINTERS ]----- */
+
DEFPRINT(AST_Directive, function(self, output){
output.print_string(self.value);
});
DEFPRINT(AST_Debugger, function(self, output){
- output.print_string("debugger");
- });
- DEFPRINT(AST_Parenthesized, function(self, output){
- output.with_parens(function(){
- self.expression.print(output);
- });
+ output.print("debugger");
+ output.semicolon();
});
- DEFPRINT(AST_Bracketed, function(self, output){
- if (self.body.length > 0) output.with_block(function(){
- self.body.forEach(function(stmt){
+
+ /* -----[ statements ]----- */
+
+ function display_body(body, is_toplevel, output) {
+ body.forEach(function(stmt){
+ if (!(stmt instanceof AST_EmptyStatement)) {
output.indent();
stmt.print(output);
output.newline();
- });
+ if (is_toplevel) output.newline();
+ }
});
- else output.print("{}");
- });
- /* -----[ statements ]----- */
+ };
+
DEFPRINT(AST_Statement, function(self, output){
if (self.body instanceof AST_Node) {
self.body.print(output);
output.semicolon();
} else {
- self.body.forEach(function(stmt){
- stmt.print(output);
- output.newline();
- });
+ display_body(self.body, self instanceof AST_Toplevel, output);
}
});
DEFPRINT(AST_LabeledStatement, function(self, output){
- output.print(self.label + ":");
- output.space();
+ self.label.print(output);
+ output.colon();
self.statement.print(output);
});
DEFPRINT(AST_SimpleStatement, function(self, output){
@@ -249,7 +375,10 @@ function OutputStream(options) {
output.semicolon();
});
DEFPRINT(AST_BlockStatement, function(self, output){
- AST_Bracketed.prototype.print.call(self, output);
+ if (self.body.length > 0) output.with_block(function(){
+ display_body(self.body, false, output);
+ });
+ else output.print("{}");
});
DEFPRINT(AST_EmptyStatement, function(self, output){
output.semicolon();
@@ -260,8 +389,20 @@ function OutputStream(options) {
self.body.print(output);
output.space();
output.print("while");
- self.condition.print(output);
- self.semicolon();
+ output.space();
+ output.with_parens(function(){
+ self.condition.print(output);
+ });
+ output.semicolon();
+ });
+ DEFPRINT(AST_While, function(self, output){
+ output.print("while");
+ output.space();
+ output.with_parens(function(){
+ self.condition.print(output);
+ });
+ output.space();
+ self.body.print(output);
});
DEFPRINT(AST_For, function(self, output){
output.print("for");
@@ -269,17 +410,17 @@ function OutputStream(options) {
output.with_parens(function(){
if (self.init) {
self.init.print(output);
- output.semicolon();
+ output.print(";");
output.space();
} else {
- output.semicolon();
+ output.print(";");
}
if (self.condition) {
self.condition.print(output);
- output.semicolon();
+ output.print(";");
output.space();
} else {
- output.semicolon();
+ output.print(";");
}
if (self.step) {
self.step.print(output);
@@ -312,11 +453,15 @@ function OutputStream(options) {
output.space();
self.body.print(output);
});
+
/* -----[ functions ]----- */
- DEFPRINT(AST_Lambda, function(self, output){
- output.print("function");
- output.space();
+ AST_Lambda.DEFMETHOD("_do_print", function(output, nokeyword){
+ var self = this;
+ if (!nokeyword) {
+ output.print("function");
+ }
if (self.name) {
+ output.space();
self.name.print(output);
}
output.with_parens(function(){
@@ -328,11 +473,17 @@ function OutputStream(options) {
output.space();
self.body.print(output);
});
+ DEFPRINT(AST_Lambda, function(self, output){
+ self._do_print(output);
+ });
+
/* -----[ exits ]----- */
AST_Exit.DEFMETHOD("_do_print", function(output, kind){
output.print(kind);
- output.space();
- this.value.print(output);
+ if (this.value) {
+ output.space();
+ this.value.print(output);
+ }
output.semicolon();
});
DEFPRINT(AST_Return, function(self, output){
@@ -341,6 +492,7 @@ function OutputStream(options) {
DEFPRINT(AST_Throw, function(self, output){
self._do_print(output, "throw");
});
+
/* -----[ loop control ]----- */
AST_LoopControl.DEFMETHOD("_do_print", function(output, kind){
output.print(kind);
@@ -356,6 +508,7 @@ function OutputStream(options) {
DEFPRINT(AST_Continue, function(self, output){
self._do_print(output, "continue");
});
+
/* -----[ if ]----- */
DEFPRINT(AST_If, function(self, output){
output.print("if");
@@ -367,9 +520,12 @@ function OutputStream(options) {
self.consequent.print(output);
if (self.alternative) {
output.space();
+ output.print("else");
+ output.space();
self.alternative.print(output);
}
});
+
/* -----[ switch ]----- */
DEFPRINT(AST_Switch, function(self, output){
output.print("switch");
@@ -380,16 +536,28 @@ function OutputStream(options) {
output.space();
self.body.print(output);
});
+ DEFPRINT(AST_SwitchBlock, function(self, output){
+ if (self.body.length > 0) output.with_block(function(){
+ self.body.forEach(function(stmt, i){
+ if (i) output.newline();
+ output.indent(true);
+ stmt.print(output);
+ });
+ });
+ else output.print("{}");
+ });
AST_SwitchBranch.DEFMETHOD("_do_print_body", function(output){
- this.body.forEach(function(stmt){
- output.indent();
- stmt.print(output);
+ if (this.body.length > 0) {
output.newline();
- });
+ this.body.forEach(function(stmt){
+ output.indent();
+ stmt.print(output);
+ output.newline();
+ });
+ }
});
DEFPRINT(AST_Default, function(self, output){
output.print("default:");
- output.newline();
self._do_print_body(output);
});
DEFPRINT(AST_Case, function(self, output){
@@ -397,9 +565,9 @@ function OutputStream(options) {
output.space();
self.expression.print(output);
output.print(":");
- output.newline();
self._do_print_body(output);
});
+
/* -----[ exceptions ]----- */
DEFPRINT(AST_Try, function(self, output){
output.print("try");
@@ -417,6 +585,10 @@ function OutputStream(options) {
DEFPRINT(AST_Catch, function(self, output){
output.print("catch");
output.space();
+ output.with_parens(function(){
+ self.argname.print(output);
+ });
+ output.space();
self.body.print(output);
});
DEFPRINT(AST_Finally, function(self, output){
@@ -424,6 +596,7 @@ function OutputStream(options) {
output.space();
self.body.print(output);
});
+
/* -----[ var/const ]----- */
AST_Definitions.DEFMETHOD("_do_print", function(output, kind){
output.print(kind);
@@ -432,7 +605,11 @@ function OutputStream(options) {
if (i) output.comma();
def.print(output);
});
- if (!this.inline) output.semicolon();
+ var p = output.parent();
+ var in_for = p instanceof AST_For || p instanceof AST_ForIn;
+ var avoid_semicolon = in_for && p.init === this;
+ if (!avoid_semicolon)
+ output.semicolon();
});
DEFPRINT(AST_Var, function(self, output){
self._do_print(output, "var");
@@ -449,16 +626,24 @@ function OutputStream(options) {
self.value.print(output);
}
});
+
/* -----[ other expressions ]----- */
DEFPRINT(AST_Call, function(self, output){
self.expression.print(output);
+ if (self instanceof AST_New && no_constructor_parens(self, output))
+ return;
output.with_parens(function(){
- self.args.forEach(function(arg, i){
+ self.args.forEach(function(expr, i){
if (i) output.comma();
- arg.print(output);
+ expr.print(output);
});
});
});
+ function no_constructor_parens(self, output) {
+ return (self.args.length == 0
+ // && !output.options("beautify")
+ );
+ };
DEFPRINT(AST_New, function(self, output){
output.print("new");
output.space();
@@ -470,7 +655,12 @@ function OutputStream(options) {
self.second.print(output);
});
DEFPRINT(AST_Dot, function(self, output){
- self.expression.print(output);
+ var expr = self.expression;
+ expr.print(output);
+ if (expr instanceof AST_Number) {
+ if (!/[xa-f.]/i.test(output.last()))
+ output.print(".");
+ }
output.print(".");
output.print_name(self.property);
});
@@ -488,43 +678,12 @@ function OutputStream(options) {
self.expression.print(output);
output.print(self.operator);
});
- AST_Binary.DEFMETHOD("_do_print", function(output){
- this.left.print(output);
+ DEFPRINT(AST_Binary, function(self, output){
+ self.left.print(output);
output.space();
- output.print(this.operator);
+ output.print(self.operator);
output.space();
- this.right.print(output);
- });
- DEFPRINT(AST_Binary, function(self, output){
- var p = output.parent();
- if (p instanceof AST_Binary) {
- var po = p.operator, pp = PRECEDENCE[po];
- var so = self.operator, sp = PRECEDENCE[so];
- if (pp > sp
- || (pp == sp
- && self === p.right
- && !(so == po &&
- (so == "*" ||
- so == "&&" ||
- so == "||")))) {
- output.with_parens(function(){
- self._do_print(output);
- });
- return;
- }
- }
- self._do_print(output);
- });
- // XXX: this is quite similar as for AST_Binary, except for the parens.
- DEFPRINT(AST_Assign, function(self, output){
- var p = output.parent();
- if (p instanceof AST_Binary) {
- output.with_parens(function(){
- self._do_print(output);
- });
- return;
- }
- self._do_print(output);
+ self.right.print(output);
});
DEFPRINT(AST_Conditional, function(self, output){
self.condition.print(output);
@@ -532,9 +691,11 @@ function OutputStream(options) {
output.print("?");
output.space();
self.consequent.print(output);
+ output.space();
output.colon();
self.alternative.print(output);
});
+
/* -----[ literals ]----- */
DEFPRINT(AST_Array, function(self, output){
output.with_square(function(){
@@ -547,24 +708,43 @@ function OutputStream(options) {
DEFPRINT(AST_Object, function(self, output){
if (self.properties.length > 0) output.with_block(function(){
self.properties.forEach(function(prop, i){
- if (i) output.comma();
+ if (i) {
+ output.comma();
+ output.newline();
+ }
output.indent();
prop.print(output);
- output.newline();
});
+ output.newline();
});
else output.print("{}");
});
DEFPRINT(AST_ObjectKeyVal, function(self, output){
- output.print_name(self.key);
+ var key = self.key;
+ if (output.options("quote_keys")) {
+ output.print_string(key);
+ } else if ((typeof key == "number"
+ || !output.options("beautify")
+ && +key + "" == key)
+ && parseFloat(key) >= 0) {
+ output.print(make_num(key));
+ } else if (!is_identifier(key)) {
+ output.print_string(key);
+ } else {
+ output.print_name(key);
+ }
output.colon();
self.value.print(output);
});
DEFPRINT(AST_ObjectSetter, function(self, output){
- throw "not yet done";
+ output.print("set");
+ output.space();
+ self.func._do_print(output, true);
});
DEFPRINT(AST_ObjectGetter, function(self, output){
- throw "not yet done";
+ output.print("get");
+ output.space();
+ self.func._do_print(output, true);
});
DEFPRINT(AST_Symbol, function(self, output){
output.print_name(self.name);
@@ -581,16 +761,69 @@ function OutputStream(options) {
DEFPRINT(AST_String, function(self, output){
output.print_string(self.getValue());
});
+ DEFPRINT(AST_Number, function(self, output){
+ output.print(make_num(self.getValue()));
+ });
DEFPRINT(AST_RegExp, function(self, output){
output.print("/");
output.print(self.pattern);
output.print("/");
if (self.mods) output.print(self.mods);
});
-})(function DEF(nodetype, generator) {
- nodetype.DEFMETHOD("print", function(stream){
- stream.push_node(this);
- generator(this, stream);
- stream.pop_node();
- });
-});
+
+ // return true if the node at the top of the stack (that means the
+ // innermost node in the current output) is lexically the first in
+ // a statement.
+ function first_in_statement(output) {
+ var a = output.stack(), i = a.length, node = a[--i], p = a[--i];
+ while (i > 0) {
+ if (p instanceof AST_Statement) return true;
+ if ((p instanceof AST_Seq && p.first === node) ||
+ (p instanceof AST_Call && p.expression === node) ||
+ (p instanceof AST_Dot && p.expression === node) ||
+ (p instanceof AST_Sub && p.expression === node) ||
+ (p instanceof AST_Conditional && p.condition === node) ||
+ (p instanceof AST_Binary && p.first === node) ||
+ (p instanceof AST_Assign && p.first === node) ||
+ (p instanceof AST_UnaryPostfix && p.expression === node))
+ {
+ node = p;
+ p = a[--i];
+ } else {
+ return false;
+ }
+ }
+ };
+
+ function best_of(a) {
+ var best = a[0], len = best.length;
+ for (var i = 1; i < a.length; ++i) {
+ if (a[i].length < len) {
+ best = a[i];
+ len = best.length;
+ }
+ }
+ return best;
+ };
+
+ function make_num(num) {
+ var str = num.toString(10), a = [ str.replace(/^0\./, ".").replace('e+', 'e') ], m;
+ if (Math.floor(num) === num) {
+ if (num >= 0) {
+ a.push("0x" + num.toString(16).toLowerCase(), // probably pointless
+ "0" + num.toString(8)); // same.
+ } else {
+ a.push("-0x" + (-num).toString(16).toLowerCase(), // probably pointless
+ "-0" + (-num).toString(8)); // same.
+ }
+ if ((m = /^(.*?)(0+)$/.exec(num))) {
+ a.push(m[1] + "e" + m[2].length);
+ }
+ } else if ((m = /^0?\.(0+)(.*)$/.exec(num))) {
+ a.push(m[2] + "e-" + (m[1].length + m[2].length),
+ str.substr(str.indexOf(".")));
+ }
+ return best_of(a);
+ };
+
+})();