Home | History | Annotate | Download | only in UglifyJS
      1 /***********************************************************************
      2 
      3   A JavaScript tokenizer / parser / beautifier / compressor.
      4 
      5   This version is suitable for Node.js.  With minimal changes (the
      6   exports stuff) it should work on any JS platform.
      7 
      8   This file implements some AST processors.  They work on data built
      9   by parse-js.
     10 
     11   Exported functions:
     12 
     13     - ast_mangle(ast, include_toplevel) -- mangles the
     14       variable/function names in the AST.  Returns an AST.  Pass true
     15       as second argument to mangle toplevel names too.
     16 
     17     - ast_squeeze(ast) -- employs various optimizations to make the
     18       final generated code even smaller.  Returns an AST.
     19 
     20     - gen_code(ast, beautify) -- generates JS code from the AST.  Pass
     21       true (or an object, see the code for some options) as second
     22       argument to get "pretty" (indented) code.
     23 
     24   -------------------------------- (C) ---------------------------------
     25 
     26                            Author: Mihai Bazon
     27                          <mihai.bazon (at) gmail.com>
     28                        http://mihai.bazon.net/blog
     29 
     30   Distributed under the BSD license:
     31 
     32     Copyright 2010 (c) Mihai Bazon <mihai.bazon (at) gmail.com>
     33 
     34     Redistribution and use in source and binary forms, with or without
     35     modification, are permitted provided that the following conditions
     36     are met:
     37 
     38         * Redistributions of source code must retain the above
     39           copyright notice, this list of conditions and the following
     40           disclaimer.
     41 
     42         * Redistributions in binary form must reproduce the above
     43           copyright notice, this list of conditions and the following
     44           disclaimer in the documentation and/or other materials
     45           provided with the distribution.
     46 
     47     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AS IS AND ANY
     48     EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     49     IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     50     PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
     51     LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
     52     OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     53     PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     54     PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     55     THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
     56     TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
     57     THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     58     SUCH DAMAGE.
     59 
     60  ***********************************************************************/
     61 
     62 var jsp = require("./parse-js"),
     63     slice = jsp.slice,
     64     member = jsp.member,
     65     PRECEDENCE = jsp.PRECEDENCE,
     66     OPERATORS = jsp.OPERATORS;
     67 
     68 /* -----[ helper for AST traversal ]----- */
     69 
     70 function ast_walker(ast) {
     71         function _vardefs(defs) {
     72                 return [ this[0], MAP(defs, function(def){
     73                         var a = [ def[0] ];
     74                         if (def.length > 1)
     75                                 a[1] = walk(def[1]);
     76                         return a;
     77                 }) ];
     78         };
     79         var walkers = {
     80                 "string": function(str) {
     81                         return [ this[0], str ];
     82                 },
     83                 "num": function(num) {
     84                         return [ this[0], num ];
     85                 },
     86                 "name": function(name) {
     87                         return [ this[0], name ];
     88                 },
     89                 "toplevel": function(statements) {
     90                         return [ this[0], MAP(statements, walk) ];
     91                 },
     92                 "block": function(statements) {
     93                         var out = [ this[0] ];
     94                         if (statements != null)
     95                                 out.push(MAP(statements, walk));
     96                         return out;
     97                 },
     98                 "var": _vardefs,
     99                 "const": _vardefs,
    100                 "try": function(t, c, f) {
    101                         return [
    102                                 this[0],
    103                                 MAP(t, walk),
    104                                 c != null ? [ c[0], MAP(c[1], walk) ] : null,
    105                                 f != null ? MAP(f, walk) : null
    106                         ];
    107                 },
    108                 "throw": function(expr) {
    109                         return [ this[0], walk(expr) ];
    110                 },
    111                 "new": function(ctor, args) {
    112                         return [ this[0], walk(ctor), MAP(args, walk) ];
    113                 },
    114                 "switch": function(expr, body) {
    115                         return [ this[0], walk(expr), MAP(body, function(branch){
    116                                 return [ branch[0] ? walk(branch[0]) : null,
    117                                          MAP(branch[1], walk) ];
    118                         }) ];
    119                 },
    120                 "break": function(label) {
    121                         return [ this[0], label ];
    122                 },
    123                 "continue": function(label) {
    124                         return [ this[0], label ];
    125                 },
    126                 "conditional": function(cond, t, e) {
    127                         return [ this[0], walk(cond), walk(t), walk(e) ];
    128                 },
    129                 "assign": function(op, lvalue, rvalue) {
    130                         return [ this[0], op, walk(lvalue), walk(rvalue) ];
    131                 },
    132                 "dot": function(expr) {
    133                         return [ this[0], walk(expr) ].concat(slice(arguments, 1));
    134                 },
    135                 "call": function(expr, args) {
    136                         return [ this[0], walk(expr), MAP(args, walk) ];
    137                 },
    138                 "function": function(name, args, body) {
    139                         return [ this[0], name, args.slice(), MAP(body, walk) ];
    140                 },
    141                 "defun": function(name, args, body) {
    142                         return [ this[0], name, args.slice(), MAP(body, walk) ];
    143                 },
    144                 "if": function(conditional, t, e) {
    145                         return [ this[0], walk(conditional), walk(t), walk(e) ];
    146                 },
    147                 "for": function(init, cond, step, block) {
    148                         return [ this[0], walk(init), walk(cond), walk(step), walk(block) ];
    149                 },
    150                 "for-in": function(has_var, key, hash, block) {
    151                         return [ this[0], has_var, key, walk(hash), walk(block) ];
    152                 },
    153                 "while": function(cond, block) {
    154                         return [ this[0], walk(cond), walk(block) ];
    155                 },
    156                 "do": function(cond, block) {
    157                         return [ this[0], walk(cond), walk(block) ];
    158                 },
    159                 "return": function(expr) {
    160                         return [ this[0], walk(expr) ];
    161                 },
    162                 "binary": function(op, left, right) {
    163                         return [ this[0], op, walk(left), walk(right) ];
    164                 },
    165                 "unary-prefix": function(op, expr) {
    166                         return [ this[0], op, walk(expr) ];
    167                 },
    168                 "unary-postfix": function(op, expr) {
    169                         return [ this[0], op, walk(expr) ];
    170                 },
    171                 "sub": function(expr, subscript) {
    172                         return [ this[0], walk(expr), walk(subscript) ];
    173                 },
    174                 "object": function(props) {
    175                         return [ this[0], MAP(props, function(p){
    176                                 return p.length == 2
    177                                         ? [ p[0], walk(p[1]) ]
    178                                         : [ p[0], walk(p[1]), p[2] ]; // get/set-ter
    179                         }) ];
    180                 },
    181                 "regexp": function(rx, mods) {
    182                         return [ this[0], rx, mods ];
    183                 },
    184                 "array": function(elements) {
    185                         return [ this[0], MAP(elements, walk) ];
    186                 },
    187                 "stat": function(stat) {
    188                         return [ this[0], walk(stat) ];
    189                 },
    190                 "seq": function() {
    191                         return [ this[0] ].concat(MAP(slice(arguments), walk));
    192                 },
    193                 "label": function(name, block) {
    194                         return [ this[0], name, walk(block) ];
    195                 },
    196                 "with": function(expr, block) {
    197                         return [ this[0], walk(expr), walk(block) ];
    198                 },
    199                 "atom": function(name) {
    200                         return [ this[0], name ];
    201                 }
    202         };
    203 
    204         var user = {};
    205         var stack = [];
    206         function walk(ast) {
    207                 if (ast == null)
    208                         return null;
    209                 try {
    210                         stack.push(ast);
    211                         var type = ast[0];
    212                         var gen = user[type];
    213                         if (gen) {
    214                                 var ret = gen.apply(ast, ast.slice(1));
    215                                 if (ret != null)
    216                                         return ret;
    217                         }
    218                         gen = walkers[type];
    219                         return gen.apply(ast, ast.slice(1));
    220                 } finally {
    221                         stack.pop();
    222                 }
    223         };
    224 
    225         function with_walkers(walkers, cont){
    226                 var save = {}, i;
    227                 for (i in walkers) if (HOP(walkers, i)) {
    228                         save[i] = user[i];
    229                         user[i] = walkers[i];
    230                 }
    231                 var ret = cont();
    232                 for (i in save) if (HOP(save, i)) {
    233                         if (!save[i]) delete user[i];
    234                         else user[i] = save[i];
    235                 }
    236                 return ret;
    237         };
    238 
    239         return {
    240                 walk: walk,
    241                 with_walkers: with_walkers,
    242                 parent: function() {
    243                         return stack[stack.length - 2]; // last one is current node
    244                 },
    245                 stack: function() {
    246                         return stack;
    247                 }
    248         };
    249 };
    250 
    251 /* -----[ Scope and mangling ]----- */
    252 
    253 function Scope(parent) {
    254         this.names = {};        // names defined in this scope
    255         this.mangled = {};      // mangled names (orig.name => mangled)
    256         this.rev_mangled = {};  // reverse lookup (mangled => orig.name)
    257         this.cname = -1;        // current mangled name
    258         this.refs = {};         // names referenced from this scope
    259         this.uses_with = false; // will become TRUE if eval() is detected in this or any subscopes
    260         this.uses_eval = false; // will become TRUE if with() is detected in this or any subscopes
    261         this.parent = parent;   // parent scope
    262         this.children = [];     // sub-scopes
    263         if (parent) {
    264                 this.level = parent.level + 1;
    265                 parent.children.push(this);
    266         } else {
    267                 this.level = 0;
    268         }
    269 };
    270 
    271 var base54 = (function(){
    272         var DIGITS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_";
    273         return function(num) {
    274                 var ret = "";
    275                 do {
    276                         ret = DIGITS.charAt(num % 54) + ret;
    277                         num = Math.floor(num / 54);
    278                 } while (num > 0);
    279                 return ret;
    280         };
    281 })();
    282 
    283 Scope.prototype = {
    284         has: function(name) {
    285                 for (var s = this; s; s = s.parent)
    286                         if (HOP(s.names, name))
    287                                 return s;
    288         },
    289         has_mangled: function(mname) {
    290                 for (var s = this; s; s = s.parent)
    291                         if (HOP(s.rev_mangled, mname))
    292                                 return s;
    293         },
    294         toJSON: function() {
    295                 return {
    296                         names: this.names,
    297                         uses_eval: this.uses_eval,
    298                         uses_with: this.uses_with
    299                 };
    300         },
    301 
    302         next_mangled: function() {
    303                 // we must be careful that the new mangled name:
    304                 //
    305                 // 1. doesn't shadow a mangled name from a parent
    306                 //    scope, unless we don't reference the original
    307                 //    name from this scope OR from any sub-scopes!
    308                 //    This will get slow.
    309                 //
    310                 // 2. doesn't shadow an original name from a parent
    311                 //    scope, in the event that the name is not mangled
    312                 //    in the parent scope and we reference that name
    313                 //    here OR IN ANY SUBSCOPES!
    314                 //
    315                 // 3. doesn't shadow a name that is referenced but not
    316                 //    defined (possibly global defined elsewhere).
    317                 for (;;) {
    318                         var m = base54(++this.cname), prior;
    319 
    320                         // case 1.
    321                         prior = this.has_mangled(m);
    322                         if (prior && this.refs[prior.rev_mangled[m]] === prior)
    323                                 continue;
    324 
    325                         // case 2.
    326                         prior = this.has(m);
    327                         if (prior && prior !== this && this.refs[m] === prior && !prior.has_mangled(m))
    328                                 continue;
    329 
    330                         // case 3.
    331                         if (HOP(this.refs, m) && this.refs[m] == null)
    332                                 continue;
    333 
    334                         // I got "do" once. :-/
    335                         if (!is_identifier(m))
    336                                 continue;
    337 
    338                         return m;
    339                 }
    340         },
    341         get_mangled: function(name, newMangle) {
    342                 if (this.uses_eval || this.uses_with) return name; // no mangle if eval or with is in use
    343                 var s = this.has(name);
    344                 if (!s) return name; // not in visible scope, no mangle
    345                 if (HOP(s.mangled, name)) return s.mangled[name]; // already mangled in this scope
    346                 if (!newMangle) return name;                      // not found and no mangling requested
    347 
    348                 var m = s.next_mangled();
    349                 s.rev_mangled[m] = name;
    350                 return s.mangled[name] = m;
    351         },
    352         define: function(name) {
    353                 if (name != null)
    354                         return this.names[name] = name;
    355         }
    356 };
    357 
    358 function ast_add_scope(ast) {
    359 
    360         var current_scope = null;
    361         var w = ast_walker(), walk = w.walk;
    362         var having_eval = [];
    363 
    364         function with_new_scope(cont) {
    365                 current_scope = new Scope(current_scope);
    366                 var ret = current_scope.body = cont();
    367                 ret.scope = current_scope;
    368                 current_scope = current_scope.parent;
    369                 return ret;
    370         };
    371 
    372         function define(name) {
    373                 return current_scope.define(name);
    374         };
    375 
    376         function reference(name) {
    377                 current_scope.refs[name] = true;
    378         };
    379 
    380         function _lambda(name, args, body) {
    381                 return [ this[0], define(name), args, with_new_scope(function(){
    382                         MAP(args, define);
    383                         return MAP(body, walk);
    384                 })];
    385         };
    386 
    387         return with_new_scope(function(){
    388                 // process AST
    389                 var ret = w.with_walkers({
    390                         "function": _lambda,
    391                         "defun": _lambda,
    392                         "with": function(expr, block) {
    393                                 for (var s = current_scope; s; s = s.parent)
    394                                         s.uses_with = true;
    395                         },
    396                         "var": function(defs) {
    397                                 MAP(defs, function(d){ define(d[0]) });
    398                         },
    399                         "const": function(defs) {
    400                                 MAP(defs, function(d){ define(d[0]) });
    401                         },
    402                         "try": function(t, c, f) {
    403                                 if (c != null) return [
    404                                         this[0],
    405                                         MAP(t, walk),
    406                                         [ define(c[0]), MAP(c[1], walk) ],
    407                                         f != null ? MAP(f, walk) : null
    408                                 ];
    409                         },
    410                         "name": function(name) {
    411                                 if (name == "eval")
    412                                         having_eval.push(current_scope);
    413                                 reference(name);
    414                         },
    415                         "for-in": function(has_var, name) {
    416                                 if (has_var) define(name);
    417                                 else reference(name);
    418                         }
    419                 }, function(){
    420                         return walk(ast);
    421                 });
    422 
    423                 // the reason why we need an additional pass here is
    424                 // that names can be used prior to their definition.
    425 
    426                 // scopes where eval was detected and their parents
    427                 // are marked with uses_eval, unless they define the
    428                 // "eval" name.
    429                 MAP(having_eval, function(scope){
    430                         if (!scope.has("eval")) while (scope) {
    431                                 scope.uses_eval = true;
    432                                 scope = scope.parent;
    433                         }
    434                 });
    435 
    436                 // for referenced names it might be useful to know
    437                 // their origin scope.  current_scope here is the
    438                 // toplevel one.
    439                 function fixrefs(scope, i) {
    440                         // do children first; order shouldn't matter
    441                         for (i = scope.children.length; --i >= 0;)
    442                                 fixrefs(scope.children[i]);
    443                         for (i in scope.refs) if (HOP(scope.refs, i)) {
    444                                 // find origin scope and propagate the reference to origin
    445                                 for (var origin = scope.has(i), s = scope; s; s = s.parent) {
    446                                         s.refs[i] = origin;
    447                                         if (s === origin) break;
    448                                 }
    449                         }
    450                 };
    451                 fixrefs(current_scope);
    452 
    453                 return ret;
    454         });
    455 
    456 };
    457 
    458 /* -----[ mangle names ]----- */
    459 
    460 function ast_mangle(ast, do_toplevel) {
    461         var w = ast_walker(), walk = w.walk, scope;
    462 
    463         function get_mangled(name, newMangle) {
    464                 if (!do_toplevel && !scope.parent) return name; // don't mangle toplevel
    465                 return scope.get_mangled(name, newMangle);
    466         };
    467 
    468         function _lambda(name, args, body) {
    469                 if (name) name = get_mangled(name);
    470                 body = with_scope(body.scope, function(){
    471                         args = MAP(args, function(name){ return get_mangled(name) });
    472                         return MAP(body, walk);
    473                 });
    474                 return [ this[0], name, args, body ];
    475         };
    476 
    477         function with_scope(s, cont) {
    478                 var _scope = scope;
    479                 scope = s;
    480                 for (var i in s.names) if (HOP(s.names, i)) {
    481                         get_mangled(i, true);
    482                 }
    483                 var ret = cont();
    484                 ret.scope = s;
    485                 scope = _scope;
    486                 return ret;
    487         };
    488 
    489         function _vardefs(defs) {
    490                 return [ this[0], MAP(defs, function(d){
    491                         return [ get_mangled(d[0]), walk(d[1]) ];
    492                 }) ];
    493         };
    494 
    495         return w.with_walkers({
    496                 "function": _lambda,
    497                 "defun": function() {
    498                         // move function declarations to the top when
    499                         // they are not in some block.
    500                         var ast = _lambda.apply(this, arguments);
    501                         switch (w.parent()[0]) {
    502                             case "toplevel":
    503                             case "function":
    504                             case "defun":
    505                                 return MAP.at_top(ast);
    506                         }
    507                         return ast;
    508                 },
    509                 "var": _vardefs,
    510                 "const": _vardefs,
    511                 "name": function(name) {
    512                         return [ this[0], get_mangled(name) ];
    513                 },
    514                 "try": function(t, c, f) {
    515                         return [ this[0],
    516                                  MAP(t, walk),
    517                                  c != null ? [ get_mangled(c[0]), MAP(c[1], walk) ] : null,
    518                                  f != null ? MAP(f, walk) : null ];
    519                 },
    520                 "toplevel": function(body) {
    521                         var self = this;
    522                         return with_scope(self.scope, function(){
    523                                 return [ self[0], MAP(body, walk) ];
    524                         });
    525                 },
    526                 "for-in": function(has_var, name, obj, stat) {
    527                         return [ this[0], has_var, get_mangled(name), walk(obj), walk(stat) ];
    528                 }
    529         }, function() {
    530                 return walk(ast_add_scope(ast));
    531         });
    532 };
    533 
    534 /* -----[
    535    - compress foo["bar"] into foo.bar,
    536    - remove block brackets {} where possible
    537    - join consecutive var declarations
    538    - various optimizations for IFs:
    539      - if (cond) foo(); else bar();  ==>  cond?foo():bar();
    540      - if (cond) foo();  ==>  cond&&foo();
    541      - if (foo) return bar(); else return baz();  ==> return foo?bar():baz(); // also for throw
    542      - if (foo) return bar(); else something();  ==> {if(foo)return bar();something()}
    543    ]----- */
    544 
    545 var warn = function(){};
    546 
    547 function best_of(ast1, ast2) {
    548         return gen_code(ast1).length > gen_code(ast2[0] == "stat" ? ast2[1] : ast2).length ? ast2 : ast1;
    549 };
    550 
    551 function last_stat(b) {
    552         if (b[0] == "block" && b[1] && b[1].length > 0)
    553                 return b[1][b[1].length - 1];
    554         return b;
    555 }
    556 
    557 function aborts(t) {
    558         if (t) {
    559                 t = last_stat(t);
    560                 if (t[0] == "return" || t[0] == "break" || t[0] == "continue" || t[0] == "throw")
    561                         return true;
    562         }
    563 };
    564 
    565 function boolean_expr(expr) {
    566         return ( (expr[0] == "unary-prefix"
    567                   && member(expr[1], [ "!", "delete" ])) ||
    568 
    569                  (expr[0] == "binary"
    570                   && member(expr[1], [ "in", "instanceof", "==", "!=", "===", "!==", "<", "<=", ">=", ">" ])) ||
    571 
    572                  (expr[0] == "binary"
    573                   && member(expr[1], [ "&&", "||" ])
    574                   && boolean_expr(expr[2])
    575                   && boolean_expr(expr[3])) ||
    576 
    577                  (expr[0] == "conditional"
    578                   && boolean_expr(expr[2])
    579                   && boolean_expr(expr[3])) ||
    580 
    581                  (expr[0] == "assign"
    582                   && expr[1] === true
    583                   && boolean_expr(expr[3])) ||
    584 
    585                  (expr[0] == "seq"
    586                   && boolean_expr(expr[expr.length - 1]))
    587                );
    588 };
    589 
    590 function make_conditional(c, t, e) {
    591         if (c[0] == "unary-prefix" && c[1] == "!") {
    592                 return e ? [ "conditional", c[2], e, t ] : [ "binary", "||", c[2], t ];
    593         } else {
    594                 return e ? [ "conditional", c, t, e ] : [ "binary", "&&", c, t ];
    595         }
    596 };
    597 
    598 function empty(b) {
    599         return !b || (b[0] == "block" && (!b[1] || b[1].length == 0));
    600 };
    601 
    602 function ast_squeeze(ast, options) {
    603         options = defaults(options, {
    604                 make_seqs   : true,
    605                 dead_code   : true,
    606                 keep_comps  : true,
    607                 no_warnings : false
    608         });
    609 
    610         var w = ast_walker(), walk = w.walk, scope;
    611 
    612         function negate(c) {
    613                     var not_c = [ "unary-prefix", "!", c ];
    614                 switch (c[0]) {
    615                     case "unary-prefix":
    616                         return c[1] == "!" && boolean_expr(c[2]) ? c[2] : not_c;
    617                     case "seq":
    618                         c = slice(c);
    619                         c[c.length - 1] = negate(c[c.length - 1]);
    620                         return c;
    621                     case "conditional":
    622                         return best_of(not_c, [ "conditional", c[1], negate(c[2]), negate(c[3]) ]);
    623                     case "binary":
    624                         var op = c[1], left = c[2], right = c[3];
    625                         if (!options.keep_comps) switch (op) {
    626                             case "<="  : return [ "binary", ">", left, right ];
    627                             case "<"   : return [ "binary", ">=", left, right ];
    628                             case ">="  : return [ "binary", "<", left, right ];
    629                             case ">"   : return [ "binary", "<=", left, right ];
    630                         }
    631                         switch (op) {
    632                             case "=="  : return [ "binary", "!=", left, right ];
    633                             case "!="  : return [ "binary", "==", left, right ];
    634                             case "===" : return [ "binary", "!==", left, right ];
    635                             case "!==" : return [ "binary", "===", left, right ];
    636                             case "&&"  : return best_of(not_c, [ "binary", "||", negate(left), negate(right) ]);
    637                             case "||"  : return best_of(not_c, [ "binary", "&&", negate(left), negate(right) ]);
    638                         }
    639                         break;
    640                 }
    641                 return not_c;
    642         };
    643 
    644         function with_scope(s, cont) {
    645                 var _scope = scope;
    646                 scope = s;
    647                 var ret = cont();
    648                 ret.scope = s;
    649                 scope = _scope;
    650                 return ret;
    651         };
    652 
    653         function is_constant(node) {
    654                 return node[0] == "string" || node[0] == "num";
    655         };
    656 
    657         function rmblock(block) {
    658                 if (block != null && block[0] == "block" && block[1] && block[1].length == 1)
    659                         block = block[1][0];
    660                 return block;
    661         };
    662 
    663         function _lambda(name, args, body) {
    664                 return [ this[0], name, args, with_scope(body.scope, function(){
    665                         return tighten(MAP(body, walk), "lambda");
    666                 }) ];
    667         };
    668 
    669         // we get here for blocks that have been already transformed.
    670         // this function does a few things:
    671         // 1. discard useless blocks
    672         // 2. join consecutive var declarations
    673         // 3. remove obviously dead code
    674         // 4. transform consecutive statements using the comma operator
    675         // 5. if block_type == "lambda" and it detects constructs like if(foo) return ... - rewrite like if (!foo) { ... }
    676         function tighten(statements, block_type) {
    677                 statements = statements.reduce(function(a, stat){
    678                         if (stat[0] == "block") {
    679                                 if (stat[1]) {
    680                                         a.push.apply(a, stat[1]);
    681                                 }
    682                         } else {
    683                                 a.push(stat);
    684                         }
    685                         return a;
    686                 }, []);
    687 
    688                 statements = (function(a, prev){
    689                         statements.forEach(function(cur){
    690                                 if (prev && ((cur[0] == "var" && prev[0] == "var") ||
    691                                              (cur[0] == "const" && prev[0] == "const"))) {
    692                                         prev[1] = prev[1].concat(cur[1]);
    693                                 } else {
    694                                         a.push(cur);
    695                                         prev = cur;
    696                                 }
    697                         });
    698                         return a;
    699                 })([]);
    700 
    701                 if (options.dead_code) statements = (function(a, has_quit){
    702                         statements.forEach(function(st){
    703                                 if (has_quit) {
    704                                         if (member(st[0], [ "function", "defun" , "var", "const" ])) {
    705                                                 a.push(st);
    706                                         }
    707                                         else if (!options.no_warnings)
    708                                                 warn("Removing unreachable code: " + gen_code(st, true));
    709                                 }
    710                                 else {
    711                                         a.push(st);
    712                                         if (member(st[0], [ "return", "throw", "break", "continue" ]))
    713                                                 has_quit = true;
    714                                 }
    715                         });
    716                         return a;
    717                 })([]);
    718 
    719                 if (options.make_seqs) statements = (function(a, prev) {
    720                         statements.forEach(function(cur){
    721                                 if (prev && prev[0] == "stat" && cur[0] == "stat") {
    722                                         prev[1] = [ "seq", prev[1], cur[1] ];
    723                                 } else {
    724                                         a.push(cur);
    725                                         prev = cur;
    726                                 }
    727                         });
    728                         return a;
    729                 })([]);
    730 
    731                 if (block_type == "lambda") statements = (function(i, a, stat){
    732                         while (i < statements.length) {
    733                                 stat = statements[i++];
    734                                 if (stat[0] == "if" && !stat[3]) {
    735                                         if (stat[2][0] == "return" && stat[2][1] == null) {
    736                                                 a.push(make_if(negate(stat[1]), [ "block", statements.slice(i) ]));
    737                                                 break;
    738                                         }
    739                                         var last = last_stat(stat[2]);
    740                                         if (last[0] == "return" && last[1] == null) {
    741                                                 a.push(make_if(stat[1], [ "block", stat[2][1].slice(0, -1) ], [ "block", statements.slice(i) ]));
    742                                                 break;
    743                                         }
    744                                 }
    745                                 a.push(stat);
    746                         }
    747                         return a;
    748                 })(0, []);
    749 
    750                 return statements;
    751         };
    752 
    753         function make_if(c, t, e) {
    754                 c = walk(c);
    755                 t = walk(t);
    756                 e = walk(e);
    757 
    758                 if (empty(t)) {
    759                         c = negate(c);
    760                         t = e;
    761                         e = null;
    762                 } else if (empty(e)) {
    763                         e = null;
    764                 } else {
    765                         // if we have both else and then, maybe it makes sense to switch them?
    766                         (function(){
    767                                 var a = gen_code(c);
    768                                 var n = negate(c);
    769                                 var b = gen_code(n);
    770                                 if (b.length < a.length) {
    771                                         var tmp = t;
    772                                         t = e;
    773                                         e = tmp;
    774                                         c = n;
    775                                 }
    776                         })();
    777                 }
    778                 if (empty(e) && empty(t))
    779                         return [ "stat", c ];
    780                 var ret = [ "if", c, t, e ];
    781                 if (t[0] == "if" && empty(t[3]) && empty(e)) {
    782                         ret = best_of(ret, walk([ "if", [ "binary", "&&", c, t[1] ], t[2] ]));
    783                 }
    784                 else if (t[0] == "stat") {
    785                         if (e) {
    786                                 if (e[0] == "stat") {
    787                                         ret = best_of(ret, [ "stat", make_conditional(c, t[1], e[1]) ]);
    788                                 }
    789                         }
    790                         else {
    791                                 ret = best_of(ret, [ "stat", make_conditional(c, t[1]) ]);
    792                         }
    793                 }
    794                 else if (e && t[0] == e[0] && (t[0] == "return" || t[0] == "throw")) {
    795                         ret = best_of(ret, [ t[0], make_conditional(c, t[1], e[1] ) ]);
    796                 }
    797                 else if (e && aborts(t)) {
    798                         ret = [ [ "if", c, t ] ];
    799                         if (e[0] == "block") {
    800                                 if (e[1]) ret = ret.concat(e[1]);
    801                         }
    802                         else {
    803                                 ret.push(e);
    804                         }
    805                         ret = walk([ "block", ret ]);
    806                 }
    807                 else if (t && aborts(e)) {
    808                         ret = [ [ "if", negate(c), e ] ];
    809                         if (t[0] == "block") {
    810                                 if (t[1]) ret = ret.concat(t[1]);
    811                         } else {
    812                                 ret.push(t);
    813                         }
    814                         ret = walk([ "block", ret ]);
    815                 }
    816                 return ret;
    817         };
    818 
    819         return w.with_walkers({
    820                 "sub": function(expr, subscript) {
    821                         if (subscript[0] == "string") {
    822                                 var name = subscript[1];
    823                                 if (is_identifier(name)) {
    824                                         return [ "dot", walk(expr), name ];
    825                                 }
    826                         }
    827                 },
    828                 "if": make_if,
    829                 "toplevel": function(body) {
    830                         return [ "toplevel", with_scope(this.scope, function(){
    831                                 return tighten(MAP(body, walk));
    832                         }) ];
    833                 },
    834                 "switch": function(expr, body) {
    835                         var last = body.length - 1;
    836                         return [ "switch", walk(expr), MAP(body, function(branch, i){
    837                                 var block = tighten(MAP(branch[1], walk));
    838                                 if (i == last && block.length > 0) {
    839                                         var node = block[block.length - 1];
    840                                         if (node[0] == "break" && !node[1])
    841                                                 block.pop();
    842                                 }
    843                                 return [ branch[0] ? walk(branch[0]) : null, block ];
    844                         }) ];
    845                 },
    846                 "function": _lambda,
    847                 "defun": _lambda,
    848                 "block": function(body) {
    849                         if (body) return rmblock([ "block", tighten(MAP(body, walk)) ]);
    850                 },
    851                 "binary": function(op, left, right) {
    852                         left = walk(left);
    853                         right = walk(right);
    854                         var best = [ "binary", op, left, right ];
    855                         if (is_constant(right) && is_constant(left)) {
    856                                 var val = {};
    857                                 var orig = val;
    858                                 switch (op) {
    859                                     case "+"   : val = left[1] +   right[1]; break;
    860                                     case "*"   : val = left[1] *   right[1]; break;
    861                                     case "/"   : val = left[1] /   right[1]; break;
    862                                     case "-"   : val = left[1] -   right[1]; break;
    863                                     case "<<"  : val = left[1] <<  right[1]; break;
    864                                     case ">>"  : val = left[1] >>  right[1]; break;
    865                                     case ">>>" : val = left[1] >>> right[1]; break;
    866                                     case "=="  : val = left[1] ==  right[1]; break;
    867                                     case "===" : val = left[1] === right[1]; break;
    868                                     case "!="  : val = left[1] !=  right[1]; break;
    869                                     case "!==" : val = left[1] !== right[1]; break;
    870                                     case "<"   : val = left[1] <   right[1]; break;
    871                                     case "<="  : val = left[1] <=  right[1]; break;
    872                                     case ">"   : val = left[1] >   right[1]; break;
    873                                     case ">="  : val = left[1] >=  right[1]; break;
    874                                 }
    875                                 if (val !== orig) {
    876                                         switch (typeof val) {
    877                                             case "string": val = [ "string", val ]; break;
    878                                             case "boolean": val = [ "name", val+"" ]; break;
    879                                             case "number": val = [ "num", val ]; break;
    880                                             default: return best;
    881                                         }
    882                                         best = best_of(best, walk(val));
    883                                 }
    884                         }
    885                         return best;
    886                 },
    887                 "conditional": function(c, t, e) {
    888                         return make_conditional(walk(c), walk(t), walk(e));
    889                 },
    890                 "try": function(t, c, f) {
    891                         return [
    892                                 "try",
    893                                 tighten(MAP(t, walk)),
    894                                 c != null ? [ c[0], tighten(MAP(c[1], walk)) ] : null,
    895                                 f != null ? tighten(MAP(f, walk)) : null
    896                         ];
    897                 },
    898                 "unary-prefix": function(op, expr) {
    899                         expr = walk(expr);
    900                         var ret = [ "unary-prefix", op, expr ];
    901                         if (op == "!")
    902                                 ret = best_of(ret, negate(expr));
    903                         return ret;
    904                 },
    905                 "name": function(name) {
    906                         switch (name) {
    907                             case "true": return [ "unary-prefix", "!", [ "num", 0 ]];
    908                             case "false": return [ "unary-prefix", "!", [ "num", 1 ]];
    909                         }
    910                 },
    911                 "new": function(ctor, args) {
    912                         if (ctor[0] == "name" && ctor[1] == "Array" && !scope.has("Array")) {
    913                                 if (args.length != 1) {
    914                                         return [ "array", args ];
    915                                 } else {
    916                                         return [ "call", [ "name", "Array" ], args ];
    917                                 }
    918                         }
    919                 },
    920                 "call": function(expr, args) {
    921                         if (expr[0] == "name" && expr[1] == "Array" && args.length != 1 && !scope.has("Array")) {
    922                                 return [ "array", args ];
    923                         }
    924                 }
    925         }, function() {
    926                 return walk(ast_add_scope(ast));
    927         });
    928 };
    929 
    930 /* -----[ re-generate code from the AST ]----- */
    931 
    932 var DOT_CALL_NO_PARENS = jsp.array_to_hash([
    933         "name",
    934         "array",
    935         "string",
    936         "dot",
    937         "sub",
    938         "call",
    939         "regexp"
    940 ]);
    941 
    942 function make_string(str) {
    943         var dq = 0, sq = 0;
    944         str = str.replace(/[\\\b\f\n\r\t\x22\x27]/g, function(s){
    945                 switch (s) {
    946                     case "\\": return "\\\\";
    947                     case "\b": return "\\b";
    948                     case "\f": return "\\f";
    949                     case "\n": return "\\n";
    950                     case "\r": return "\\r";
    951                     case "\t": return "\\t";
    952                     case '"': ++dq; return '"';
    953                     case "'": ++sq; return "'";
    954                 }
    955                 return s;
    956         });
    957         if (dq > sq) {
    958                 return "'" + str.replace(/\x27/g, "\\'") + "'";
    959         } else {
    960                 return '"' + str.replace(/\x22/g, '\\"') + '"';
    961         }
    962 };
    963 
    964 function gen_code(ast, beautify) {
    965         if (beautify) beautify = defaults(beautify, {
    966                 indent_start : 0,
    967                 indent_level : 4,
    968                 quote_keys   : false,
    969                 space_colon  : false
    970         });
    971         var indentation = 0,
    972             newline = beautify ? "\n" : "",
    973             space = beautify ? " " : "";
    974 
    975         function indent(line) {
    976                 if (line == null)
    977                         line = "";
    978                 if (beautify)
    979                         line = repeat_string(" ", beautify.indent_start + indentation * beautify.indent_level) + line;
    980                 return line;
    981         };
    982 
    983         function with_indent(cont, incr) {
    984                 if (incr == null) incr = 1;
    985                 indentation += incr;
    986                 try { return cont.apply(null, slice(arguments, 1)); }
    987                 finally { indentation -= incr; }
    988         };
    989 
    990         function add_spaces(a) {
    991                 if (beautify)
    992                         return a.join(" ");
    993                 var b = [];
    994                 for (var i = 0; i < a.length; ++i) {
    995                         var next = a[i + 1];
    996                         b.push(a[i]);
    997                         if (next &&
    998                             ((/[a-z0-9_\x24]$/i.test(a[i].toString()) && /^[a-z0-9_\x24]/i.test(next.toString())) ||
    999                              (/[\+\-]$/.test(a[i].toString()) && /^[\+\-]/.test(next.toString())))) {
   1000                                 b.push(" ");
   1001                         }
   1002                 }
   1003                 return b.join("");
   1004         };
   1005 
   1006         function add_commas(a) {
   1007                 return a.join("," + space);
   1008         };
   1009 
   1010         function parenthesize(expr) {
   1011                 var gen = make(expr);
   1012                 for (var i = 1; i < arguments.length; ++i) {
   1013                         var el = arguments[i];
   1014                         if ((el instanceof Function && el(expr)) || expr[0] == el)
   1015                                 return "(" + gen + ")";
   1016                 }
   1017                 return gen;
   1018         };
   1019 
   1020         function best_of(a) {
   1021                 if (a.length == 1) {
   1022                         return a[0];
   1023                 }
   1024                 if (a.length == 2) {
   1025                         var b = a[1];
   1026                         a = a[0];
   1027                         return a.length <= b.length ? a : b;
   1028                 }
   1029                 return best_of([ a[0], best_of(a.slice(1)) ]);
   1030         };
   1031 
   1032         function needs_parens(expr) {
   1033                 if (expr[0] == "function") {
   1034                         // dot/call on a literal function requires the
   1035                         // function literal itself to be parenthesized
   1036                         // only if it's the first "thing" in a
   1037                         // statement.  This means that the parent is
   1038                         // "stat", but it could also be a "seq" and
   1039                         // we're the first in this "seq" and the
   1040                         // parent is "stat", and so on.  Messy stuff,
   1041                         // but it worths the trouble.
   1042                         var a = slice($stack), self = a.pop(), p = a.pop();
   1043                         while (p) {
   1044                                 if (p[0] == "stat") return true;
   1045                                 if ((p[0] == "seq" && p[1] === self) ||
   1046                                     (p[0] == "call" && p[1] === self) ||
   1047                                     (p[0] == "binary" && p[2] === self)) {
   1048                                         self = p;
   1049                                         p = a.pop();
   1050                                 } else {
   1051                                         return false;
   1052                                 }
   1053                         }
   1054                 }
   1055                 return !HOP(DOT_CALL_NO_PARENS, expr[0]);
   1056         };
   1057 
   1058         function make_num(num) {
   1059                 var str = num.toString(10), a = [ str.replace(/^0\./, ".") ], m;
   1060                 if (Math.floor(num) === num) {
   1061                         a.push("0x" + num.toString(16).toLowerCase(), // probably pointless
   1062                                "0" + num.toString(8)); // same.
   1063                         if ((m = /^(.*?)(0+)$/.exec(num))) {
   1064                                 a.push(m[1] + "e" + m[2].length);
   1065                         }
   1066                 } else if ((m = /^0?\.(0+)(.*)$/.exec(num))) {
   1067                         a.push(m[2] + "e-" + (m[1].length + m[2].length),
   1068                                str.substr(str.indexOf(".")));
   1069                 }
   1070                 return best_of(a);
   1071         };
   1072 
   1073         var generators = {
   1074                 "string": make_string,
   1075                 "num": make_num,
   1076                 "name": make_name,
   1077                 "toplevel": function(statements) {
   1078                         return make_block_statements(statements)
   1079                                 .join(newline + newline);
   1080                 },
   1081                 "block": make_block,
   1082                 "var": function(defs) {
   1083                         return "var " + add_commas(MAP(defs, make_1vardef)) + ";";
   1084                 },
   1085                 "const": function(defs) {
   1086                         return "const " + add_commas(MAP(defs, make_1vardef)) + ";";
   1087                 },
   1088                 "try": function(tr, ca, fi) {
   1089                         var out = [ "try", make_block(tr) ];
   1090                         if (ca) out.push("catch", "(" + ca[0] + ")", make_block(ca[1]));
   1091                         if (fi) out.push("finally", make_block(fi));
   1092                         return add_spaces(out);
   1093                 },
   1094                 "throw": function(expr) {
   1095                         return add_spaces([ "throw", make(expr) ]) + ";";
   1096                 },
   1097                 "new": function(ctor, args) {
   1098                         args = args.length > 0 ? "(" + add_commas(MAP(args, make)) + ")" : "";
   1099                         return add_spaces([ "new", parenthesize(ctor, "seq", "binary", "conditional", "assign", function(expr){
   1100                                 var w = ast_walker(), has_call = {};
   1101                                 try {
   1102                                         w.with_walkers({
   1103                                                 "call": function() { throw has_call },
   1104                                                 "function": function() { return this }
   1105                                         }, function(){
   1106                                                 w.walk(expr);
   1107                                         });
   1108                                 } catch(ex) {
   1109                                         if (ex === has_call)
   1110                                                 return true;
   1111                                         throw ex;
   1112                                 }
   1113                         }) + args ]);
   1114                 },
   1115                 "switch": function(expr, body) {
   1116                         return add_spaces([ "switch", "(" + make(expr) + ")", make_switch_block(body) ]);
   1117                 },
   1118                 "break": function(label) {
   1119                         var out = "break";
   1120                         if (label != null)
   1121                                 out += " " + make_name(label);
   1122                         return out + ";";
   1123                 },
   1124                 "continue": function(label) {
   1125                         var out = "continue";
   1126                         if (label != null)
   1127                                 out += " " + make_name(label);
   1128                         return out + ";";
   1129                 },
   1130                 "conditional": function(co, th, el) {
   1131                         return add_spaces([ parenthesize(co, "assign", "seq", "conditional"), "?",
   1132                                             parenthesize(th, "seq"), ":",
   1133                                             parenthesize(el, "seq") ]);
   1134                 },
   1135                 "assign": function(op, lvalue, rvalue) {
   1136                         if (op && op !== true) op += "=";
   1137                         else op = "=";
   1138                         return add_spaces([ make(lvalue), op, parenthesize(rvalue, "seq") ]);
   1139                 },
   1140                 "dot": function(expr) {
   1141                         var out = make(expr), i = 1;
   1142                         if (expr[0] == "num")
   1143                                 out += ".";
   1144                         else if (needs_parens(expr))
   1145                                 out = "(" + out + ")";
   1146                         while (i < arguments.length)
   1147                                 out += "." + make_name(arguments[i++]);
   1148                         return out;
   1149                 },
   1150                 "call": function(func, args) {
   1151                         var f = make(func);
   1152                         if (needs_parens(func))
   1153                                 f = "(" + f + ")";
   1154                         return f + "(" + add_commas(MAP(args, function(expr){
   1155                                 return parenthesize(expr, "seq");
   1156                         })) + ")";
   1157                 },
   1158                 "function": make_function,
   1159                 "defun": make_function,
   1160                 "if": function(co, th, el) {
   1161                         var out = [ "if", "(" + make(co) + ")", el ? make_then(th) : make(th) ];
   1162                         if (el) {
   1163                                 out.push("else", make(el));
   1164                         }
   1165                         return add_spaces(out);
   1166                 },
   1167                 "for": function(init, cond, step, block) {
   1168                         var out = [ "for" ];
   1169                         init = (init != null ? make(init) : "").replace(/;*\s*$/, ";" + space);
   1170                         cond = (cond != null ? make(cond) : "").replace(/;*\s*$/, ";" + space);
   1171                         step = (step != null ? make(step) : "").replace(/;*\s*$/, "");
   1172                         var args = init + cond + step;
   1173                         if (args == "; ; ") args = ";;";
   1174                         out.push("(" + args + ")", make(block));
   1175                         return add_spaces(out);
   1176                 },
   1177                 "for-in": function(has_var, key, hash, block) {
   1178                         var out = add_spaces([ "for", "(" ]);
   1179                         if (has_var)
   1180                                 out += "var ";
   1181                         out += add_spaces([ make_name(key) + " in " + make(hash) + ")", make(block) ]);
   1182                         return out;
   1183                 },
   1184                 "while": function(condition, block) {
   1185                         return add_spaces([ "while", "(" + make(condition) + ")", make(block) ]);
   1186                 },
   1187                 "do": function(condition, block) {
   1188                         return add_spaces([ "do", make(block), "while", "(" + make(condition) + ")" ]) + ";";
   1189                 },
   1190                 "return": function(expr) {
   1191                         var out = [ "return" ];
   1192                         if (expr != null) out.push(make(expr));
   1193                         return add_spaces(out) + ";";
   1194                 },
   1195                 "binary": function(operator, lvalue, rvalue) {
   1196                         var left = make(lvalue), right = make(rvalue);
   1197                         // XXX: I'm pretty sure other cases will bite here.
   1198                         //      we need to be smarter.
   1199                         //      adding parens all the time is the safest bet.
   1200                         if (member(lvalue[0], [ "assign", "conditional", "seq" ]) ||
   1201                             lvalue[0] == "binary" && PRECEDENCE[operator] > PRECEDENCE[lvalue[1]]) {
   1202                                 left = "(" + left + ")";
   1203                         }
   1204                         if (member(rvalue[0], [ "assign", "conditional", "seq" ]) ||
   1205                             rvalue[0] == "binary" && PRECEDENCE[operator] >= PRECEDENCE[rvalue[1]] &&
   1206                             !(rvalue[1] == operator && member(operator, [ "&&", "||", "*" ]))) {
   1207                                 right = "(" + right + ")";
   1208                         }
   1209                         return add_spaces([ left, operator, right ]);
   1210                 },
   1211                 "unary-prefix": function(operator, expr) {
   1212                         var val = make(expr);
   1213                         if (!(expr[0] == "num" || (expr[0] == "unary-prefix" && !HOP(OPERATORS, operator + expr[1])) || !needs_parens(expr)))
   1214                                 val = "(" + val + ")";
   1215                         return operator + (jsp.is_alphanumeric_char(operator.charAt(0)) ? " " : "") + val;
   1216                 },
   1217                 "unary-postfix": function(operator, expr) {
   1218                         var val = make(expr);
   1219                         if (!(expr[0] == "num" || (expr[0] == "unary-postfix" && !HOP(OPERATORS, operator + expr[1])) || !needs_parens(expr)))
   1220                                 val = "(" + val + ")";
   1221                         return val + operator;
   1222                 },
   1223                 "sub": function(expr, subscript) {
   1224                         var hash = make(expr);
   1225                         if (needs_parens(expr))
   1226                                 hash = "(" + hash + ")";
   1227                         return hash + "[" + make(subscript) + "]";
   1228                 },
   1229                 "object": function(props) {
   1230                         if (props.length == 0)
   1231                                 return "{}";
   1232                         return "{" + newline + with_indent(function(){
   1233                                 return MAP(props, function(p){
   1234                                         if (p.length == 3) {
   1235                                                 // getter/setter.  The name is in p[0], the arg.list in p[1][2], the
   1236                                                 // body in p[1][3] and type ("get" / "set") in p[2].
   1237                                                 return indent(make_function(p[0], p[1][2], p[1][3], p[2]));
   1238                                         }
   1239                                         var key = p[0], val = make(p[1]);
   1240                                         if (beautify && beautify.quote_keys) {
   1241                                                 key = make_string(key);
   1242                                         } else if ((typeof key == "number" || !beautify && +key + "" == key)
   1243                                                    && parseFloat(key) >= 0) {
   1244                                                 key = make_num(+key);
   1245                                         } else if (!is_identifier(key)) {
   1246                                                 key = make_string(key);
   1247                                         }
   1248                                         return indent(add_spaces(beautify && beautify.space_colon
   1249                                                                  ? [ key, ":", val ]
   1250                                                                  : [ key + ":", val ]));
   1251                                 }).join("," + newline);
   1252                         }) + newline + indent("}");
   1253                 },
   1254                 "regexp": function(rx, mods) {
   1255                         return "/" + rx + "/" + mods;
   1256                 },
   1257                 "array": function(elements) {
   1258                         if (elements.length == 0) return "[]";
   1259                         return add_spaces([ "[", add_commas(MAP(elements, function(el){
   1260                                 if (!beautify && el[0] == "atom" && el[1] == "undefined") return "";
   1261                                 return parenthesize(el, "seq");
   1262                         })), "]" ]);
   1263                 },
   1264                 "stat": function(stmt) {
   1265                         return make(stmt).replace(/;*\s*$/, ";");
   1266                 },
   1267                 "seq": function() {
   1268                         return add_commas(MAP(slice(arguments), make));
   1269                 },
   1270                 "label": function(name, block) {
   1271                         return add_spaces([ make_name(name), ":", make(block) ]);
   1272                 },
   1273                 "with": function(expr, block) {
   1274                         return add_spaces([ "with", "(" + make(expr) + ")", make(block) ]);
   1275                 },
   1276                 "atom": function(name) {
   1277                         return make_name(name);
   1278                 }
   1279         };
   1280 
   1281         // The squeezer replaces "block"-s that contain only a single
   1282         // statement with the statement itself; technically, the AST
   1283         // is correct, but this can create problems when we output an
   1284         // IF having an ELSE clause where the THEN clause ends in an
   1285         // IF *without* an ELSE block (then the outer ELSE would refer
   1286         // to the inner IF).  This function checks for this case and
   1287         // adds the block brackets if needed.
   1288         function make_then(th) {
   1289                 if (th[0] == "do") {
   1290                         // https://github.com/mishoo/UglifyJS/issues/#issue/57
   1291                         // IE croaks with "syntax error" on code like this:
   1292                         //     if (foo) do ... while(cond); else ...
   1293                         // we need block brackets around do/while
   1294                         return make([ "block", [ th ]]);
   1295                 }
   1296                 var b = th;
   1297                 while (true) {
   1298                         var type = b[0];
   1299                         if (type == "if") {
   1300                                 if (!b[3])
   1301                                         // no else, we must add the block
   1302                                         return make([ "block", [ th ]]);
   1303                                 b = b[3];
   1304                         }
   1305                         else if (type == "while" || type == "do") b = b[2];
   1306                         else if (type == "for" || type == "for-in") b = b[4];
   1307                         else break;
   1308                 }
   1309                 return make(th);
   1310         };
   1311 
   1312         function make_function(name, args, body, keyword) {
   1313                 var out = keyword || "function";
   1314                 if (name) {
   1315                         out += " " + make_name(name);
   1316                 }
   1317                 out += "(" + add_commas(MAP(args, make_name)) + ")";
   1318                 return add_spaces([ out, make_block(body) ]);
   1319         };
   1320 
   1321         function make_name(name) {
   1322                 return name.toString();
   1323         };
   1324 
   1325         function make_block_statements(statements) {
   1326                 for (var a = [], last = statements.length - 1, i = 0; i <= last; ++i) {
   1327                         var stat = statements[i];
   1328                         var code = make(stat);
   1329                         if (code != ";") {
   1330                                 if (!beautify && i == last) {
   1331                                         if ((stat[0] == "while" && empty(stat[2])) ||
   1332                                             (member(stat[0], [ "for", "for-in"] ) && empty(stat[4])) ||
   1333                                             (stat[0] == "if" && empty(stat[2]) && !stat[3]) ||
   1334                                             (stat[0] == "if" && stat[3] && empty(stat[3]))) {
   1335                                                 code = code.replace(/;*\s*$/, ";");
   1336                                         } else {
   1337                                                 code = code.replace(/;+\s*$/, "");
   1338                                         }
   1339                                 }
   1340                                 a.push(code);
   1341                         }
   1342                 }
   1343                 return MAP(a, indent);
   1344         };
   1345 
   1346         function make_switch_block(body) {
   1347                 var n = body.length;
   1348                 if (n == 0) return "{}";
   1349                 return "{" + newline + MAP(body, function(branch, i){
   1350                         var has_body = branch[1].length > 0, code = with_indent(function(){
   1351                                 return indent(branch[0]
   1352                                               ? add_spaces([ "case", make(branch[0]) + ":" ])
   1353                                               : "default:");
   1354                         }, 0.5) + (has_body ? newline + with_indent(function(){
   1355                                 return make_block_statements(branch[1]).join(newline);
   1356                         }) : "");
   1357                         if (!beautify && has_body && i < n - 1)
   1358                                 code += ";";
   1359                         return code;
   1360                 }).join(newline) + newline + indent("}");
   1361         };
   1362 
   1363         function make_block(statements) {
   1364                 if (!statements) return ";";
   1365                 if (statements.length == 0) return "{}";
   1366                 return "{" + newline + with_indent(function(){
   1367                         return make_block_statements(statements).join(newline);
   1368                 }) + newline + indent("}");
   1369         };
   1370 
   1371         function make_1vardef(def) {
   1372                 var name = def[0], val = def[1];
   1373                 if (val != null)
   1374                         name = add_spaces([ name, "=", make(val) ]);
   1375                 return name;
   1376         };
   1377 
   1378         var $stack = [];
   1379 
   1380         function make(node) {
   1381                 var type = node[0];
   1382                 var gen = generators[type];
   1383                 if (!gen)
   1384                         throw new Error("Can't find generator for \"" + type + "\"");
   1385                 $stack.push(node);
   1386                 var ret = gen.apply(type, node.slice(1));
   1387                 $stack.pop();
   1388                 return ret;
   1389         };
   1390 
   1391         return make(ast);
   1392 };
   1393 
   1394 function split_lines(code, max_line_length) {
   1395         var splits = [ 0 ];
   1396         jsp.parse(function(){
   1397                 var next_token = jsp.tokenizer(code);
   1398                 var last_split = 0;
   1399                 var prev_token;
   1400                 function current_length(tok) {
   1401                         return tok.pos - last_split;
   1402                 };
   1403                 function split_here(tok) {
   1404                         last_split = tok.pos;
   1405                         splits.push(last_split);
   1406                 };
   1407                 function custom(){
   1408                         var tok = next_token.apply(this, arguments);
   1409                         out: {
   1410                                 if (prev_token) {
   1411                                         if (prev_token.type == "keyword") break out;
   1412                                 }
   1413                                 if (current_length(tok) > max_line_length) {
   1414                                         switch (tok.type) {
   1415                                             case "keyword":
   1416                                             case "atom":
   1417                                             case "name":
   1418                                             case "punc":
   1419                                                 split_here(tok);
   1420                                                 break out;
   1421                                         }
   1422                                 }
   1423                         }
   1424                         prev_token = tok;
   1425                         return tok;
   1426                 };
   1427                 custom.context = function() {
   1428                         return next_token.context.apply(this, arguments);
   1429                 };
   1430                 return custom;
   1431         }());
   1432         return splits.map(function(pos, i){
   1433                 return code.substring(pos, splits[i + 1] || code.length);
   1434         }).join("\n");
   1435 };
   1436 
   1437 /* -----[ Utilities ]----- */
   1438 
   1439 function repeat_string(str, i) {
   1440         if (i <= 0) return "";
   1441         if (i == 1) return str;
   1442         var d = repeat_string(str, i >> 1);
   1443         d += d;
   1444         if (i & 1) d += str;
   1445         return d;
   1446 };
   1447 
   1448 function defaults(args, defs) {
   1449         var ret = {};
   1450         if (args === true)
   1451                 args = {};
   1452         for (var i in defs) if (HOP(defs, i)) {
   1453                 ret[i] = (args && HOP(args, i)) ? args[i] : defs[i];
   1454         }
   1455         return ret;
   1456 };
   1457 
   1458 function is_identifier(name) {
   1459         return /^[a-z_$][a-z0-9_$]*$/i.test(name)
   1460                 && name != "this"
   1461                 && !HOP(jsp.KEYWORDS_ATOM, name)
   1462                 && !HOP(jsp.RESERVED_WORDS, name)
   1463                 && !HOP(jsp.KEYWORDS, name);
   1464 };
   1465 
   1466 function HOP(obj, prop) {
   1467         return Object.prototype.hasOwnProperty.call(obj, prop);
   1468 };
   1469 
   1470 // some utilities
   1471 
   1472 var MAP;
   1473 
   1474 (function(){
   1475         MAP = function(a, f, o) {
   1476                 var ret = [];
   1477                 for (var i = 0; i < a.length; ++i) {
   1478                         var val = f.call(o, a[i], i);
   1479                         if (val instanceof AtTop) ret.unshift(val.v);
   1480                         else ret.push(val);
   1481                 }
   1482                 return ret;
   1483         };
   1484         MAP.at_top = function(val) { return new AtTop(val) };
   1485         function AtTop(val) { this.v = val };
   1486 })();
   1487 
   1488 /* -----[ Exports ]----- */
   1489 
   1490 exports.ast_walker = ast_walker;
   1491 exports.ast_mangle = ast_mangle;
   1492 exports.ast_squeeze = ast_squeeze;
   1493 exports.gen_code = gen_code;
   1494 exports.ast_add_scope = ast_add_scope;
   1495 exports.ast_squeeze_more = require("./squeeze-more").ast_squeeze_more;
   1496 exports.set_logger = function(logger) { warn = logger };
   1497 exports.make_string = make_string;
   1498 exports.split_lines = split_lines;
   1499