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