diff options
author | Mihai Bazon <mihai@bazon.net> | 2012-08-17 15:59:42 +0300 |
---|---|---|
committer | Mihai Bazon <mihai@bazon.net> | 2012-08-17 15:59:42 +0300 |
commit | 13f7b119bb7e2184dcd623e167f507b31e73725a (patch) | |
tree | 63f144da8e583624588c0bde6e330dfac93335eb /lib/output.js | |
parent | c7c163b82e72ef8fcd5b899ffd3e003adfaf80ee (diff) | |
download | tracifyjs-13f7b119bb7e2184dcd623e167f507b31e73725a.tar.gz tracifyjs-13f7b119bb7e2184dcd623e167f507b31e73725a.zip |
code generator finally seems to work properly
Diffstat (limited to 'lib/output.js')
-rw-r--r-- | lib/output.js | 445 |
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); + }; + +})(); |