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@gmail.com>
28                       http://mihai.bazon.net/blog
29
30  Distributed under the BSD license:
31
32    Copyright 2010 (c) Mihai Bazon <mihai.bazon@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
62var 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
70function 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
253function 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
271var 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
283Scope.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
358function 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
460function 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
545var warn = function(){};
546
547function best_of(ast1, ast2) {
548        return gen_code(ast1).length > gen_code(ast2[0] == "stat" ? ast2[1] : ast2).length ? ast2 : ast1;
549};
550
551function 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
557function 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
565function 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
590function 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
598function empty(b) {
599        return !b || (b[0] == "block" && (!b[1] || b[1].length == 0));
600};
601
602function 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
932var DOT_CALL_NO_PARENS = jsp.array_to_hash([
933        "name",
934        "array",
935        "string",
936        "dot",
937        "sub",
938        "call",
939        "regexp"
940]);
941
942function 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
964function 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
1394function 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
1439function 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
1448function 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
1458function 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
1466function HOP(obj, prop) {
1467        return Object.prototype.hasOwnProperty.call(obj, prop);
1468};
1469
1470// some utilities
1471
1472var 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
1490exports.ast_walker = ast_walker;
1491exports.ast_mangle = ast_mangle;
1492exports.ast_squeeze = ast_squeeze;
1493exports.gen_code = gen_code;
1494exports.ast_add_scope = ast_add_scope;
1495exports.ast_squeeze_more = require("./squeeze-more").ast_squeeze_more;
1496exports.set_logger = function(logger) { warn = logger };
1497exports.make_string = make_string;
1498exports.split_lines = split_lines;
1499