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