Tizen 2.0 Release
[platform/framework/web/web-ui-fw.git] / libs / js / jquery-mobile-1.2.0 / node_modules / grunt / node_modules / uglify-js / lib / process.js
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, options) -- mangles the variable/function names
14       in the AST.  Returns an AST.
15
16     - ast_squeeze(ast) -- employs various optimizations to make the
17       final generated code even smaller.  Returns an AST.
18
19     - gen_code(ast, options) -- generates JS code from the AST.  Pass
20       true (or an object, see the code for some options) as second
21       argument to get "pretty" (indented) code.
22
23   -------------------------------- (C) ---------------------------------
24
25                            Author: Mihai Bazon
26                          <mihai.bazon@gmail.com>
27                        http://mihai.bazon.net/blog
28
29   Distributed under the BSD license:
30
31     Copyright 2010 (c) Mihai Bazon <mihai.bazon@gmail.com>
32
33     Redistribution and use in source and binary forms, with or without
34     modification, are permitted provided that the following conditions
35     are met:
36
37         * Redistributions of source code must retain the above
38           copyright notice, this list of conditions and the following
39           disclaimer.
40
41         * Redistributions in binary form must reproduce the above
42           copyright notice, this list of conditions and the following
43           disclaimer in the documentation and/or other materials
44           provided with the distribution.
45
46     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
47     EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
48     IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
49     PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
50     LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
51     OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
52     PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
53     PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
54     THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
55     TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
56     THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
57     SUCH DAMAGE.
58
59  ***********************************************************************/
60
61 var jsp = require("./parse-js"),
62     curry = jsp.curry,
63     slice = jsp.slice,
64     member = jsp.member,
65     is_identifier_char = jsp.is_identifier_char,
66     PRECEDENCE = jsp.PRECEDENCE,
67     OPERATORS = jsp.OPERATORS;
68
69 /* -----[ helper for AST traversal ]----- */
70
71 function ast_walker() {
72     function _vardefs(defs) {
73         return [ this[0], MAP(defs, function(def){
74             var a = [ def[0] ];
75             if (def.length > 1)
76                 a[1] = walk(def[1]);
77             return a;
78         }) ];
79     };
80     function _block(statements) {
81         var out = [ this[0] ];
82         if (statements != null)
83             out.push(MAP(statements, walk));
84         return out;
85     };
86     var walkers = {
87         "string": function(str) {
88             return [ this[0], str ];
89         },
90         "num": function(num) {
91             return [ this[0], num ];
92         },
93         "name": function(name) {
94             return [ this[0], name ];
95         },
96         "toplevel": function(statements) {
97             return [ this[0], MAP(statements, walk) ];
98         },
99         "block": _block,
100         "splice": _block,
101         "var": _vardefs,
102         "const": _vardefs,
103         "try": function(t, c, f) {
104             return [
105                 this[0],
106                 MAP(t, walk),
107                 c != null ? [ c[0], MAP(c[1], walk) ] : null,
108                 f != null ? MAP(f, walk) : null
109             ];
110         },
111         "throw": function(expr) {
112             return [ this[0], walk(expr) ];
113         },
114         "new": function(ctor, args) {
115             return [ this[0], walk(ctor), MAP(args, walk) ];
116         },
117         "switch": function(expr, body) {
118             return [ this[0], walk(expr), MAP(body, function(branch){
119                 return [ branch[0] ? walk(branch[0]) : null,
120                          MAP(branch[1], walk) ];
121             }) ];
122         },
123         "break": function(label) {
124             return [ this[0], label ];
125         },
126         "continue": function(label) {
127             return [ this[0], label ];
128         },
129         "conditional": function(cond, t, e) {
130             return [ this[0], walk(cond), walk(t), walk(e) ];
131         },
132         "assign": function(op, lvalue, rvalue) {
133             return [ this[0], op, walk(lvalue), walk(rvalue) ];
134         },
135         "dot": function(expr) {
136             return [ this[0], walk(expr) ].concat(slice(arguments, 1));
137         },
138         "call": function(expr, args) {
139             return [ this[0], walk(expr), MAP(args, walk) ];
140         },
141         "function": function(name, args, body) {
142             return [ this[0], name, args.slice(), MAP(body, walk) ];
143         },
144         "debugger": function() {
145             return [ this[0] ];
146         },
147         "defun": function(name, args, body) {
148             return [ this[0], name, args.slice(), MAP(body, walk) ];
149         },
150         "if": function(conditional, t, e) {
151             return [ this[0], walk(conditional), walk(t), walk(e) ];
152         },
153         "for": function(init, cond, step, block) {
154             return [ this[0], walk(init), walk(cond), walk(step), walk(block) ];
155         },
156         "for-in": function(vvar, key, hash, block) {
157             return [ this[0], walk(vvar), walk(key), walk(hash), walk(block) ];
158         },
159         "while": function(cond, block) {
160             return [ this[0], walk(cond), walk(block) ];
161         },
162         "do": function(cond, block) {
163             return [ this[0], walk(cond), walk(block) ];
164         },
165         "return": function(expr) {
166             return [ this[0], walk(expr) ];
167         },
168         "binary": function(op, left, right) {
169             return [ this[0], op, walk(left), walk(right) ];
170         },
171         "unary-prefix": function(op, expr) {
172             return [ this[0], op, walk(expr) ];
173         },
174         "unary-postfix": function(op, expr) {
175             return [ this[0], op, walk(expr) ];
176         },
177         "sub": function(expr, subscript) {
178             return [ this[0], walk(expr), walk(subscript) ];
179         },
180         "object": function(props) {
181             return [ this[0], MAP(props, function(p){
182                 return p.length == 2
183                     ? [ p[0], walk(p[1]) ]
184                     : [ p[0], walk(p[1]), p[2] ]; // get/set-ter
185             }) ];
186         },
187         "regexp": function(rx, mods) {
188             return [ this[0], rx, mods ];
189         },
190         "array": function(elements) {
191             return [ this[0], MAP(elements, walk) ];
192         },
193         "stat": function(stat) {
194             return [ this[0], walk(stat) ];
195         },
196         "seq": function() {
197             return [ this[0] ].concat(MAP(slice(arguments), walk));
198         },
199         "label": function(name, block) {
200             return [ this[0], name, walk(block) ];
201         },
202         "with": function(expr, block) {
203             return [ this[0], walk(expr), walk(block) ];
204         },
205         "atom": function(name) {
206             return [ this[0], name ];
207         },
208         "directive": function(dir) {
209             return [ this[0], dir ];
210         }
211     };
212
213     var user = {};
214     var stack = [];
215     function walk(ast) {
216         if (ast == null)
217             return null;
218         try {
219             stack.push(ast);
220             var type = ast[0];
221             var gen = user[type];
222             if (gen) {
223                 var ret = gen.apply(ast, ast.slice(1));
224                 if (ret != null)
225                     return ret;
226             }
227             gen = walkers[type];
228             return gen.apply(ast, ast.slice(1));
229         } finally {
230             stack.pop();
231         }
232     };
233
234     function dive(ast) {
235         if (ast == null)
236             return null;
237         try {
238             stack.push(ast);
239             return walkers[ast[0]].apply(ast, ast.slice(1));
240         } finally {
241             stack.pop();
242         }
243     };
244
245     function with_walkers(walkers, cont){
246         var save = {}, i;
247         for (i in walkers) if (HOP(walkers, i)) {
248             save[i] = user[i];
249             user[i] = walkers[i];
250         }
251         var ret = cont();
252         for (i in save) if (HOP(save, i)) {
253             if (!save[i]) delete user[i];
254             else user[i] = save[i];
255         }
256         return ret;
257     };
258
259     return {
260         walk: walk,
261         dive: dive,
262         with_walkers: with_walkers,
263         parent: function() {
264             return stack[stack.length - 2]; // last one is current node
265         },
266         stack: function() {
267             return stack;
268         }
269     };
270 };
271
272 /* -----[ Scope and mangling ]----- */
273
274 function Scope(parent) {
275     this.names = {};        // names defined in this scope
276     this.mangled = {};      // mangled names (orig.name => mangled)
277     this.rev_mangled = {};  // reverse lookup (mangled => orig.name)
278     this.cname = -1;        // current mangled name
279     this.refs = {};         // names referenced from this scope
280     this.uses_with = false; // will become TRUE if with() is detected in this or any subscopes
281     this.uses_eval = false; // will become TRUE if eval() is detected in this or any subscopes
282     this.directives = [];   // directives activated from this scope
283     this.parent = parent;   // parent scope
284     this.children = [];     // sub-scopes
285     if (parent) {
286         this.level = parent.level + 1;
287         parent.children.push(this);
288     } else {
289         this.level = 0;
290     }
291 };
292
293 function base54_digits() {
294     if (typeof DIGITS_OVERRIDE_FOR_TESTING != "undefined")
295         return DIGITS_OVERRIDE_FOR_TESTING;
296     else
297         return "etnrisouaflchpdvmgybwESxTNCkLAOM_DPHBjFIqRUzWXV$JKQGYZ0516372984";
298 }
299
300 var base54 = (function(){
301     var DIGITS = base54_digits();
302     return function(num) {
303         var ret = "", base = 54;
304         do {
305             ret += DIGITS.charAt(num % base);
306             num = Math.floor(num / base);
307             base = 64;
308         } while (num > 0);
309         return ret;
310     };
311 })();
312
313 Scope.prototype = {
314     has: function(name) {
315         for (var s = this; s; s = s.parent)
316             if (HOP(s.names, name))
317                 return s;
318     },
319     has_mangled: function(mname) {
320         for (var s = this; s; s = s.parent)
321             if (HOP(s.rev_mangled, mname))
322                 return s;
323     },
324     toJSON: function() {
325         return {
326             names: this.names,
327             uses_eval: this.uses_eval,
328             uses_with: this.uses_with
329         };
330     },
331
332     next_mangled: function() {
333         // we must be careful that the new mangled name:
334         //
335         // 1. doesn't shadow a mangled name from a parent
336         //    scope, unless we don't reference the original
337         //    name from this scope OR from any sub-scopes!
338         //    This will get slow.
339         //
340         // 2. doesn't shadow an original name from a parent
341         //    scope, in the event that the name is not mangled
342         //    in the parent scope and we reference that name
343         //    here OR IN ANY SUBSCOPES!
344         //
345         // 3. doesn't shadow a name that is referenced but not
346         //    defined (possibly global defined elsewhere).
347         for (;;) {
348             var m = base54(++this.cname), prior;
349
350             // case 1.
351             prior = this.has_mangled(m);
352             if (prior && this.refs[prior.rev_mangled[m]] === prior)
353                 continue;
354
355             // case 2.
356             prior = this.has(m);
357             if (prior && prior !== this && this.refs[m] === prior && !prior.has_mangled(m))
358                 continue;
359
360             // case 3.
361             if (HOP(this.refs, m) && this.refs[m] == null)
362                 continue;
363
364             // I got "do" once. :-/
365             if (!is_identifier(m))
366                 continue;
367
368             return m;
369         }
370     },
371     set_mangle: function(name, m) {
372         this.rev_mangled[m] = name;
373         return this.mangled[name] = m;
374     },
375     get_mangled: function(name, newMangle) {
376         if (this.uses_eval || this.uses_with) return name; // no mangle if eval or with is in use
377         var s = this.has(name);
378         if (!s) return name; // not in visible scope, no mangle
379         if (HOP(s.mangled, name)) return s.mangled[name]; // already mangled in this scope
380         if (!newMangle) return name;                      // not found and no mangling requested
381         return s.set_mangle(name, s.next_mangled());
382     },
383     references: function(name) {
384         return name && !this.parent || this.uses_with || this.uses_eval || this.refs[name];
385     },
386     define: function(name, type) {
387         if (name != null) {
388             if (type == "var" || !HOP(this.names, name))
389                 this.names[name] = type || "var";
390             return name;
391         }
392     },
393     active_directive: function(dir) {
394         return member(dir, this.directives) || this.parent && this.parent.active_directive(dir);
395     }
396 };
397
398 function ast_add_scope(ast) {
399
400     var current_scope = null;
401     var w = ast_walker(), walk = w.walk;
402     var having_eval = [];
403
404     function with_new_scope(cont) {
405         current_scope = new Scope(current_scope);
406         current_scope.labels = new Scope();
407         var ret = current_scope.body = cont();
408         ret.scope = current_scope;
409         current_scope = current_scope.parent;
410         return ret;
411     };
412
413     function define(name, type) {
414         return current_scope.define(name, type);
415     };
416
417     function reference(name) {
418         current_scope.refs[name] = true;
419     };
420
421     function _lambda(name, args, body) {
422         var is_defun = this[0] == "defun";
423         return [ this[0], is_defun ? define(name, "defun") : name, args, with_new_scope(function(){
424             if (!is_defun) define(name, "lambda");
425             MAP(args, function(name){ define(name, "arg") });
426             return MAP(body, walk);
427         })];
428     };
429
430     function _vardefs(type) {
431         return function(defs) {
432             MAP(defs, function(d){
433                 define(d[0], type);
434                 if (d[1]) reference(d[0]);
435             });
436         };
437     };
438
439     function _breacont(label) {
440         if (label)
441             current_scope.labels.refs[label] = true;
442     };
443
444     return with_new_scope(function(){
445         // process AST
446         var ret = w.with_walkers({
447             "function": _lambda,
448             "defun": _lambda,
449             "label": function(name, stat) { current_scope.labels.define(name) },
450             "break": _breacont,
451             "continue": _breacont,
452             "with": function(expr, block) {
453                 for (var s = current_scope; s; s = s.parent)
454                     s.uses_with = true;
455             },
456             "var": _vardefs("var"),
457             "const": _vardefs("const"),
458             "try": function(t, c, f) {
459                 if (c != null) return [
460                     this[0],
461                     MAP(t, walk),
462                     [ define(c[0], "catch"), MAP(c[1], walk) ],
463                     f != null ? MAP(f, walk) : null
464                 ];
465             },
466             "name": function(name) {
467                 if (name == "eval")
468                     having_eval.push(current_scope);
469                 reference(name);
470             }
471         }, function(){
472             return walk(ast);
473         });
474
475         // the reason why we need an additional pass here is
476         // that names can be used prior to their definition.
477
478         // scopes where eval was detected and their parents
479         // are marked with uses_eval, unless they define the
480         // "eval" name.
481         MAP(having_eval, function(scope){
482             if (!scope.has("eval")) while (scope) {
483                 scope.uses_eval = true;
484                 scope = scope.parent;
485             }
486         });
487
488         // for referenced names it might be useful to know
489         // their origin scope.  current_scope here is the
490         // toplevel one.
491         function fixrefs(scope, i) {
492             // do children first; order shouldn't matter
493             for (i = scope.children.length; --i >= 0;)
494                 fixrefs(scope.children[i]);
495             for (i in scope.refs) if (HOP(scope.refs, i)) {
496                 // find origin scope and propagate the reference to origin
497                 for (var origin = scope.has(i), s = scope; s; s = s.parent) {
498                     s.refs[i] = origin;
499                     if (s === origin) break;
500                 }
501             }
502         };
503         fixrefs(current_scope);
504
505         return ret;
506     });
507
508 };
509
510 /* -----[ mangle names ]----- */
511
512 function ast_mangle(ast, options) {
513     var w = ast_walker(), walk = w.walk, scope;
514     options = defaults(options, {
515         mangle       : true,
516         toplevel     : false,
517         defines      : null,
518         except       : null,
519         no_functions : false
520     });
521
522     function get_mangled(name, newMangle) {
523         if (!options.mangle) return name;
524         if (!options.toplevel && !scope.parent) return name; // don't mangle toplevel
525         if (options.except && member(name, options.except))
526             return name;
527         if (options.no_functions && HOP(scope.names, name) &&
528             (scope.names[name] == 'defun' || scope.names[name] == 'lambda'))
529             return name;
530         return scope.get_mangled(name, newMangle);
531     };
532
533     function get_define(name) {
534         if (options.defines) {
535             // we always lookup a defined symbol for the current scope FIRST, so declared
536             // vars trump a DEFINE symbol, but if no such var is found, then match a DEFINE value
537             if (!scope.has(name)) {
538                 if (HOP(options.defines, name)) {
539                     return options.defines[name];
540                 }
541             }
542             return null;
543         }
544     };
545
546     function _lambda(name, args, body) {
547         if (!options.no_functions && options.mangle) {
548             var is_defun = this[0] == "defun", extra;
549             if (name) {
550                 if (is_defun) name = get_mangled(name);
551                 else if (body.scope.references(name)) {
552                     extra = {};
553                     if (!(scope.uses_eval || scope.uses_with))
554                         name = extra[name] = scope.next_mangled();
555                     else
556                         extra[name] = name;
557                 }
558                 else name = null;
559             }
560         }
561         body = with_scope(body.scope, function(){
562             args = MAP(args, function(name){ return get_mangled(name) });
563             return MAP(body, walk);
564         }, extra);
565         return [ this[0], name, args, body ];
566     };
567
568     function with_scope(s, cont, extra) {
569         var _scope = scope;
570         scope = s;
571         if (extra) for (var i in extra) if (HOP(extra, i)) {
572             s.set_mangle(i, extra[i]);
573         }
574         for (var i in s.names) if (HOP(s.names, i)) {
575             get_mangled(i, true);
576         }
577         var ret = cont();
578         ret.scope = s;
579         scope = _scope;
580         return ret;
581     };
582
583     function _vardefs(defs) {
584         return [ this[0], MAP(defs, function(d){
585             return [ get_mangled(d[0]), walk(d[1]) ];
586         }) ];
587     };
588
589     function _breacont(label) {
590         if (label) return [ this[0], scope.labels.get_mangled(label) ];
591     };
592
593     return w.with_walkers({
594         "function": _lambda,
595         "defun": function() {
596             // move function declarations to the top when
597             // they are not in some block.
598             var ast = _lambda.apply(this, arguments);
599             switch (w.parent()[0]) {
600               case "toplevel":
601               case "function":
602               case "defun":
603                 return MAP.at_top(ast);
604             }
605             return ast;
606         },
607         "label": function(label, stat) {
608             if (scope.labels.refs[label]) return [
609                 this[0],
610                 scope.labels.get_mangled(label, true),
611                 walk(stat)
612             ];
613             return walk(stat);
614         },
615         "break": _breacont,
616         "continue": _breacont,
617         "var": _vardefs,
618         "const": _vardefs,
619         "name": function(name) {
620             return get_define(name) || [ this[0], get_mangled(name) ];
621         },
622         "try": function(t, c, f) {
623             return [ this[0],
624                      MAP(t, walk),
625                      c != null ? [ get_mangled(c[0]), MAP(c[1], walk) ] : null,
626                      f != null ? MAP(f, walk) : null ];
627         },
628         "toplevel": function(body) {
629             var self = this;
630             return with_scope(self.scope, function(){
631                 return [ self[0], MAP(body, walk) ];
632             });
633         },
634         "directive": function() {
635             return MAP.at_top(this);
636         }
637     }, function() {
638         return walk(ast_add_scope(ast));
639     });
640 };
641
642 /* -----[
643    - compress foo["bar"] into foo.bar,
644    - remove block brackets {} where possible
645    - join consecutive var declarations
646    - various optimizations for IFs:
647    - if (cond) foo(); else bar();  ==>  cond?foo():bar();
648    - if (cond) foo();  ==>  cond&&foo();
649    - if (foo) return bar(); else return baz();  ==> return foo?bar():baz(); // also for throw
650    - if (foo) return bar(); else something();  ==> {if(foo)return bar();something()}
651    ]----- */
652
653 var warn = function(){};
654
655 function best_of(ast1, ast2) {
656     return gen_code(ast1).length > gen_code(ast2[0] == "stat" ? ast2[1] : ast2).length ? ast2 : ast1;
657 };
658
659 function last_stat(b) {
660     if (b[0] == "block" && b[1] && b[1].length > 0)
661         return b[1][b[1].length - 1];
662     return b;
663 }
664
665 function aborts(t) {
666     if (t) switch (last_stat(t)[0]) {
667       case "return":
668       case "break":
669       case "continue":
670       case "throw":
671         return true;
672     }
673 };
674
675 function boolean_expr(expr) {
676     return ( (expr[0] == "unary-prefix"
677               && member(expr[1], [ "!", "delete" ])) ||
678
679              (expr[0] == "binary"
680               && member(expr[1], [ "in", "instanceof", "==", "!=", "===", "!==", "<", "<=", ">=", ">" ])) ||
681
682              (expr[0] == "binary"
683               && member(expr[1], [ "&&", "||" ])
684               && boolean_expr(expr[2])
685               && boolean_expr(expr[3])) ||
686
687              (expr[0] == "conditional"
688               && boolean_expr(expr[2])
689               && boolean_expr(expr[3])) ||
690
691              (expr[0] == "assign"
692               && expr[1] === true
693               && boolean_expr(expr[3])) ||
694
695              (expr[0] == "seq"
696               && boolean_expr(expr[expr.length - 1]))
697            );
698 };
699
700 function empty(b) {
701     return !b || (b[0] == "block" && (!b[1] || b[1].length == 0));
702 };
703
704 function is_string(node) {
705     return (node[0] == "string" ||
706             node[0] == "unary-prefix" && node[1] == "typeof" ||
707             node[0] == "binary" && node[1] == "+" &&
708             (is_string(node[2]) || is_string(node[3])));
709 };
710
711 var when_constant = (function(){
712
713     var $NOT_CONSTANT = {};
714
715     // this can only evaluate constant expressions.  If it finds anything
716     // not constant, it throws $NOT_CONSTANT.
717     function evaluate(expr) {
718         switch (expr[0]) {
719           case "string":
720           case "num":
721             return expr[1];
722           case "name":
723           case "atom":
724             switch (expr[1]) {
725               case "true": return true;
726               case "false": return false;
727               case "null": return null;
728             }
729             break;
730           case "unary-prefix":
731             switch (expr[1]) {
732               case "!": return !evaluate(expr[2]);
733               case "typeof": return typeof evaluate(expr[2]);
734               case "~": return ~evaluate(expr[2]);
735               case "-": return -evaluate(expr[2]);
736               case "+": return +evaluate(expr[2]);
737             }
738             break;
739           case "binary":
740             var left = expr[2], right = expr[3];
741             switch (expr[1]) {
742               case "&&"         : return evaluate(left) &&         evaluate(right);
743               case "||"         : return evaluate(left) ||         evaluate(right);
744               case "|"          : return evaluate(left) |          evaluate(right);
745               case "&"          : return evaluate(left) &          evaluate(right);
746               case "^"          : return evaluate(left) ^          evaluate(right);
747               case "+"          : return evaluate(left) +          evaluate(right);
748               case "*"          : return evaluate(left) *          evaluate(right);
749               case "/"          : return evaluate(left) /          evaluate(right);
750               case "%"          : return evaluate(left) %          evaluate(right);
751               case "-"          : return evaluate(left) -          evaluate(right);
752               case "<<"         : return evaluate(left) <<         evaluate(right);
753               case ">>"         : return evaluate(left) >>         evaluate(right);
754               case ">>>"        : return evaluate(left) >>>        evaluate(right);
755               case "=="         : return evaluate(left) ==         evaluate(right);
756               case "==="        : return evaluate(left) ===        evaluate(right);
757               case "!="         : return evaluate(left) !=         evaluate(right);
758               case "!=="        : return evaluate(left) !==        evaluate(right);
759               case "<"          : return evaluate(left) <          evaluate(right);
760               case "<="         : return evaluate(left) <=         evaluate(right);
761               case ">"          : return evaluate(left) >          evaluate(right);
762               case ">="         : return evaluate(left) >=         evaluate(right);
763               case "in"         : return evaluate(left) in         evaluate(right);
764               case "instanceof" : return evaluate(left) instanceof evaluate(right);
765             }
766         }
767         throw $NOT_CONSTANT;
768     };
769
770     return function(expr, yes, no) {
771         try {
772             var val = evaluate(expr), ast;
773             switch (typeof val) {
774               case "string": ast =  [ "string", val ]; break;
775               case "number": ast =  [ "num", val ]; break;
776               case "boolean": ast =  [ "name", String(val) ]; break;
777               default:
778                 if (val === null) { ast = [ "atom", "null" ]; break; }
779                 throw new Error("Can't handle constant of type: " + (typeof val));
780             }
781             return yes.call(expr, ast, val);
782         } catch(ex) {
783             if (ex === $NOT_CONSTANT) {
784                 if (expr[0] == "binary"
785                     && (expr[1] == "===" || expr[1] == "!==")
786                     && ((is_string(expr[2]) && is_string(expr[3]))
787                         || (boolean_expr(expr[2]) && boolean_expr(expr[3])))) {
788                     expr[1] = expr[1].substr(0, 2);
789                 }
790                 else if (no && expr[0] == "binary"
791                          && (expr[1] == "||" || expr[1] == "&&")) {
792                     // the whole expression is not constant but the lval may be...
793                     try {
794                         var lval = evaluate(expr[2]);
795                         expr = ((expr[1] == "&&" && (lval ? expr[3] : lval))    ||
796                                 (expr[1] == "||" && (lval ? lval    : expr[3])) ||
797                                 expr);
798                     } catch(ex2) {
799                         // IGNORE... lval is not constant
800                     }
801                 }
802                 return no ? no.call(expr, expr) : null;
803             }
804             else throw ex;
805         }
806     };
807
808 })();
809
810 function warn_unreachable(ast) {
811     if (!empty(ast))
812         warn("Dropping unreachable code: " + gen_code(ast, true));
813 };
814
815 function prepare_ifs(ast) {
816     var w = ast_walker(), walk = w.walk;
817     // In this first pass, we rewrite ifs which abort with no else with an
818     // if-else.  For example:
819     //
820     // if (x) {
821     //     blah();
822     //     return y;
823     // }
824     // foobar();
825     //
826     // is rewritten into:
827     //
828     // if (x) {
829     //     blah();
830     //     return y;
831     // } else {
832     //     foobar();
833     // }
834     function redo_if(statements) {
835         statements = MAP(statements, walk);
836
837         for (var i = 0; i < statements.length; ++i) {
838             var fi = statements[i];
839             if (fi[0] != "if") continue;
840
841             if (fi[3]) continue;
842
843             var t = fi[2];
844             if (!aborts(t)) continue;
845
846             var conditional = walk(fi[1]);
847
848             var e_body = redo_if(statements.slice(i + 1));
849             var e = e_body.length == 1 ? e_body[0] : [ "block", e_body ];
850
851             return statements.slice(0, i).concat([ [
852                 fi[0],          // "if"
853                 conditional,    // conditional
854                 t,              // then
855                 e               // else
856             ] ]);
857         }
858
859         return statements;
860     };
861
862     function redo_if_lambda(name, args, body) {
863         body = redo_if(body);
864         return [ this[0], name, args, body ];
865     };
866
867     function redo_if_block(statements) {
868         return [ this[0], statements != null ? redo_if(statements) : null ];
869     };
870
871     return w.with_walkers({
872         "defun": redo_if_lambda,
873         "function": redo_if_lambda,
874         "block": redo_if_block,
875         "splice": redo_if_block,
876         "toplevel": function(statements) {
877             return [ this[0], redo_if(statements) ];
878         },
879         "try": function(t, c, f) {
880             return [
881                 this[0],
882                 redo_if(t),
883                 c != null ? [ c[0], redo_if(c[1]) ] : null,
884                 f != null ? redo_if(f) : null
885             ];
886         }
887     }, function() {
888         return walk(ast);
889     });
890 };
891
892 function for_side_effects(ast, handler) {
893     var w = ast_walker(), walk = w.walk;
894     var $stop = {}, $restart = {};
895     function stop() { throw $stop };
896     function restart() { throw $restart };
897     function found(){ return handler.call(this, this, w, stop, restart) };
898     function unary(op) {
899         if (op == "++" || op == "--")
900             return found.apply(this, arguments);
901     };
902     function binary(op) {
903         if (op == "&&" || op == "||")
904             return found.apply(this, arguments);
905     };
906     return w.with_walkers({
907         "try": found,
908         "throw": found,
909         "return": found,
910         "new": found,
911         "switch": found,
912         "break": found,
913         "continue": found,
914         "assign": found,
915         "call": found,
916         "if": found,
917         "for": found,
918         "for-in": found,
919         "while": found,
920         "do": found,
921         "return": found,
922         "unary-prefix": unary,
923         "unary-postfix": unary,
924         "conditional": found,
925         "binary": binary,
926         "defun": found
927     }, function(){
928         while (true) try {
929             walk(ast);
930             break;
931         } catch(ex) {
932             if (ex === $stop) break;
933             if (ex === $restart) continue;
934             throw ex;
935         }
936     });
937 };
938
939 function ast_lift_variables(ast) {
940     var w = ast_walker(), walk = w.walk, scope;
941     function do_body(body, env) {
942         var _scope = scope;
943         scope = env;
944         body = MAP(body, walk);
945         var hash = {}, names = MAP(env.names, function(type, name){
946             if (type != "var") return MAP.skip;
947             if (!env.references(name)) return MAP.skip;
948             hash[name] = true;
949             return [ name ];
950         });
951         if (names.length > 0) {
952             // looking for assignments to any of these variables.
953             // we can save considerable space by moving the definitions
954             // in the var declaration.
955             for_side_effects([ "block", body ], function(ast, walker, stop, restart) {
956                 if (ast[0] == "assign"
957                     && ast[1] === true
958                     && ast[2][0] == "name"
959                     && HOP(hash, ast[2][1])) {
960                     // insert the definition into the var declaration
961                     for (var i = names.length; --i >= 0;) {
962                         if (names[i][0] == ast[2][1]) {
963                             if (names[i][1]) // this name already defined, we must stop
964                                 stop();
965                             names[i][1] = ast[3]; // definition
966                             names.push(names.splice(i, 1)[0]);
967                             break;
968                         }
969                     }
970                     // remove this assignment from the AST.
971                     var p = walker.parent();
972                     if (p[0] == "seq") {
973                         var a = p[2];
974                         a.unshift(0, p.length);
975                         p.splice.apply(p, a);
976                     }
977                     else if (p[0] == "stat") {
978                         p.splice(0, p.length, "block"); // empty statement
979                     }
980                     else {
981                         stop();
982                     }
983                     restart();
984                 }
985                 stop();
986             });
987             body.unshift([ "var", names ]);
988         }
989         scope = _scope;
990         return body;
991     };
992     function _vardefs(defs) {
993         var ret = null;
994         for (var i = defs.length; --i >= 0;) {
995             var d = defs[i];
996             if (!d[1]) continue;
997             d = [ "assign", true, [ "name", d[0] ], d[1] ];
998             if (ret == null) ret = d;
999             else ret = [ "seq", d, ret ];
1000         }
1001         if (ret == null && w.parent()[0] != "for") {
1002             if (w.parent()[0] == "for-in")
1003                 return [ "name", defs[0][0] ];
1004             return MAP.skip;
1005         }
1006         return [ "stat", ret ];
1007     };
1008     function _toplevel(body) {
1009         return [ this[0], do_body(body, this.scope) ];
1010     };
1011     return w.with_walkers({
1012         "function": function(name, args, body){
1013             for (var i = args.length; --i >= 0 && !body.scope.references(args[i]);)
1014                 args.pop();
1015             if (!body.scope.references(name)) name = null;
1016             return [ this[0], name, args, do_body(body, body.scope) ];
1017         },
1018         "defun": function(name, args, body){
1019             if (!scope.references(name)) return MAP.skip;
1020             for (var i = args.length; --i >= 0 && !body.scope.references(args[i]);)
1021                 args.pop();
1022             return [ this[0], name, args, do_body(body, body.scope) ];
1023         },
1024         "var": _vardefs,
1025         "toplevel": _toplevel
1026     }, function(){
1027         return walk(ast_add_scope(ast));
1028     });
1029 };
1030
1031 function ast_squeeze(ast, options) {
1032     ast = squeeze_1(ast, options);
1033     ast = squeeze_2(ast, options);
1034     return ast;
1035 };
1036
1037 function squeeze_1(ast, options) {
1038     options = defaults(options, {
1039         make_seqs   : true,
1040         dead_code   : true,
1041         no_warnings : false,
1042         keep_comps  : true,
1043         unsafe      : false
1044     });
1045
1046     var w = ast_walker(), walk = w.walk, scope;
1047
1048     function negate(c) {
1049         var not_c = [ "unary-prefix", "!", c ];
1050         switch (c[0]) {
1051           case "unary-prefix":
1052             return c[1] == "!" && boolean_expr(c[2]) ? c[2] : not_c;
1053           case "seq":
1054             c = slice(c);
1055             c[c.length - 1] = negate(c[c.length - 1]);
1056             return c;
1057           case "conditional":
1058             return best_of(not_c, [ "conditional", c[1], negate(c[2]), negate(c[3]) ]);
1059           case "binary":
1060             var op = c[1], left = c[2], right = c[3];
1061             if (!options.keep_comps) switch (op) {
1062               case "<="  : return [ "binary", ">", left, right ];
1063               case "<"   : return [ "binary", ">=", left, right ];
1064               case ">="  : return [ "binary", "<", left, right ];
1065               case ">"   : return [ "binary", "<=", left, right ];
1066             }
1067             switch (op) {
1068               case "=="  : return [ "binary", "!=", left, right ];
1069               case "!="  : return [ "binary", "==", left, right ];
1070               case "===" : return [ "binary", "!==", left, right ];
1071               case "!==" : return [ "binary", "===", left, right ];
1072               case "&&"  : return best_of(not_c, [ "binary", "||", negate(left), negate(right) ]);
1073               case "||"  : return best_of(not_c, [ "binary", "&&", negate(left), negate(right) ]);
1074             }
1075             break;
1076         }
1077         return not_c;
1078     };
1079
1080     function make_conditional(c, t, e) {
1081         var make_real_conditional = function() {
1082             if (c[0] == "unary-prefix" && c[1] == "!") {
1083                 return e ? [ "conditional", c[2], e, t ] : [ "binary", "||", c[2], t ];
1084             } else {
1085                 return e ? best_of(
1086                     [ "conditional", c, t, e ],
1087                     [ "conditional", negate(c), e, t ]
1088                 ) : [ "binary", "&&", c, t ];
1089             }
1090         };
1091         // shortcut the conditional if the expression has a constant value
1092         return when_constant(c, function(ast, val){
1093             warn_unreachable(val ? e : t);
1094             return          (val ? t : e);
1095         }, make_real_conditional);
1096     };
1097
1098     function rmblock(block) {
1099         if (block != null && block[0] == "block" && block[1]) {
1100             if (block[1].length == 1)
1101                 block = block[1][0];
1102             else if (block[1].length == 0)
1103                 block = [ "block" ];
1104         }
1105         return block;
1106     };
1107
1108     function _lambda(name, args, body) {
1109         return [ this[0], name, args, tighten(body, "lambda") ];
1110     };
1111
1112     // this function does a few things:
1113     // 1. discard useless blocks
1114     // 2. join consecutive var declarations
1115     // 3. remove obviously dead code
1116     // 4. transform consecutive statements using the comma operator
1117     // 5. if block_type == "lambda" and it detects constructs like if(foo) return ... - rewrite like if (!foo) { ... }
1118     function tighten(statements, block_type) {
1119         statements = MAP(statements, walk);
1120
1121         statements = statements.reduce(function(a, stat){
1122             if (stat[0] == "block") {
1123                 if (stat[1]) {
1124                     a.push.apply(a, stat[1]);
1125                 }
1126             } else {
1127                 a.push(stat);
1128             }
1129             return a;
1130         }, []);
1131
1132         statements = (function(a, prev){
1133             statements.forEach(function(cur){
1134                 if (prev && ((cur[0] == "var" && prev[0] == "var") ||
1135                              (cur[0] == "const" && prev[0] == "const"))) {
1136                     prev[1] = prev[1].concat(cur[1]);
1137                 } else {
1138                     a.push(cur);
1139                     prev = cur;
1140                 }
1141             });
1142             return a;
1143         })([]);
1144
1145         if (options.dead_code) statements = (function(a, has_quit){
1146             statements.forEach(function(st){
1147                 if (has_quit) {
1148                     if (st[0] == "function" || st[0] == "defun") {
1149                         a.push(st);
1150                     }
1151                     else if (st[0] == "var" || st[0] == "const") {
1152                         if (!options.no_warnings)
1153                             warn("Variables declared in unreachable code");
1154                         st[1] = MAP(st[1], function(def){
1155                             if (def[1] && !options.no_warnings)
1156                                 warn_unreachable([ "assign", true, [ "name", def[0] ], def[1] ]);
1157                             return [ def[0] ];
1158                         });
1159                         a.push(st);
1160                     }
1161                     else if (!options.no_warnings)
1162                         warn_unreachable(st);
1163                 }
1164                 else {
1165                     a.push(st);
1166                     if (member(st[0], [ "return", "throw", "break", "continue" ]))
1167                         has_quit = true;
1168                 }
1169             });
1170             return a;
1171         })([]);
1172
1173         if (options.make_seqs) statements = (function(a, prev) {
1174             statements.forEach(function(cur){
1175                 if (prev && prev[0] == "stat" && cur[0] == "stat") {
1176                     prev[1] = [ "seq", prev[1], cur[1] ];
1177                 } else {
1178                     a.push(cur);
1179                     prev = cur;
1180                 }
1181             });
1182             if (a.length >= 2
1183                 && a[a.length-2][0] == "stat"
1184                 && (a[a.length-1][0] == "return" || a[a.length-1][0] == "throw")
1185                 && a[a.length-1][1])
1186             {
1187                 a.splice(a.length - 2, 2,
1188                          [ a[a.length-1][0],
1189                            [ "seq", a[a.length-2][1], a[a.length-1][1] ]]);
1190             }
1191             return a;
1192         })([]);
1193
1194         // this increases jQuery by 1K.  Probably not such a good idea after all..
1195         // part of this is done in prepare_ifs anyway.
1196         // if (block_type == "lambda") statements = (function(i, a, stat){
1197         //         while (i < statements.length) {
1198         //                 stat = statements[i++];
1199         //                 if (stat[0] == "if" && !stat[3]) {
1200         //                         if (stat[2][0] == "return" && stat[2][1] == null) {
1201         //                                 a.push(make_if(negate(stat[1]), [ "block", statements.slice(i) ]));
1202         //                                 break;
1203         //                         }
1204         //                         var last = last_stat(stat[2]);
1205         //                         if (last[0] == "return" && last[1] == null) {
1206         //                                 a.push(make_if(stat[1], [ "block", stat[2][1].slice(0, -1) ], [ "block", statements.slice(i) ]));
1207         //                                 break;
1208         //                         }
1209         //                 }
1210         //                 a.push(stat);
1211         //         }
1212         //         return a;
1213         // })(0, []);
1214
1215         return statements;
1216     };
1217
1218     function make_if(c, t, e) {
1219         return when_constant(c, function(ast, val){
1220             if (val) {
1221                 t = walk(t);
1222                 warn_unreachable(e);
1223                 return t || [ "block" ];
1224             } else {
1225                 e = walk(e);
1226                 warn_unreachable(t);
1227                 return e || [ "block" ];
1228             }
1229         }, function() {
1230             return make_real_if(c, t, e);
1231         });
1232     };
1233
1234     function abort_else(c, t, e) {
1235         var ret = [ [ "if", negate(c), e ] ];
1236         if (t[0] == "block") {
1237             if (t[1]) ret = ret.concat(t[1]);
1238         } else {
1239             ret.push(t);
1240         }
1241         return walk([ "block", ret ]);
1242     };
1243
1244     function make_real_if(c, t, e) {
1245         c = walk(c);
1246         t = walk(t);
1247         e = walk(e);
1248
1249         if (empty(e) && empty(t))
1250             return [ "stat", c ];
1251
1252         if (empty(t)) {
1253             c = negate(c);
1254             t = e;
1255             e = null;
1256         } else if (empty(e)) {
1257             e = null;
1258         } else {
1259             // if we have both else and then, maybe it makes sense to switch them?
1260             (function(){
1261                 var a = gen_code(c);
1262                 var n = negate(c);
1263                 var b = gen_code(n);
1264                 if (b.length < a.length) {
1265                     var tmp = t;
1266                     t = e;
1267                     e = tmp;
1268                     c = n;
1269                 }
1270             })();
1271         }
1272         var ret = [ "if", c, t, e ];
1273         if (t[0] == "if" && empty(t[3]) && empty(e)) {
1274             ret = best_of(ret, walk([ "if", [ "binary", "&&", c, t[1] ], t[2] ]));
1275         }
1276         else if (t[0] == "stat") {
1277             if (e) {
1278                 if (e[0] == "stat")
1279                     ret = best_of(ret, [ "stat", make_conditional(c, t[1], e[1]) ]);
1280                 else if (aborts(e))
1281                     ret = abort_else(c, t, e);
1282             }
1283             else {
1284                 ret = best_of(ret, [ "stat", make_conditional(c, t[1]) ]);
1285             }
1286         }
1287         else if (e && t[0] == e[0] && (t[0] == "return" || t[0] == "throw") && t[1] && e[1]) {
1288             ret = best_of(ret, [ t[0], make_conditional(c, t[1], e[1] ) ]);
1289         }
1290         else if (e && aborts(t)) {
1291             ret = [ [ "if", c, t ] ];
1292             if (e[0] == "block") {
1293                 if (e[1]) ret = ret.concat(e[1]);
1294             }
1295             else {
1296                 ret.push(e);
1297             }
1298             ret = walk([ "block", ret ]);
1299         }
1300         else if (t && aborts(e)) {
1301             ret = abort_else(c, t, e);
1302         }
1303         return ret;
1304     };
1305
1306     function _do_while(cond, body) {
1307         return when_constant(cond, function(cond, val){
1308             if (!val) {
1309                 warn_unreachable(body);
1310                 return [ "block" ];
1311             } else {
1312                 return [ "for", null, null, null, walk(body) ];
1313             }
1314         });
1315     };
1316
1317     return w.with_walkers({
1318         "sub": function(expr, subscript) {
1319             if (subscript[0] == "string") {
1320                 var name = subscript[1];
1321                 if (is_identifier(name))
1322                     return [ "dot", walk(expr), name ];
1323                 else if (/^[1-9][0-9]*$/.test(name) || name === "0")
1324                     return [ "sub", walk(expr), [ "num", parseInt(name, 10) ] ];
1325             }
1326         },
1327         "if": make_if,
1328         "toplevel": function(body) {
1329             return [ "toplevel", tighten(body) ];
1330         },
1331         "switch": function(expr, body) {
1332             var last = body.length - 1;
1333             return [ "switch", walk(expr), MAP(body, function(branch, i){
1334                 var block = tighten(branch[1]);
1335                 if (i == last && block.length > 0) {
1336                     var node = block[block.length - 1];
1337                     if (node[0] == "break" && !node[1])
1338                         block.pop();
1339                 }
1340                 return [ branch[0] ? walk(branch[0]) : null, block ];
1341             }) ];
1342         },
1343         "function": _lambda,
1344         "defun": _lambda,
1345         "block": function(body) {
1346             if (body) return rmblock([ "block", tighten(body) ]);
1347         },
1348         "binary": function(op, left, right) {
1349             return when_constant([ "binary", op, walk(left), walk(right) ], function yes(c){
1350                 return best_of(walk(c), this);
1351             }, function no() {
1352                 return function(){
1353                     if(op != "==" && op != "!=") return;
1354                     var l = walk(left), r = walk(right);
1355                     if(l && l[0] == "unary-prefix" && l[1] == "!" && l[2][0] == "num")
1356                         left = ['num', +!l[2][1]];
1357                     else if (r && r[0] == "unary-prefix" && r[1] == "!" && r[2][0] == "num")
1358                         right = ['num', +!r[2][1]];
1359                     return ["binary", op, left, right];
1360                 }() || this;
1361             });
1362         },
1363         "conditional": function(c, t, e) {
1364             return make_conditional(walk(c), walk(t), walk(e));
1365         },
1366         "try": function(t, c, f) {
1367             return [
1368                 "try",
1369                 tighten(t),
1370                 c != null ? [ c[0], tighten(c[1]) ] : null,
1371                 f != null ? tighten(f) : null
1372             ];
1373         },
1374         "unary-prefix": function(op, expr) {
1375             expr = walk(expr);
1376             var ret = [ "unary-prefix", op, expr ];
1377             if (op == "!")
1378                 ret = best_of(ret, negate(expr));
1379             return when_constant(ret, function(ast, val){
1380                 return walk(ast); // it's either true or false, so minifies to !0 or !1
1381             }, function() { return ret });
1382         },
1383         "name": function(name) {
1384             switch (name) {
1385               case "true": return [ "unary-prefix", "!", [ "num", 0 ]];
1386               case "false": return [ "unary-prefix", "!", [ "num", 1 ]];
1387             }
1388         },
1389         "while": _do_while,
1390         "assign": function(op, lvalue, rvalue) {
1391             lvalue = walk(lvalue);
1392             rvalue = walk(rvalue);
1393             var okOps = [ '+', '-', '/', '*', '%', '>>', '<<', '>>>', '|', '^', '&' ];
1394             if (op === true && lvalue[0] === "name" && rvalue[0] === "binary" &&
1395                 ~okOps.indexOf(rvalue[1]) && rvalue[2][0] === "name" &&
1396                 rvalue[2][1] === lvalue[1]) {
1397                 return [ this[0], rvalue[1], lvalue, rvalue[3] ]
1398             }
1399             return [ this[0], op, lvalue, rvalue ];
1400         },
1401         "call": function(expr, args) {
1402             expr = walk(expr);
1403             if (options.unsafe && expr[0] == "dot" && expr[1][0] == "string" && expr[2] == "toString") {
1404                 return expr[1];
1405             }
1406             return [ this[0], expr,  MAP(args, walk) ];
1407         },
1408         "num": function (num) {
1409             if (!isFinite(num))
1410                 return [ "binary", "/", num === 1 / 0
1411                          ? [ "num", 1 ] : num === -1 / 0
1412                          ? [ "unary-prefix", "-", [ "num", 1 ] ]
1413                          : [ "num", 0 ], [ "num", 0 ] ];
1414
1415             return [ this[0], num ];
1416         }
1417     }, function() {
1418         return walk(prepare_ifs(walk(prepare_ifs(ast))));
1419     });
1420 };
1421
1422 function squeeze_2(ast, options) {
1423     var w = ast_walker(), walk = w.walk, scope;
1424     function with_scope(s, cont) {
1425         var save = scope, ret;
1426         scope = s;
1427         ret = cont();
1428         scope = save;
1429         return ret;
1430     };
1431     function lambda(name, args, body) {
1432         return [ this[0], name, args, with_scope(body.scope, curry(MAP, body, walk)) ];
1433     };
1434     return w.with_walkers({
1435         "directive": function(dir) {
1436             if (scope.active_directive(dir))
1437                 return [ "block" ];
1438             scope.directives.push(dir);
1439         },
1440         "toplevel": function(body) {
1441             return [ this[0], with_scope(this.scope, curry(MAP, body, walk)) ];
1442         },
1443         "function": lambda,
1444         "defun": lambda
1445     }, function(){
1446         return walk(ast_add_scope(ast));
1447     });
1448 };
1449
1450 /* -----[ re-generate code from the AST ]----- */
1451
1452 var DOT_CALL_NO_PARENS = jsp.array_to_hash([
1453     "name",
1454     "array",
1455     "object",
1456     "string",
1457     "dot",
1458     "sub",
1459     "call",
1460     "regexp",
1461     "defun"
1462 ]);
1463
1464 function make_string(str, ascii_only) {
1465     var dq = 0, sq = 0;
1466     str = str.replace(/[\\\b\f\n\r\t\x22\x27\u2028\u2029\0]/g, function(s){
1467         switch (s) {
1468           case "\\": return "\\\\";
1469           case "\b": return "\\b";
1470           case "\f": return "\\f";
1471           case "\n": return "\\n";
1472           case "\r": return "\\r";
1473           case "\u2028": return "\\u2028";
1474           case "\u2029": return "\\u2029";
1475           case '"': ++dq; return '"';
1476           case "'": ++sq; return "'";
1477           case "\0": return "\\0";
1478         }
1479         return s;
1480     });
1481     if (ascii_only) str = to_ascii(str);
1482     if (dq > sq) return "'" + str.replace(/\x27/g, "\\'") + "'";
1483     else return '"' + str.replace(/\x22/g, '\\"') + '"';
1484 };
1485
1486 function to_ascii(str) {
1487     return str.replace(/[\u0080-\uffff]/g, function(ch) {
1488         var code = ch.charCodeAt(0).toString(16);
1489         while (code.length < 4) code = "0" + code;
1490         return "\\u" + code;
1491     });
1492 };
1493
1494 var SPLICE_NEEDS_BRACKETS = jsp.array_to_hash([ "if", "while", "do", "for", "for-in", "with" ]);
1495
1496 function gen_code(ast, options) {
1497     options = defaults(options, {
1498         indent_start : 0,
1499         indent_level : 4,
1500         quote_keys   : false,
1501         space_colon  : false,
1502         beautify     : false,
1503         ascii_only   : false,
1504         inline_script: false
1505     });
1506     var beautify = !!options.beautify;
1507     var indentation = 0,
1508     newline = beautify ? "\n" : "",
1509     space = beautify ? " " : "";
1510
1511     function encode_string(str) {
1512         var ret = make_string(str, options.ascii_only);
1513         if (options.inline_script)
1514             ret = ret.replace(/<\x2fscript([>\/\t\n\f\r ])/gi, "<\\/script$1");
1515         return ret;
1516     };
1517
1518     function make_name(name) {
1519         name = name.toString();
1520         if (options.ascii_only)
1521             name = to_ascii(name);
1522         return name;
1523     };
1524
1525     function indent(line) {
1526         if (line == null)
1527             line = "";
1528         if (beautify)
1529             line = repeat_string(" ", options.indent_start + indentation * options.indent_level) + line;
1530         return line;
1531     };
1532
1533     function with_indent(cont, incr) {
1534         if (incr == null) incr = 1;
1535         indentation += incr;
1536         try { return cont.apply(null, slice(arguments, 1)); }
1537         finally { indentation -= incr; }
1538     };
1539
1540     function last_char(str) {
1541         str = str.toString();
1542         return str.charAt(str.length - 1);
1543     };
1544
1545     function first_char(str) {
1546         return str.toString().charAt(0);
1547     };
1548
1549     function add_spaces(a) {
1550         if (beautify)
1551             return a.join(" ");
1552         var b = [];
1553         for (var i = 0; i < a.length; ++i) {
1554             var next = a[i + 1];
1555             b.push(a[i]);
1556             if (next &&
1557                 ((is_identifier_char(last_char(a[i])) && (is_identifier_char(first_char(next))
1558                                                           || first_char(next) == "\\")) ||
1559                  (/[\+\-]$/.test(a[i].toString()) && /^[\+\-]/.test(next.toString()) ||
1560                  last_char(a[i]) == "/" && first_char(next) == "/"))) {
1561                 b.push(" ");
1562             }
1563         }
1564         return b.join("");
1565     };
1566
1567     function add_commas(a) {
1568         return a.join("," + space);
1569     };
1570
1571     function parenthesize(expr) {
1572         var gen = make(expr);
1573         for (var i = 1; i < arguments.length; ++i) {
1574             var el = arguments[i];
1575             if ((el instanceof Function && el(expr)) || expr[0] == el)
1576                 return "(" + gen + ")";
1577         }
1578         return gen;
1579     };
1580
1581     function best_of(a) {
1582         if (a.length == 1) {
1583             return a[0];
1584         }
1585         if (a.length == 2) {
1586             var b = a[1];
1587             a = a[0];
1588             return a.length <= b.length ? a : b;
1589         }
1590         return best_of([ a[0], best_of(a.slice(1)) ]);
1591     };
1592
1593     function needs_parens(expr) {
1594         if (expr[0] == "function" || expr[0] == "object") {
1595             // dot/call on a literal function requires the
1596             // function literal itself to be parenthesized
1597             // only if it's the first "thing" in a
1598             // statement.  This means that the parent is
1599             // "stat", but it could also be a "seq" and
1600             // we're the first in this "seq" and the
1601             // parent is "stat", and so on.  Messy stuff,
1602             // but it worths the trouble.
1603             var a = slice(w.stack()), self = a.pop(), p = a.pop();
1604             while (p) {
1605                 if (p[0] == "stat") return true;
1606                 if (((p[0] == "seq" || p[0] == "call" || p[0] == "dot" || p[0] == "sub" || p[0] == "conditional") && p[1] === self) ||
1607                     ((p[0] == "binary" || p[0] == "assign" || p[0] == "unary-postfix") && p[2] === self)) {
1608                     self = p;
1609                     p = a.pop();
1610                 } else {
1611                     return false;
1612                 }
1613             }
1614         }
1615         return !HOP(DOT_CALL_NO_PARENS, expr[0]);
1616     };
1617
1618     function make_num(num) {
1619         var str = num.toString(10), a = [ str.replace(/^0\./, ".").replace('e+', 'e') ], m;
1620         if (Math.floor(num) === num) {
1621             if (num >= 0) {
1622                 a.push("0x" + num.toString(16).toLowerCase(), // probably pointless
1623                        "0" + num.toString(8)); // same.
1624             } else {
1625                 a.push("-0x" + (-num).toString(16).toLowerCase(), // probably pointless
1626                        "-0" + (-num).toString(8)); // same.
1627             }
1628             if ((m = /^(.*?)(0+)$/.exec(num))) {
1629                 a.push(m[1] + "e" + m[2].length);
1630             }
1631         } else if ((m = /^0?\.(0+)(.*)$/.exec(num))) {
1632             a.push(m[2] + "e-" + (m[1].length + m[2].length),
1633                    str.substr(str.indexOf(".")));
1634         }
1635         return best_of(a);
1636     };
1637
1638     var w = ast_walker();
1639     var make = w.walk;
1640     return w.with_walkers({
1641         "string": encode_string,
1642         "num": make_num,
1643         "name": make_name,
1644         "debugger": function(){ return "debugger;" },
1645         "toplevel": function(statements) {
1646             return make_block_statements(statements)
1647                 .join(newline + newline);
1648         },
1649         "splice": function(statements) {
1650             var parent = w.parent();
1651             if (HOP(SPLICE_NEEDS_BRACKETS, parent)) {
1652                 // we need block brackets in this case
1653                 return make_block.apply(this, arguments);
1654             } else {
1655                 return MAP(make_block_statements(statements, true),
1656                            function(line, i) {
1657                                // the first line is already indented
1658                                return i > 0 ? indent(line) : line;
1659                            }).join(newline);
1660             }
1661         },
1662         "block": make_block,
1663         "var": function(defs) {
1664             return "var " + add_commas(MAP(defs, make_1vardef)) + ";";
1665         },
1666         "const": function(defs) {
1667             return "const " + add_commas(MAP(defs, make_1vardef)) + ";";
1668         },
1669         "try": function(tr, ca, fi) {
1670             var out = [ "try", make_block(tr) ];
1671             if (ca) out.push("catch", "(" + ca[0] + ")", make_block(ca[1]));
1672             if (fi) out.push("finally", make_block(fi));
1673             return add_spaces(out);
1674         },
1675         "throw": function(expr) {
1676             return add_spaces([ "throw", make(expr) ]) + ";";
1677         },
1678         "new": function(ctor, args) {
1679             args = args.length > 0 ? "(" + add_commas(MAP(args, function(expr){
1680                 return parenthesize(expr, "seq");
1681             })) + ")" : "";
1682             return add_spaces([ "new", parenthesize(ctor, "seq", "binary", "conditional", "assign", function(expr){
1683                 var w = ast_walker(), has_call = {};
1684                 try {
1685                     w.with_walkers({
1686                         "call": function() { throw has_call },
1687                         "function": function() { return this }
1688                     }, function(){
1689                         w.walk(expr);
1690                     });
1691                 } catch(ex) {
1692                     if (ex === has_call)
1693                         return true;
1694                     throw ex;
1695                 }
1696             }) + args ]);
1697         },
1698         "switch": function(expr, body) {
1699             return add_spaces([ "switch", "(" + make(expr) + ")", make_switch_block(body) ]);
1700         },
1701         "break": function(label) {
1702             var out = "break";
1703             if (label != null)
1704                 out += " " + make_name(label);
1705             return out + ";";
1706         },
1707         "continue": function(label) {
1708             var out = "continue";
1709             if (label != null)
1710                 out += " " + make_name(label);
1711             return out + ";";
1712         },
1713         "conditional": function(co, th, el) {
1714             return add_spaces([ parenthesize(co, "assign", "seq", "conditional"), "?",
1715                                 parenthesize(th, "seq"), ":",
1716                                 parenthesize(el, "seq") ]);
1717         },
1718         "assign": function(op, lvalue, rvalue) {
1719             if (op && op !== true) op += "=";
1720             else op = "=";
1721             return add_spaces([ make(lvalue), op, parenthesize(rvalue, "seq") ]);
1722         },
1723         "dot": function(expr) {
1724             var out = make(expr), i = 1;
1725             if (expr[0] == "num") {
1726                 if (!/[a-f.]/i.test(out))
1727                     out += ".";
1728             } else if (expr[0] != "function" && needs_parens(expr))
1729                 out = "(" + out + ")";
1730             while (i < arguments.length)
1731                 out += "." + make_name(arguments[i++]);
1732             return out;
1733         },
1734         "call": function(func, args) {
1735             var f = make(func);
1736             if (f.charAt(0) != "(" && needs_parens(func))
1737                 f = "(" + f + ")";
1738             return f + "(" + add_commas(MAP(args, function(expr){
1739                 return parenthesize(expr, "seq");
1740             })) + ")";
1741         },
1742         "function": make_function,
1743         "defun": make_function,
1744         "if": function(co, th, el) {
1745             var out = [ "if", "(" + make(co) + ")", el ? make_then(th) : make(th) ];
1746             if (el) {
1747                 out.push("else", make(el));
1748             }
1749             return add_spaces(out);
1750         },
1751         "for": function(init, cond, step, block) {
1752             var out = [ "for" ];
1753             init = (init != null ? make(init) : "").replace(/;*\s*$/, ";" + space);
1754             cond = (cond != null ? make(cond) : "").replace(/;*\s*$/, ";" + space);
1755             step = (step != null ? make(step) : "").replace(/;*\s*$/, "");
1756             var args = init + cond + step;
1757             if (args == "; ; ") args = ";;";
1758             out.push("(" + args + ")", make(block));
1759             return add_spaces(out);
1760         },
1761         "for-in": function(vvar, key, hash, block) {
1762             return add_spaces([ "for", "(" +
1763                                 (vvar ? make(vvar).replace(/;+$/, "") : make(key)),
1764                                 "in",
1765                                 make(hash) + ")", make(block) ]);
1766         },
1767         "while": function(condition, block) {
1768             return add_spaces([ "while", "(" + make(condition) + ")", make(block) ]);
1769         },
1770         "do": function(condition, block) {
1771             return add_spaces([ "do", make(block), "while", "(" + make(condition) + ")" ]) + ";";
1772         },
1773         "return": function(expr) {
1774             var out = [ "return" ];
1775             if (expr != null) out.push(make(expr));
1776             return add_spaces(out) + ";";
1777         },
1778         "binary": function(operator, lvalue, rvalue) {
1779             var left = make(lvalue), right = make(rvalue);
1780             // XXX: I'm pretty sure other cases will bite here.
1781             //      we need to be smarter.
1782             //      adding parens all the time is the safest bet.
1783             if (member(lvalue[0], [ "assign", "conditional", "seq" ]) ||
1784                 lvalue[0] == "binary" && PRECEDENCE[operator] > PRECEDENCE[lvalue[1]] ||
1785                 lvalue[0] == "function" && needs_parens(this)) {
1786                 left = "(" + left + ")";
1787             }
1788             if (member(rvalue[0], [ "assign", "conditional", "seq" ]) ||
1789                 rvalue[0] == "binary" && PRECEDENCE[operator] >= PRECEDENCE[rvalue[1]] &&
1790                 !(rvalue[1] == operator && member(operator, [ "&&", "||", "*" ]))) {
1791                 right = "(" + right + ")";
1792             }
1793             else if (!beautify && options.inline_script && (operator == "<" || operator == "<<")
1794                      && rvalue[0] == "regexp" && /^script/i.test(rvalue[1])) {
1795                 right = " " + right;
1796             }
1797             return add_spaces([ left, operator, right ]);
1798         },
1799         "unary-prefix": function(operator, expr) {
1800             var val = make(expr);
1801             if (!(expr[0] == "num" || (expr[0] == "unary-prefix" && !HOP(OPERATORS, operator + expr[1])) || !needs_parens(expr)))
1802                 val = "(" + val + ")";
1803             return operator + (jsp.is_alphanumeric_char(operator.charAt(0)) ? " " : "") + val;
1804         },
1805         "unary-postfix": function(operator, expr) {
1806             var val = make(expr);
1807             if (!(expr[0] == "num" || (expr[0] == "unary-postfix" && !HOP(OPERATORS, operator + expr[1])) || !needs_parens(expr)))
1808                 val = "(" + val + ")";
1809             return val + operator;
1810         },
1811         "sub": function(expr, subscript) {
1812             var hash = make(expr);
1813             if (needs_parens(expr))
1814                 hash = "(" + hash + ")";
1815             return hash + "[" + make(subscript) + "]";
1816         },
1817         "object": function(props) {
1818             var obj_needs_parens = needs_parens(this);
1819             if (props.length == 0)
1820                 return obj_needs_parens ? "({})" : "{}";
1821             var out = "{" + newline + with_indent(function(){
1822                 return MAP(props, function(p){
1823                     if (p.length == 3) {
1824                         // getter/setter.  The name is in p[0], the arg.list in p[1][2], the
1825                         // body in p[1][3] and type ("get" / "set") in p[2].
1826                         return indent(make_function(p[0], p[1][2], p[1][3], p[2], true));
1827                     }
1828                     var key = p[0], val = parenthesize(p[1], "seq");
1829                     if (options.quote_keys) {
1830                         key = encode_string(key);
1831                     } else if ((typeof key == "number" || !beautify && +key + "" == key)
1832                                && parseFloat(key) >= 0) {
1833                         key = make_num(+key);
1834                     } else if (!is_identifier(key)) {
1835                         key = encode_string(key);
1836                     }
1837                     return indent(add_spaces(beautify && options.space_colon
1838                                              ? [ key, ":", val ]
1839                                              : [ key + ":", val ]));
1840                 }).join("," + newline);
1841             }) + newline + indent("}");
1842             return obj_needs_parens ? "(" + out + ")" : out;
1843         },
1844         "regexp": function(rx, mods) {
1845             if (options.ascii_only) rx = to_ascii(rx);
1846             return "/" + rx + "/" + mods;
1847         },
1848         "array": function(elements) {
1849             if (elements.length == 0) return "[]";
1850             return add_spaces([ "[", add_commas(MAP(elements, function(el, i){
1851                 if (!beautify && el[0] == "atom" && el[1] == "undefined") return i === elements.length - 1 ? "," : "";
1852                 return parenthesize(el, "seq");
1853             })), "]" ]);
1854         },
1855         "stat": function(stmt) {
1856             return stmt != null
1857                 ? make(stmt).replace(/;*\s*$/, ";")
1858                 : ";";
1859         },
1860         "seq": function() {
1861             return add_commas(MAP(slice(arguments), make));
1862         },
1863         "label": function(name, block) {
1864             return add_spaces([ make_name(name), ":", make(block) ]);
1865         },
1866         "with": function(expr, block) {
1867             return add_spaces([ "with", "(" + make(expr) + ")", make(block) ]);
1868         },
1869         "atom": function(name) {
1870             return make_name(name);
1871         },
1872         "directive": function(dir) {
1873             return make_string(dir) + ";";
1874         }
1875     }, function(){ return make(ast) });
1876
1877     // The squeezer replaces "block"-s that contain only a single
1878     // statement with the statement itself; technically, the AST
1879     // is correct, but this can create problems when we output an
1880     // IF having an ELSE clause where the THEN clause ends in an
1881     // IF *without* an ELSE block (then the outer ELSE would refer
1882     // to the inner IF).  This function checks for this case and
1883     // adds the block brackets if needed.
1884     function make_then(th) {
1885         if (th == null) return ";";
1886         if (th[0] == "do") {
1887             // https://github.com/mishoo/UglifyJS/issues/#issue/57
1888             // IE croaks with "syntax error" on code like this:
1889             //     if (foo) do ... while(cond); else ...
1890             // we need block brackets around do/while
1891             return make_block([ th ]);
1892         }
1893         var b = th;
1894         while (true) {
1895             var type = b[0];
1896             if (type == "if") {
1897                 if (!b[3])
1898                     // no else, we must add the block
1899                     return make([ "block", [ th ]]);
1900                 b = b[3];
1901             }
1902             else if (type == "while" || type == "do") b = b[2];
1903             else if (type == "for" || type == "for-in") b = b[4];
1904             else break;
1905         }
1906         return make(th);
1907     };
1908
1909     function make_function(name, args, body, keyword, no_parens) {
1910         var out = keyword || "function";
1911         if (name) {
1912             out += " " + make_name(name);
1913         }
1914         out += "(" + add_commas(MAP(args, make_name)) + ")";
1915         out = add_spaces([ out, make_block(body) ]);
1916         return (!no_parens && needs_parens(this)) ? "(" + out + ")" : out;
1917     };
1918
1919     function must_has_semicolon(node) {
1920         switch (node[0]) {
1921           case "with":
1922           case "while":
1923             return empty(node[2]) || must_has_semicolon(node[2]);
1924           case "for":
1925           case "for-in":
1926             return empty(node[4]) || must_has_semicolon(node[4]);
1927           case "if":
1928             if (empty(node[2]) && !node[3]) return true; // `if' with empty `then' and no `else'
1929             if (node[3]) {
1930                 if (empty(node[3])) return true; // `else' present but empty
1931                 return must_has_semicolon(node[3]); // dive into the `else' branch
1932             }
1933             return must_has_semicolon(node[2]); // dive into the `then' branch
1934           case "directive":
1935             return true;
1936         }
1937     };
1938
1939     function make_block_statements(statements, noindent) {
1940         for (var a = [], last = statements.length - 1, i = 0; i <= last; ++i) {
1941             var stat = statements[i];
1942             var code = make(stat);
1943             if (code != ";") {
1944                 if (!beautify && i == last && !must_has_semicolon(stat)) {
1945                     code = code.replace(/;+\s*$/, "");
1946                 }
1947                 a.push(code);
1948             }
1949         }
1950         return noindent ? a : MAP(a, indent);
1951     };
1952
1953     function make_switch_block(body) {
1954         var n = body.length;
1955         if (n == 0) return "{}";
1956         return "{" + newline + MAP(body, function(branch, i){
1957             var has_body = branch[1].length > 0, code = with_indent(function(){
1958                 return indent(branch[0]
1959                               ? add_spaces([ "case", make(branch[0]) + ":" ])
1960                               : "default:");
1961             }, 0.5) + (has_body ? newline + with_indent(function(){
1962                 return make_block_statements(branch[1]).join(newline);
1963             }) : "");
1964             if (!beautify && has_body && i < n - 1)
1965                 code += ";";
1966             return code;
1967         }).join(newline) + newline + indent("}");
1968     };
1969
1970     function make_block(statements) {
1971         if (!statements) return ";";
1972         if (statements.length == 0) return "{}";
1973         return "{" + newline + with_indent(function(){
1974             return make_block_statements(statements).join(newline);
1975         }) + newline + indent("}");
1976     };
1977
1978     function make_1vardef(def) {
1979         var name = def[0], val = def[1];
1980         if (val != null)
1981             name = add_spaces([ make_name(name), "=", parenthesize(val, "seq") ]);
1982         return name;
1983     };
1984
1985 };
1986
1987 function split_lines(code, max_line_length) {
1988     var splits = [ 0 ];
1989     jsp.parse(function(){
1990         var next_token = jsp.tokenizer(code);
1991         var last_split = 0;
1992         var prev_token;
1993         function current_length(tok) {
1994             return tok.pos - last_split;
1995         };
1996         function split_here(tok) {
1997             last_split = tok.pos;
1998             splits.push(last_split);
1999         };
2000         function custom(){
2001             var tok = next_token.apply(this, arguments);
2002             out: {
2003                 if (prev_token) {
2004                     if (prev_token.type == "keyword") break out;
2005                 }
2006                 if (current_length(tok) > max_line_length) {
2007                     switch (tok.type) {
2008                       case "keyword":
2009                       case "atom":
2010                       case "name":
2011                       case "punc":
2012                         split_here(tok);
2013                         break out;
2014                     }
2015                 }
2016             }
2017             prev_token = tok;
2018             return tok;
2019         };
2020         custom.context = function() {
2021             return next_token.context.apply(this, arguments);
2022         };
2023         return custom;
2024     }());
2025     return splits.map(function(pos, i){
2026         return code.substring(pos, splits[i + 1] || code.length);
2027     }).join("\n");
2028 };
2029
2030 /* -----[ Utilities ]----- */
2031
2032 function repeat_string(str, i) {
2033     if (i <= 0) return "";
2034     if (i == 1) return str;
2035     var d = repeat_string(str, i >> 1);
2036     d += d;
2037     if (i & 1) d += str;
2038     return d;
2039 };
2040
2041 function defaults(args, defs) {
2042     var ret = {};
2043     if (args === true)
2044         args = {};
2045     for (var i in defs) if (HOP(defs, i)) {
2046         ret[i] = (args && HOP(args, i)) ? args[i] : defs[i];
2047     }
2048     return ret;
2049 };
2050
2051 function is_identifier(name) {
2052     return /^[a-z_$][a-z0-9_$]*$/i.test(name)
2053         && name != "this"
2054         && !HOP(jsp.KEYWORDS_ATOM, name)
2055         && !HOP(jsp.RESERVED_WORDS, name)
2056         && !HOP(jsp.KEYWORDS, name);
2057 };
2058
2059 function HOP(obj, prop) {
2060     return Object.prototype.hasOwnProperty.call(obj, prop);
2061 };
2062
2063 // some utilities
2064
2065 var MAP;
2066
2067 (function(){
2068     MAP = function(a, f, o) {
2069         var ret = [], top = [], i;
2070         function doit() {
2071             var val = f.call(o, a[i], i);
2072             if (val instanceof AtTop) {
2073                 val = val.v;
2074                 if (val instanceof Splice) {
2075                     top.push.apply(top, val.v);
2076                 } else {
2077                     top.push(val);
2078                 }
2079             }
2080             else if (val != skip) {
2081                 if (val instanceof Splice) {
2082                     ret.push.apply(ret, val.v);
2083                 } else {
2084                     ret.push(val);
2085                 }
2086             }
2087         };
2088         if (a instanceof Array) for (i = 0; i < a.length; ++i) doit();
2089         else for (i in a) if (HOP(a, i)) doit();
2090         return top.concat(ret);
2091     };
2092     MAP.at_top = function(val) { return new AtTop(val) };
2093     MAP.splice = function(val) { return new Splice(val) };
2094     var skip = MAP.skip = {};
2095     function AtTop(val) { this.v = val };
2096     function Splice(val) { this.v = val };
2097 })();
2098
2099 /* -----[ Exports ]----- */
2100
2101 exports.ast_walker = ast_walker;
2102 exports.ast_mangle = ast_mangle;
2103 exports.ast_squeeze = ast_squeeze;
2104 exports.ast_lift_variables = ast_lift_variables;
2105 exports.gen_code = gen_code;
2106 exports.ast_add_scope = ast_add_scope;
2107 exports.set_logger = function(logger) { warn = logger };
2108 exports.make_string = make_string;
2109 exports.split_lines = split_lines;
2110 exports.MAP = MAP;
2111
2112 // keep this last!
2113 exports.ast_squeeze_more = require("./squeeze-more").ast_squeeze_more;
2114
2115 // Local variables:
2116 // js-indent-level: 4
2117 // End: