vendor/uglifyjs/lib/process.js in uglifier-0.1.0 vs vendor/uglifyjs/lib/process.js in uglifier-0.1.1

- old
+ new

@@ -403,13 +403,11 @@ }, "try": function(t, c, f) { if (c != null) return [ "try", t.map(walk), - with_new_scope(function(){ - return [ define(c[0]), c[1].map(walk) ]; - }), + [ define(c[0]), c[1].map(walk) ], f != null ? f.map(walk) : null ]; }, "name": function(name) { if (name == "eval") @@ -507,13 +505,11 @@ return [ "name", get_mangled(name) ]; }, "try": function(t, c, f) { return [ "try", t.map(walk), - c ? with_scope(c.scope, function(){ - return [ get_mangled(c[0]), c[1].map(walk) ]; - }) : null, + c != null ? [ get_mangled(c[0]), c[1].map(walk) ] : null, f != null ? f.map(walk) : null ]; }, "toplevel": function(body) { return with_scope(this.scope, function(){ return [ "toplevel", body.map(walk) ]; @@ -525,40 +521,10 @@ }, function() { return walk(ast_add_scope(ast)); }); }; -// function ast_has_side_effects(ast) { -// var w = ast_walker(); -// var FOUND_SIDE_EFFECTS = {}; -// function _found() { throw FOUND_SIDE_EFFECTS }; -// try { -// w.with_walkers({ -// "new": _found, -// "call": _found, -// "assign": _found, -// "defun": _found, -// "var": _found, -// "const": _found, -// "throw": _found, -// "return": _found, -// "break": _found, -// "continue": _found, -// "label": _found, -// "function": function(name) { -// if (name) _found(); -// } -// }, function(){ -// w.walk(ast); -// }); -// } catch(ex) { -// if (ex === FOUND_SIDE_EFFECTS) -// return true; -// throw ex; -// } -// }; - /* -----[ - compress foo["bar"] into foo.bar, - remove block brackets {} where possible - join consecutive var declarations - various optimizations for IFs: @@ -570,39 +536,176 @@ function warn(msg) { sys.debug(msg); }; +function best_of(ast1, ast2) { + return gen_code(ast1).length > gen_code(ast2[0] == "stat" ? ast2[1] : ast2).length ? ast2 : ast1; +}; + +function last_stat(b) { + if (b[0] == "block" && b[1] && b[1].length > 0) + return b[1][b[1].length - 1]; + return b; +} + +function aborts(t) { + if (t) { + t = last_stat(t); + if (t[0] == "return" || t[0] == "break" || t[0] == "continue" || t[0] == "throw") + return true; + } +}; + +function negate(c) { + var not_c = [ "unary-prefix", "!", c ]; + switch (c[0]) { + case "unary-prefix": + return c[1] == "!" ? c[2] : c; + case "atom": + switch (c[1]) { + case "false": return [ "num", 0 ]; + case "true": return [ "num", 1 ]; + } + break; + case "binary": + var op = c[1], left = c[2], right = c[3]; + switch (op) { + case "<=": return [ "binary", ">", left, right ]; + case "<": return [ "binary", ">=", left, right ]; + case ">=": return [ "binary", "<", left, right ]; + case ">": return [ "binary", "<=", left, right ]; + case "==": return [ "binary", "!=", left, right ]; + case "!=": return [ "binary", "==", left, right ]; + case "===": return [ "binary", "!==", left, right ]; + case "!==": return [ "binary", "===", left, right ]; + case "&&": return best_of(not_c, [ "binary", "||", negate(left), negate(right) ]); + case "||": return best_of(not_c, [ "binary", "&&", negate(left), negate(right) ]); + } + break; + } + return not_c; +}; + +function make_conditional(c, t, e) { + if (c[0] == "unary-prefix" && c[1] == "!") { + return e ? [ "conditional", c[2], e, t ] : [ "binary", "||", c[2], t ]; + } else { + return e ? [ "conditional", c, t, e ] : [ "binary", "&&", c, t ]; + } +}; + +function empty(b) { + return !b || (b[0] == "block" && (!b[1] || b[1].length == 0)); +}; + function ast_squeeze(ast, options) { options = defaults(options, { - make_seqs: true, - dead_code: true, - no_warnings: false + make_seqs : true, + dead_code : true, + no_warnings : false, + extra : false }); var w = ast_walker(), walk = w.walk; function is_constant(node) { return node[0] == "string" || node[0] == "num"; }; + function find_first_execute(node) { + if (!node) + return false; + + switch (node[0]) { + case "num": + case "string": + case "name": + return node; + case "call": + case "conditional": + case "for": + case "if": + case "new": + case "return": + case "stat": + case "switch": + case "throw": + return find_first_execute(node[1]); + case "binary": + return find_first_execute(node[2]); + case "assign": + if (node[1] === true) + return find_first_execute(node[3]); + break; + case "var": + if (node[1][0].length > 1) + return find_first_execute(node[1][0][1]); + break; + } + return null; + } + + function find_assign_recursive(p, v) { + if (p[0] == "assign" && p[1] != true || p[0] == "unary-prefix") { + if (p[2][0] == "name" && v[0] == "name" && p[2][1] == v[1]) + return true; + return false; + } + + if (p[0] != "assign" || p[1] !== true) + return false; + + if ((is_constant(p[3]) && p[3][0] == v[0] && p[3][1] == v[1]) || + (p[3][0] == "name" && v[0] == "name" && p[3][1] == v[1]) || + (p[2][0] == "name" && v[0] == "name" && p[2][1] == v[1])) + return true; + + return find_assign_recursive(p[3], v); + }; + function rmblock(block) { if (block != null && block[0] == "block" && block[1] && block[1].length == 1) block = block[1][0]; return block; }; + function clone(obj) { + if (obj && obj.constructor == Array) + return obj.map(clone); + return obj; + }; + + function make_seq_to_statements(node) { + if (node[0] != "seq") { + switch (node[0]) { + case "var": + case "const": + return [ node ]; + default: + return [ [ "stat", node ] ]; + } + } + + var ret = []; + for (var i = 1; i < node.length; i++) + ret.push.apply(ret, make_seq_to_statements(node[i])); + + return ret; + }; + function _lambda(name, args, body) { return [ this[0], name, args, tighten(body.map(walk), "lambda") ]; }; // we get here for blocks that have been already transformed. - // this function does two things: + // this function does a few things: // 1. discard useless blocks // 2. join consecutive var declarations // 3. remove obviously dead code // 4. transform consecutive statements using the comma operator + // 5. if block_type == "lambda" and it detects constructs like if(foo) return ... - rewrite like if (!foo) { ... } function tighten(statements, block_type) { statements = statements.reduce(function(a, stat){ if (stat[0] == "block") { if (stat[1]) { a.push.apply(a, stat[1]); @@ -611,10 +714,68 @@ a.push(stat); } return a; }, []); + if (options.extra) { + // Detightening things. We do this because then we can assume that the + // statements are structured in a specific way. + statements = (function(a, prev) { + statements.forEach(function(cur) { + switch (cur[0]) { + case "for": + if (cur[1] != null) { + a.push.apply(a, make_seq_to_statements(cur[1])); + cur[1] = null; + } + a.push(cur); + break; + case "stat": + var stats = make_seq_to_statements(cur[1]); + stats.forEach(function(s) { + if (s[1][0] == "unary-postfix") + s[1][0] = "unary-prefix"; + }); + a.push.apply(a, stats); + break; + default: + a.push(cur); + } + }); + return a; + })([]); + + statements = (function(a, prev) { + statements.forEach(function(cur) { + if (!(prev && prev[0] == "stat")) { + a.push(cur); + prev = cur; + return; + } + + var p = prev[1]; + var c = find_first_execute(cur); + if (c && find_assign_recursive(p, c)) { + var old_cur = clone(cur); + c.splice(0, c.length); + c.push.apply(c, p); + var tmp_cur = best_of(cur, [ "toplevel", [ prev, old_cur ] ]); + if (tmp_cur == cur) { + a[a.length -1] = cur; + } else { + cur = old_cur; + a.push(cur); + } + } else { + a.push(cur); + } + prev = cur; + }); + return a; + })([]); + } + statements = (function(a, prev){ statements.forEach(function(cur){ if (prev && ((cur[0] == "var" && prev[0] == "var") || (cur[0] == "const" && prev[0] == "const"))) { prev[1] = prev[1].concat(cur[1]); @@ -644,85 +805,82 @@ return a; })([]); if (options.make_seqs) statements = (function(a, prev) { statements.forEach(function(cur){ - if (!prev) { - a.push(cur); - if (cur[0] == "stat") prev = cur; - } else if (cur[0] == "stat" && prev[0] == "stat") { + if (prev && prev[0] == "stat" && cur[0] == "stat") { prev[1] = [ "seq", prev[1], cur[1] ]; } else { a.push(cur); - prev = null; + prev = cur; } }); return a; })([]); + if (options.extra) { + statements = (function(a, prev){ + statements.forEach(function(cur){ + var replaced = false; + if (prev && cur[0] == "for" && cur[1] == null && (prev[0] == "var" || prev[0] == "const" || prev[0] == "stat")) { + cur[1] = prev; + a[a.length - 1] = cur; + } else { + a.push(cur); + } + prev = cur; + }); + return a; + })([]); + } + if (block_type == "lambda") statements = (function(i, a, stat){ while (i < statements.length) { stat = statements[i++]; - if (stat[0] == "if" && stat[2][0] == "return" && stat[2][1] == null && !stat[3]) { - a.push(make_if(negate(stat[1]), [ "block", statements.slice(i) ])); - break; - } else { - a.push(stat); + if (stat[0] == "if" && !stat[3]) { + if (stat[2][0] == "return" && stat[2][1] == null) { + a.push(make_if(negate(stat[1]), [ "block", statements.slice(i) ])); + break; + } + var last = last_stat(stat[2]); + if (last[0] == "return" && last[1] == null) { + a.push(make_if(stat[1], [ "block", stat[2][1].slice(0, -1) ], [ "block", statements.slice(i) ])); + break; + } } + a.push(stat); } return a; })(0, []); return statements; }; - function best_of(ast1, ast2) { - return gen_code(ast1).length > gen_code(ast2[0] == "stat" ? ast2[1] : ast2).length ? ast2 : ast1; - }; - - function aborts(t) { - if (t[0] == "block" && t[1] && t[1].length > 0) - t = t[1][t[1].length - 1]; // interested in last statement - if (t[0] == "return" || t[0] == "break" || t[0] == "continue" || t[0] == "throw") - return true; - }; - - function negate(c) { - if (c[0] == "unary-prefix" && c[1] == "!") return c[2]; - else return [ "unary-prefix", "!", c ]; - }; - - function make_conditional(c, t, e) { - if (c[0] == "unary-prefix" && c[1] == "!") { - return e ? [ "conditional", c[2], e, t ] : [ "binary", "||", c[2], t ]; - } else { - return e ? [ "conditional", c, t, e ] : [ "binary", "&&", c, t ]; - } - }; - - function empty(b) { - return !b || (b[0] == "block" && (!b[1] || b[1].length == 0)); - }; - function make_if(c, t, e) { c = walk(c); t = walk(t); e = walk(e); - var negated = c[0] == "unary-prefix" && c[1] == "!"; + if (empty(t)) { - if (negated) c = c[2]; - else c = [ "unary-prefix", "!", c ]; + c = negate(c); t = e; e = null; - } - if (empty(e)) { + } else if (empty(e)) { e = null; } else { - if (negated) { - c = c[2]; - var tmp = t; t = e; e = tmp; - } + // if we have both else and then, maybe it makes sense to switch them? + (function(){ + var a = gen_code(c); + var n = negate(c); + var b = gen_code(n); + if (b.length < a.length) { + var tmp = t; + t = e; + e = tmp; + c = n; + } + })(); } if (empty(e) && empty(t)) return [ "stat", c ]; var ret = [ "if", c, t, e ]; if (t[0] == "stat") { @@ -746,10 +904,19 @@ else { ret.push(e); } ret = walk([ "block", ret ]); } + else if (t && aborts(e)) { + ret = [ [ "if", negate(c), e ] ]; + if (t[0] == "block") { + if (t[1]) ret = ret.concat(t[1]); + } else { + ret.push(t); + } + ret = walk([ "block", ret ]); + } return ret; }; return w.with_walkers({ "sub": function(expr, subscript) { @@ -783,24 +950,28 @@ }, "binary": function(op, left, right) { left = walk(left); right = walk(right); var best = [ "binary", op, left, right ]; - if (is_constant(left) && is_constant(right)) { - var val = null; - switch (op) { - case "+": val = left[1] + right[1]; break; - case "*": val = left[1] * right[1]; break; - case "/": val = left[1] / right[1]; break; - case "-": val = left[1] - right[1]; break; - case "<<": val = left[1] << right[1]; break; - case ">>": val = left[1] >> right[1]; break; - case ">>>": val = left[1] >>> right[1]; break; + if (is_constant(right)) { + if (is_constant(left)) { + var val = null; + switch (op) { + case "+": val = left[1] + right[1]; break; + case "*": val = left[1] * right[1]; break; + case "/": val = left[1] / right[1]; break; + case "-": val = left[1] - right[1]; break; + case "<<": val = left[1] << right[1]; break; + case ">>": val = left[1] >> right[1]; break; + case ">>>": val = left[1] >>> right[1]; break; + } + if (val != null) { + best = best_of(best, [ typeof val == "string" ? "string" : "num", val ]); + } + } else if (left[0] == "binary" && left[1] == "+" && left[3][0] == "string") { + best = best_of(best, [ "binary", "+", left[2], [ "string", left[3][1] + right[1] ] ]); } - if (val != null) { - best = best_of(best, [ typeof val == "string" ? "string" : "num", val ]); - } } return best; }, "conditional": function(c, t, e) { return make_conditional(walk(c), walk(t), walk(e)); @@ -810,10 +981,20 @@ "try", tighten(t.map(walk)), c != null ? [ c[0], tighten(c[1].map(walk)) ] : null, f != null ? tighten(f.map(walk)) : null ]; + }, + "unary-prefix": function(op, cond) { + if (op == "!") + return best_of(this, negate(cond)); + }, + "call": function(expr, args) { + if (expr[0] == "dot" && expr[2] == "toString" && args.length == 0) { + // foo.toString() ==> foo+"" + return [ "binary", "+", expr[1], [ "string", "" ]]; + } } }, function() { return walk(ast); }); @@ -1003,11 +1184,11 @@ parenthesize(el, "seq") ]); }, "assign": function(op, lvalue, rvalue) { if (op && op !== true) op += "="; else op = "="; - return add_spaces([ make(lvalue), op, make(rvalue) ]); + return add_spaces([ make(lvalue), op, parenthesize(rvalue, "seq") ]); }, "dot": function(expr) { var out = make(expr), i = 1; if (needs_parens(expr)) out = "(" + out + ")"; @@ -1191,9 +1372,13 @@ // + '"'; return JSON.stringify(str); // STILL cheating. }; function make_name(name) { + switch (name) { + case "true": return beautify ? "true" : "!0"; + case "false": return beautify ? "false" : "!1"; + } return name.toString(); }; function make_block_statements(statements) { for (var a = [], last = statements.length - 1, i = 0; i <= last; ++i) {