1 /***********************************************************************
3 A JavaScript tokenizer / parser / beautifier / compressor.
5 This version is suitable for Node.js. With minimal changes (the
6 exports stuff) it should work on any JS platform.
8 This file implements some AST processors. They work on data built
13 - ast_mangle(ast, options) -- mangles the variable/function names
14 in the AST. Returns an AST.
16 - ast_squeeze(ast) -- employs various optimizations to make the
17 final generated code even smaller. Returns an AST.
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.
23 -------------------------------- (C) ---------------------------------
26 <mihai.bazon@gmail.com>
27 http://mihai.bazon.net/blog
29 Distributed under the BSD license:
31 Copyright 2010 (c) Mihai Bazon <mihai.bazon@gmail.com>
33 Redistribution and use in source and binary forms, with or without
34 modification, are permitted provided that the following conditions
37 * Redistributions of source code must retain the above
38 copyright notice, this list of conditions and the following
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.
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
59 ***********************************************************************/
61 var jsp = require("./parse-js"),
64 PRECEDENCE = jsp.PRECEDENCE,
65 OPERATORS = jsp.OPERATORS;
67 /* -----[ helper for AST traversal ]----- */
69 function ast_walker() {
70 function _vardefs(defs) {
71 return [ this[0], MAP(defs, function(def){
78 function _block(statements) {
79 var out = [ this[0] ];
80 if (statements != null)
81 out.push(MAP(statements, walk));
85 "string": function(str) {
86 return [ this[0], str ];
88 "num": function(num) {
89 return [ this[0], num ];
91 "name": function(name) {
92 return [ this[0], name ];
94 "toplevel": function(statements) {
95 return [ this[0], MAP(statements, walk) ];
101 "try": function(t, c, f) {
105 c != null ? [ c[0], MAP(c[1], walk) ] : null,
106 f != null ? MAP(f, walk) : null
109 "throw": function(expr) {
110 return [ this[0], walk(expr) ];
112 "new": function(ctor, args) {
113 return [ this[0], walk(ctor), MAP(args, walk) ];
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) ];
121 "break": function(label) {
122 return [ this[0], label ];
124 "continue": function(label) {
125 return [ this[0], label ];
127 "conditional": function(cond, t, e) {
128 return [ this[0], walk(cond), walk(t), walk(e) ];
130 "assign": function(op, lvalue, rvalue) {
131 return [ this[0], op, walk(lvalue), walk(rvalue) ];
133 "dot": function(expr) {
134 return [ this[0], walk(expr) ].concat(slice(arguments, 1));
136 "call": function(expr, args) {
137 return [ this[0], walk(expr), MAP(args, walk) ];
139 "function": function(name, args, body) {
140 return [ this[0], name, args.slice(), MAP(body, walk) ];
142 "debugger": function() {
145 "defun": function(name, args, body) {
146 return [ this[0], name, args.slice(), MAP(body, walk) ];
148 "if": function(conditional, t, e) {
149 return [ this[0], walk(conditional), walk(t), walk(e) ];
151 "for": function(init, cond, step, block) {
152 return [ this[0], walk(init), walk(cond), walk(step), walk(block) ];
154 "for-in": function(vvar, key, hash, block) {
155 return [ this[0], walk(vvar), walk(key), walk(hash), walk(block) ];
157 "while": function(cond, block) {
158 return [ this[0], walk(cond), walk(block) ];
160 "do": function(cond, block) {
161 return [ this[0], walk(cond), walk(block) ];
163 "return": function(expr) {
164 return [ this[0], walk(expr) ];
166 "binary": function(op, left, right) {
167 return [ this[0], op, walk(left), walk(right) ];
169 "unary-prefix": function(op, expr) {
170 return [ this[0], op, walk(expr) ];
172 "unary-postfix": function(op, expr) {
173 return [ this[0], op, walk(expr) ];
175 "sub": function(expr, subscript) {
176 return [ this[0], walk(expr), walk(subscript) ];
178 "object": function(props) {
179 return [ this[0], MAP(props, function(p){
181 ? [ p[0], walk(p[1]) ]
182 : [ p[0], walk(p[1]), p[2] ]; // get/set-ter
185 "regexp": function(rx, mods) {
186 return [ this[0], rx, mods ];
188 "array": function(elements) {
189 return [ this[0], MAP(elements, walk) ];
191 "stat": function(stat) {
192 return [ this[0], walk(stat) ];
195 return [ this[0] ].concat(MAP(slice(arguments), walk));
197 "label": function(name, block) {
198 return [ this[0], name, walk(block) ];
200 "with": function(expr, block) {
201 return [ this[0], walk(expr), walk(block) ];
203 "atom": function(name) {
204 return [ this[0], name ];
216 var gen = user[type];
218 var ret = gen.apply(ast, ast.slice(1));
223 return gen.apply(ast, ast.slice(1));
234 return walkers[ast[0]].apply(ast, ast.slice(1));
240 function with_walkers(walkers, cont){
242 for (i in walkers) if (HOP(walkers, i)) {
244 user[i] = walkers[i];
247 for (i in save) if (HOP(save, i)) {
248 if (!save[i]) delete user[i];
249 else user[i] = save[i];
257 with_walkers: with_walkers,
259 return stack[stack.length - 2]; // last one is current node
267 /* -----[ Scope and mangling ]----- */
269 function Scope(parent) {
270 this.names = {}; // names defined in this scope
271 this.mangled = {}; // mangled names (orig.name => mangled)
272 this.rev_mangled = {}; // reverse lookup (mangled => orig.name)
273 this.cname = -1; // current mangled name
274 this.refs = {}; // names referenced from this scope
275 this.uses_with = false; // will become TRUE if with() is detected in this or any subscopes
276 this.uses_eval = false; // will become TRUE if eval() is detected in this or any subscopes
277 this.parent = parent; // parent scope
278 this.children = []; // sub-scopes
280 this.level = parent.level + 1;
281 parent.children.push(this);
287 var base54 = (function(){
288 var DIGITS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_";
289 return function(num) {
292 ret = DIGITS.charAt(num % 54) + ret;
293 num = Math.floor(num / 54);
300 has: function(name) {
301 for (var s = this; s; s = s.parent)
302 if (HOP(s.names, name))
305 has_mangled: function(mname) {
306 for (var s = this; s; s = s.parent)
307 if (HOP(s.rev_mangled, mname))
313 uses_eval: this.uses_eval,
314 uses_with: this.uses_with
318 next_mangled: function() {
319 // we must be careful that the new mangled name:
321 // 1. doesn't shadow a mangled name from a parent
322 // scope, unless we don't reference the original
323 // name from this scope OR from any sub-scopes!
324 // This will get slow.
326 // 2. doesn't shadow an original name from a parent
327 // scope, in the event that the name is not mangled
328 // in the parent scope and we reference that name
329 // here OR IN ANY SUBSCOPES!
331 // 3. doesn't shadow a name that is referenced but not
332 // defined (possibly global defined elsewhere).
334 var m = base54(++this.cname), prior;
337 prior = this.has_mangled(m);
338 if (prior && this.refs[prior.rev_mangled[m]] === prior)
343 if (prior && prior !== this && this.refs[m] === prior && !prior.has_mangled(m))
347 if (HOP(this.refs, m) && this.refs[m] == null)
350 // I got "do" once. :-/
351 if (!is_identifier(m))
357 set_mangle: function(name, m) {
358 this.rev_mangled[m] = name;
359 return this.mangled[name] = m;
361 get_mangled: function(name, newMangle) {
362 if (this.uses_eval || this.uses_with) return name; // no mangle if eval or with is in use
363 var s = this.has(name);
364 if (!s) return name; // not in visible scope, no mangle
365 if (HOP(s.mangled, name)) return s.mangled[name]; // already mangled in this scope
366 if (!newMangle) return name; // not found and no mangling requested
367 return s.set_mangle(name, s.next_mangled());
369 references: function(name) {
370 return name && !this.parent || this.uses_with || this.uses_eval || this.refs[name];
372 define: function(name, type) {
374 if (type == "var" || !HOP(this.names, name))
375 this.names[name] = type || "var";
381 function ast_add_scope(ast) {
383 var current_scope = null;
384 var w = ast_walker(), walk = w.walk;
385 var having_eval = [];
387 function with_new_scope(cont) {
388 current_scope = new Scope(current_scope);
389 current_scope.labels = new Scope();
390 var ret = current_scope.body = cont();
391 ret.scope = current_scope;
392 current_scope = current_scope.parent;
396 function define(name, type) {
397 return current_scope.define(name, type);
400 function reference(name) {
401 current_scope.refs[name] = true;
404 function _lambda(name, args, body) {
405 var is_defun = this[0] == "defun";
406 return [ this[0], is_defun ? define(name, "defun") : name, args, with_new_scope(function(){
407 if (!is_defun) define(name, "lambda");
408 MAP(args, function(name){ define(name, "arg") });
409 return MAP(body, walk);
413 function _vardefs(type) {
414 return function(defs) {
415 MAP(defs, function(d){
417 if (d[1]) reference(d[0]);
422 function _breacont(label) {
424 current_scope.labels.refs[label] = true;
427 return with_new_scope(function(){
429 var ret = w.with_walkers({
432 "label": function(name, stat) { current_scope.labels.define(name) },
434 "continue": _breacont,
435 "with": function(expr, block) {
436 for (var s = current_scope; s; s = s.parent)
439 "var": _vardefs("var"),
440 "const": _vardefs("const"),
441 "try": function(t, c, f) {
442 if (c != null) return [
445 [ define(c[0], "catch"), MAP(c[1], walk) ],
446 f != null ? MAP(f, walk) : null
449 "name": function(name) {
451 having_eval.push(current_scope);
458 // the reason why we need an additional pass here is
459 // that names can be used prior to their definition.
461 // scopes where eval was detected and their parents
462 // are marked with uses_eval, unless they define the
464 MAP(having_eval, function(scope){
465 if (!scope.has("eval")) while (scope) {
466 scope.uses_eval = true;
467 scope = scope.parent;
471 // for referenced names it might be useful to know
472 // their origin scope. current_scope here is the
474 function fixrefs(scope, i) {
475 // do children first; order shouldn't matter
476 for (i = scope.children.length; --i >= 0;)
477 fixrefs(scope.children[i]);
478 for (i in scope.refs) if (HOP(scope.refs, i)) {
479 // find origin scope and propagate the reference to origin
480 for (var origin = scope.has(i), s = scope; s; s = s.parent) {
482 if (s === origin) break;
486 fixrefs(current_scope);
493 /* -----[ mangle names ]----- */
495 function ast_mangle(ast, options) {
496 var w = ast_walker(), walk = w.walk, scope;
497 options = options || {};
499 function get_mangled(name, newMangle) {
500 if (!options.toplevel && !scope.parent) return name; // don't mangle toplevel
501 if (options.except && member(name, options.except))
503 return scope.get_mangled(name, newMangle);
506 function get_define(name) {
507 if (options.defines) {
508 // we always lookup a defined symbol for the current scope FIRST, so declared
509 // vars trump a DEFINE symbol, but if no such var is found, then match a DEFINE value
510 if (!scope.has(name)) {
511 if (HOP(options.defines, name)) {
512 return options.defines[name];
519 function _lambda(name, args, body) {
520 if (!options.no_functions) {
521 var is_defun = this[0] == "defun", extra;
523 if (is_defun) name = get_mangled(name);
524 else if (body.scope.references(name)) {
526 if (!(scope.uses_eval || scope.uses_with))
527 name = extra[name] = scope.next_mangled();
534 body = with_scope(body.scope, function(){
535 args = MAP(args, function(name){ return get_mangled(name) });
536 return MAP(body, walk);
538 return [ this[0], name, args, body ];
541 function with_scope(s, cont, extra) {
544 if (extra) for (var i in extra) if (HOP(extra, i)) {
545 s.set_mangle(i, extra[i]);
547 for (var i in s.names) if (HOP(s.names, i)) {
548 get_mangled(i, true);
556 function _vardefs(defs) {
557 return [ this[0], MAP(defs, function(d){
558 return [ get_mangled(d[0]), walk(d[1]) ];
562 function _breacont(label) {
563 if (label) return [ this[0], scope.labels.get_mangled(label) ];
566 return w.with_walkers({
568 "defun": function() {
569 // move function declarations to the top when
570 // they are not in some block.
571 var ast = _lambda.apply(this, arguments);
572 switch (w.parent()[0]) {
576 return MAP.at_top(ast);
580 "label": function(label, stat) {
581 if (scope.labels.refs[label]) return [
583 scope.labels.get_mangled(label, true),
589 "continue": _breacont,
592 "name": function(name) {
593 return get_define(name) || [ this[0], get_mangled(name) ];
595 "try": function(t, c, f) {
598 c != null ? [ get_mangled(c[0]), MAP(c[1], walk) ] : null,
599 f != null ? MAP(f, walk) : null ];
601 "toplevel": function(body) {
603 return with_scope(self.scope, function(){
604 return [ self[0], MAP(body, walk) ];
608 return walk(ast_add_scope(ast));
613 - compress foo["bar"] into foo.bar,
614 - remove block brackets {} where possible
615 - join consecutive var declarations
616 - various optimizations for IFs:
617 - if (cond) foo(); else bar(); ==> cond?foo():bar();
618 - if (cond) foo(); ==> cond&&foo();
619 - if (foo) return bar(); else return baz(); ==> return foo?bar():baz(); // also for throw
620 - if (foo) return bar(); else something(); ==> {if(foo)return bar();something()}
623 var warn = function(){};
625 function best_of(ast1, ast2) {
626 return gen_code(ast1).length > gen_code(ast2[0] == "stat" ? ast2[1] : ast2).length ? ast2 : ast1;
629 function last_stat(b) {
630 if (b[0] == "block" && b[1] && b[1].length > 0)
631 return b[1][b[1].length - 1];
636 if (t) switch (last_stat(t)[0]) {
645 function boolean_expr(expr) {
646 return ( (expr[0] == "unary-prefix"
647 && member(expr[1], [ "!", "delete" ])) ||
650 && member(expr[1], [ "in", "instanceof", "==", "!=", "===", "!==", "<", "<=", ">=", ">" ])) ||
653 && member(expr[1], [ "&&", "||" ])
654 && boolean_expr(expr[2])
655 && boolean_expr(expr[3])) ||
657 (expr[0] == "conditional"
658 && boolean_expr(expr[2])
659 && boolean_expr(expr[3])) ||
663 && boolean_expr(expr[3])) ||
666 && boolean_expr(expr[expr.length - 1]))
671 return !b || (b[0] == "block" && (!b[1] || b[1].length == 0));
674 function is_string(node) {
675 return (node[0] == "string" ||
676 node[0] == "unary-prefix" && node[1] == "typeof" ||
677 node[0] == "binary" && node[1] == "+" &&
678 (is_string(node[2]) || is_string(node[3])));
681 var when_constant = (function(){
683 var $NOT_CONSTANT = {};
685 // this can only evaluate constant expressions. If it finds anything
686 // not constant, it throws $NOT_CONSTANT.
687 function evaluate(expr) {
695 case "true": return true;
696 case "false": return false;
697 case "null": return null;
702 case "!": return !evaluate(expr[2]);
703 case "typeof": return typeof evaluate(expr[2]);
704 case "~": return ~evaluate(expr[2]);
705 case "-": return -evaluate(expr[2]);
706 case "+": return +evaluate(expr[2]);
710 var left = expr[2], right = expr[3];
712 case "&&" : return evaluate(left) && evaluate(right);
713 case "||" : return evaluate(left) || evaluate(right);
714 case "|" : return evaluate(left) | evaluate(right);
715 case "&" : return evaluate(left) & evaluate(right);
716 case "^" : return evaluate(left) ^ evaluate(right);
717 case "+" : return evaluate(left) + evaluate(right);
718 case "*" : return evaluate(left) * evaluate(right);
719 case "/" : return evaluate(left) / evaluate(right);
720 case "%" : return evaluate(left) % evaluate(right);
721 case "-" : return evaluate(left) - evaluate(right);
722 case "<<" : return evaluate(left) << evaluate(right);
723 case ">>" : return evaluate(left) >> evaluate(right);
724 case ">>>" : return evaluate(left) >>> evaluate(right);
725 case "==" : return evaluate(left) == evaluate(right);
726 case "===" : return evaluate(left) === evaluate(right);
727 case "!=" : return evaluate(left) != evaluate(right);
728 case "!==" : return evaluate(left) !== evaluate(right);
729 case "<" : return evaluate(left) < evaluate(right);
730 case "<=" : return evaluate(left) <= evaluate(right);
731 case ">" : return evaluate(left) > evaluate(right);
732 case ">=" : return evaluate(left) >= evaluate(right);
733 case "in" : return evaluate(left) in evaluate(right);
734 case "instanceof" : return evaluate(left) instanceof evaluate(right);
740 return function(expr, yes, no) {
742 var val = evaluate(expr), ast;
743 switch (typeof val) {
744 case "string": ast = [ "string", val ]; break;
745 case "number": ast = [ "num", val ]; break;
746 case "boolean": ast = [ "name", String(val) ]; break;
748 if (val === null) { ast = [ "atom", "null" ]; break; }
749 throw new Error("Can't handle constant of type: " + (typeof val));
751 return yes.call(expr, ast, val);
753 if (ex === $NOT_CONSTANT) {
754 if (expr[0] == "binary"
755 && (expr[1] == "===" || expr[1] == "!==")
756 && ((is_string(expr[2]) && is_string(expr[3]))
757 || (boolean_expr(expr[2]) && boolean_expr(expr[3])))) {
758 expr[1] = expr[1].substr(0, 2);
760 else if (no && expr[0] == "binary"
761 && (expr[1] == "||" || expr[1] == "&&")) {
762 // the whole expression is not constant but the lval may be...
764 var lval = evaluate(expr[2]);
765 expr = ((expr[1] == "&&" && (lval ? expr[3] : lval)) ||
766 (expr[1] == "||" && (lval ? lval : expr[3])) ||
769 // IGNORE... lval is not constant
772 return no ? no.call(expr, expr) : null;
780 function warn_unreachable(ast) {
782 warn("Dropping unreachable code: " + gen_code(ast, true));
785 function prepare_ifs(ast) {
786 var w = ast_walker(), walk = w.walk;
787 // In this first pass, we rewrite ifs which abort with no else with an
788 // if-else. For example:
796 // is rewritten into:
804 function redo_if(statements) {
805 statements = MAP(statements, walk);
807 for (var i = 0; i < statements.length; ++i) {
808 var fi = statements[i];
809 if (fi[0] != "if") continue;
811 if (fi[3] && walk(fi[3])) continue;
814 if (!aborts(t)) continue;
816 var conditional = walk(fi[1]);
818 var e_body = redo_if(statements.slice(i + 1));
819 var e = e_body.length == 1 ? e_body[0] : [ "block", e_body ];
821 return statements.slice(0, i).concat([ [
823 conditional, // conditional
832 function redo_if_lambda(name, args, body) {
833 body = redo_if(body);
834 return [ this[0], name, args, body ];
837 function redo_if_block(statements) {
838 return [ this[0], statements != null ? redo_if(statements) : null ];
841 return w.with_walkers({
842 "defun": redo_if_lambda,
843 "function": redo_if_lambda,
844 "block": redo_if_block,
845 "splice": redo_if_block,
846 "toplevel": function(statements) {
847 return [ this[0], redo_if(statements) ];
849 "try": function(t, c, f) {
853 c != null ? [ c[0], redo_if(c[1]) ] : null,
854 f != null ? redo_if(f) : null
862 function for_side_effects(ast, handler) {
863 var w = ast_walker(), walk = w.walk;
864 var $stop = {}, $restart = {};
865 function stop() { throw $stop };
866 function restart() { throw $restart };
867 function found(){ return handler.call(this, this, w, stop, restart) };
869 if (op == "++" || op == "--")
870 return found.apply(this, arguments);
872 return w.with_walkers({
888 "unary-prefix": unary,
889 "unary-postfix": unary,
896 if (ex === $stop) break;
897 if (ex === $restart) continue;
903 function ast_lift_variables(ast) {
904 var w = ast_walker(), walk = w.walk, scope;
905 function do_body(body, env) {
908 body = MAP(body, walk);
909 var hash = {}, names = MAP(env.names, function(type, name){
910 if (type != "var") return MAP.skip;
911 if (!env.references(name)) return MAP.skip;
915 if (names.length > 0) {
916 // looking for assignments to any of these variables.
917 // we can save considerable space by moving the definitions
918 // in the var declaration.
919 for_side_effects([ "block", body ], function(ast, walker, stop, restart) {
920 if (ast[0] == "assign"
922 && ast[2][0] == "name"
923 && HOP(hash, ast[2][1])) {
924 // insert the definition into the var declaration
925 for (var i = names.length; --i >= 0;) {
926 if (names[i][0] == ast[2][1]) {
927 if (names[i][1]) // this name already defined, we must stop
929 names[i][1] = ast[3]; // definition
930 names.push(names.splice(i, 1)[0]);
934 // remove this assignment from the AST.
935 var p = walker.parent();
938 a.unshift(0, p.length);
939 p.splice.apply(p, a);
941 else if (p[0] == "stat") {
942 p.splice(0, p.length, "block"); // empty statement
951 body.unshift([ "var", names ]);
956 function _vardefs(defs) {
958 for (var i = defs.length; --i >= 0;) {
961 d = [ "assign", true, [ "name", d[0] ], d[1] ];
962 if (ret == null) ret = d;
963 else ret = [ "seq", d, ret ];
966 if (w.parent()[0] == "for-in")
967 return [ "name", defs[0][0] ];
970 return [ "stat", ret ];
972 function _toplevel(body) {
973 return [ this[0], do_body(body, this.scope) ];
975 return w.with_walkers({
976 "function": function(name, args, body){
977 for (var i = args.length; --i >= 0 && !body.scope.references(args[i]);)
979 if (!body.scope.references(name)) name = null;
980 return [ this[0], name, args, do_body(body, body.scope) ];
982 "defun": function(name, args, body){
983 if (!scope.references(name)) return MAP.skip;
984 for (var i = args.length; --i >= 0 && !body.scope.references(args[i]);)
986 return [ this[0], name, args, do_body(body, body.scope) ];
989 "toplevel": _toplevel
991 return walk(ast_add_scope(ast));
995 function ast_squeeze(ast, options) {
996 options = defaults(options, {
1003 var w = ast_walker(), walk = w.walk;
1005 function negate(c) {
1006 var not_c = [ "unary-prefix", "!", c ];
1008 case "unary-prefix":
1009 return c[1] == "!" && boolean_expr(c[2]) ? c[2] : not_c;
1012 c[c.length - 1] = negate(c[c.length - 1]);
1015 return best_of(not_c, [ "conditional", c[1], negate(c[2]), negate(c[3]) ]);
1017 var op = c[1], left = c[2], right = c[3];
1018 if (!options.keep_comps) switch (op) {
1019 case "<=" : return [ "binary", ">", left, right ];
1020 case "<" : return [ "binary", ">=", left, right ];
1021 case ">=" : return [ "binary", "<", left, right ];
1022 case ">" : return [ "binary", "<=", left, right ];
1025 case "==" : return [ "binary", "!=", left, right ];
1026 case "!=" : return [ "binary", "==", left, right ];
1027 case "===" : return [ "binary", "!==", left, right ];
1028 case "!==" : return [ "binary", "===", left, right ];
1029 case "&&" : return best_of(not_c, [ "binary", "||", negate(left), negate(right) ]);
1030 case "||" : return best_of(not_c, [ "binary", "&&", negate(left), negate(right) ]);
1037 function make_conditional(c, t, e) {
1038 var make_real_conditional = function() {
1039 if (c[0] == "unary-prefix" && c[1] == "!") {
1040 return e ? [ "conditional", c[2], e, t ] : [ "binary", "||", c[2], t ];
1043 [ "conditional", c, t, e ],
1044 [ "conditional", negate(c), e, t ]
1045 ) : [ "binary", "&&", c, t ];
1048 // shortcut the conditional if the expression has a constant value
1049 return when_constant(c, function(ast, val){
1050 warn_unreachable(val ? e : t);
1051 return (val ? t : e);
1052 }, make_real_conditional);
1055 function rmblock(block) {
1056 if (block != null && block[0] == "block" && block[1]) {
1057 if (block[1].length == 1)
1058 block = block[1][0];
1059 else if (block[1].length == 0)
1060 block = [ "block" ];
1065 function _lambda(name, args, body) {
1066 return [ this[0], name, args, tighten(body, "lambda") ];
1069 // this function does a few things:
1070 // 1. discard useless blocks
1071 // 2. join consecutive var declarations
1072 // 3. remove obviously dead code
1073 // 4. transform consecutive statements using the comma operator
1074 // 5. if block_type == "lambda" and it detects constructs like if(foo) return ... - rewrite like if (!foo) { ... }
1075 function tighten(statements, block_type) {
1076 statements = MAP(statements, walk);
1078 statements = statements.reduce(function(a, stat){
1079 if (stat[0] == "block") {
1081 a.push.apply(a, stat[1]);
1089 statements = (function(a, prev){
1090 statements.forEach(function(cur){
1091 if (prev && ((cur[0] == "var" && prev[0] == "var") ||
1092 (cur[0] == "const" && prev[0] == "const"))) {
1093 prev[1] = prev[1].concat(cur[1]);
1102 if (options.dead_code) statements = (function(a, has_quit){
1103 statements.forEach(function(st){
1105 if (st[0] == "function" || st[0] == "defun") {
1108 else if (st[0] == "var" || st[0] == "const") {
1109 if (!options.no_warnings)
1110 warn("Variables declared in unreachable code");
1111 st[1] = MAP(st[1], function(def){
1112 if (def[1] && !options.no_warnings)
1113 warn_unreachable([ "assign", true, [ "name", def[0] ], def[1] ]);
1118 else if (!options.no_warnings)
1119 warn_unreachable(st);
1123 if (member(st[0], [ "return", "throw", "break", "continue" ]))
1130 if (options.make_seqs) statements = (function(a, prev) {
1131 statements.forEach(function(cur){
1132 if (prev && prev[0] == "stat" && cur[0] == "stat") {
1133 prev[1] = [ "seq", prev[1], cur[1] ];
1140 && a[a.length-2][0] == "stat"
1141 && (a[a.length-1][0] == "return" || a[a.length-1][0] == "throw")
1142 && a[a.length-1][1])
1144 a.splice(a.length - 2, 2,
1146 [ "seq", a[a.length-2][1], a[a.length-1][1] ]]);
1151 // this increases jQuery by 1K. Probably not such a good idea after all..
1152 // part of this is done in prepare_ifs anyway.
1153 // if (block_type == "lambda") statements = (function(i, a, stat){
1154 // while (i < statements.length) {
1155 // stat = statements[i++];
1156 // if (stat[0] == "if" && !stat[3]) {
1157 // if (stat[2][0] == "return" && stat[2][1] == null) {
1158 // a.push(make_if(negate(stat[1]), [ "block", statements.slice(i) ]));
1161 // var last = last_stat(stat[2]);
1162 // if (last[0] == "return" && last[1] == null) {
1163 // a.push(make_if(stat[1], [ "block", stat[2][1].slice(0, -1) ], [ "block", statements.slice(i) ]));
1175 function make_if(c, t, e) {
1176 return when_constant(c, function(ast, val){
1179 warn_unreachable(e);
1180 return t || [ "block" ];
1183 warn_unreachable(t);
1184 return e || [ "block" ];
1187 return make_real_if(c, t, e);
1191 function abort_else(c, t, e) {
1192 var ret = [ [ "if", negate(c), e ] ];
1193 if (t[0] == "block") {
1194 if (t[1]) ret = ret.concat(t[1]);
1198 return walk([ "block", ret ]);
1201 function make_real_if(c, t, e) {
1210 } else if (empty(e)) {
1213 // if we have both else and then, maybe it makes sense to switch them?
1215 var a = gen_code(c);
1217 var b = gen_code(n);
1218 if (b.length < a.length) {
1226 if (empty(e) && empty(t))
1227 return [ "stat", c ];
1228 var ret = [ "if", c, t, e ];
1229 if (t[0] == "if" && empty(t[3]) && empty(e)) {
1230 ret = best_of(ret, walk([ "if", [ "binary", "&&", c, t[1] ], t[2] ]));
1232 else if (t[0] == "stat") {
1235 ret = best_of(ret, [ "stat", make_conditional(c, t[1], e[1]) ]);
1237 ret = abort_else(c, t, e);
1240 ret = best_of(ret, [ "stat", make_conditional(c, t[1]) ]);
1243 else if (e && t[0] == e[0] && (t[0] == "return" || t[0] == "throw") && t[1] && e[1]) {
1244 ret = best_of(ret, [ t[0], make_conditional(c, t[1], e[1] ) ]);
1246 else if (e && aborts(t)) {
1247 ret = [ [ "if", c, t ] ];
1248 if (e[0] == "block") {
1249 if (e[1]) ret = ret.concat(e[1]);
1254 ret = walk([ "block", ret ]);
1256 else if (t && aborts(e)) {
1257 ret = abort_else(c, t, e);
1262 function _do_while(cond, body) {
1263 return when_constant(cond, function(cond, val){
1265 warn_unreachable(body);
1268 return [ "for", null, null, null, walk(body) ];
1273 return w.with_walkers({
1274 "sub": function(expr, subscript) {
1275 if (subscript[0] == "string") {
1276 var name = subscript[1];
1277 if (is_identifier(name))
1278 return [ "dot", walk(expr), name ];
1279 else if (/^[1-9][0-9]*$/.test(name) || name === "0")
1280 return [ "sub", walk(expr), [ "num", parseInt(name, 10) ] ];
1284 "toplevel": function(body) {
1285 return [ "toplevel", tighten(body) ];
1287 "switch": function(expr, body) {
1288 var last = body.length - 1;
1289 return [ "switch", walk(expr), MAP(body, function(branch, i){
1290 var block = tighten(branch[1]);
1291 if (i == last && block.length > 0) {
1292 var node = block[block.length - 1];
1293 if (node[0] == "break" && !node[1])
1296 return [ branch[0] ? walk(branch[0]) : null, block ];
1299 "function": _lambda,
1301 "block": function(body) {
1302 if (body) return rmblock([ "block", tighten(body) ]);
1304 "binary": function(op, left, right) {
1305 return when_constant([ "binary", op, walk(left), walk(right) ], function yes(c){
1306 return best_of(walk(c), this);
1309 if(op != "==" && op != "!=") return;
1310 var l = walk(left), r = walk(right);
1311 if(l && l[0] == "unary-prefix" && l[1] == "!" && l[2][0] == "num")
1312 left = ['num', +!l[2][1]];
1313 else if (r && r[0] == "unary-prefix" && r[1] == "!" && r[2][0] == "num")
1314 right = ['num', +!r[2][1]];
1315 return ["binary", op, left, right];
1319 "conditional": function(c, t, e) {
1320 return make_conditional(walk(c), walk(t), walk(e));
1322 "try": function(t, c, f) {
1326 c != null ? [ c[0], tighten(c[1]) ] : null,
1327 f != null ? tighten(f) : null
1330 "unary-prefix": function(op, expr) {
1332 var ret = [ "unary-prefix", op, expr ];
1334 ret = best_of(ret, negate(expr));
1335 return when_constant(ret, function(ast, val){
1336 return walk(ast); // it's either true or false, so minifies to !0 or !1
1337 }, function() { return ret });
1339 "name": function(name) {
1341 case "true": return [ "unary-prefix", "!", [ "num", 0 ]];
1342 case "false": return [ "unary-prefix", "!", [ "num", 1 ]];
1346 "assign": function(op, lvalue, rvalue) {
1347 lvalue = walk(lvalue);
1348 rvalue = walk(rvalue);
1349 var okOps = [ '+', '-', '/', '*', '%', '>>', '<<', '>>>', '|', '^', '&' ];
1350 if (op === true && lvalue[0] === "name" && rvalue[0] === "binary" &&
1351 ~okOps.indexOf(rvalue[1]) && rvalue[2][0] === "name" &&
1352 rvalue[2][1] === lvalue[1]) {
1353 return [ this[0], rvalue[1], lvalue, rvalue[3] ]
1355 return [ this[0], op, lvalue, rvalue ];
1358 for (var i = 0; i < 2; ++i) {
1359 ast = prepare_ifs(ast);
1366 /* -----[ re-generate code from the AST ]----- */
1368 var DOT_CALL_NO_PARENS = jsp.array_to_hash([
1380 function make_string(str, ascii_only) {
1382 str = str.replace(/[\\\b\f\n\r\t\x22\x27\u2028\u2029\0]/g, function(s){
1384 case "\\": return "\\\\";
1385 case "\b": return "\\b";
1386 case "\f": return "\\f";
1387 case "\n": return "\\n";
1388 case "\r": return "\\r";
1389 case "\t": return "\\t";
1390 case "\u2028": return "\\u2028";
1391 case "\u2029": return "\\u2029";
1392 case '"': ++dq; return '"';
1393 case "'": ++sq; return "'";
1394 case "\0": return "\\0";
1398 if (ascii_only) str = to_ascii(str);
1399 if (dq > sq) return "'" + str.replace(/\x27/g, "\\'") + "'";
1400 else return '"' + str.replace(/\x22/g, '\\"') + '"';
1403 function to_ascii(str) {
1404 return str.replace(/[\u0080-\uffff]/g, function(ch) {
1405 var code = ch.charCodeAt(0).toString(16);
1406 while (code.length < 4) code = "0" + code;
1407 return "\\u" + code;
1411 var SPLICE_NEEDS_BRACKETS = jsp.array_to_hash([ "if", "while", "do", "for", "for-in", "with" ]);
1413 function gen_code(ast, options) {
1414 options = defaults(options, {
1418 space_colon : false,
1421 inline_script: false
1423 var beautify = !!options.beautify;
1424 var indentation = 0,
1425 newline = beautify ? "\n" : "",
1426 space = beautify ? " " : "";
1428 function encode_string(str) {
1429 var ret = make_string(str, options.ascii_only);
1430 if (options.inline_script)
1431 ret = ret.replace(/<\x2fscript([>\/\t\n\f\r ])/gi, "<\\/script$1");
1435 function make_name(name) {
1436 name = name.toString();
1437 if (options.ascii_only)
1438 name = to_ascii(name);
1442 function indent(line) {
1446 line = repeat_string(" ", options.indent_start + indentation * options.indent_level) + line;
1450 function with_indent(cont, incr) {
1451 if (incr == null) incr = 1;
1452 indentation += incr;
1453 try { return cont.apply(null, slice(arguments, 1)); }
1454 finally { indentation -= incr; }
1457 function add_spaces(a) {
1461 for (var i = 0; i < a.length; ++i) {
1462 var next = a[i + 1];
1465 ((/[a-z0-9_\x24]$/i.test(a[i].toString()) && /^[a-z0-9_\x24]/i.test(next.toString())) ||
1466 (/[\+\-]$/.test(a[i].toString()) && /^[\+\-]/.test(next.toString())))) {
1473 function add_commas(a) {
1474 return a.join("," + space);
1477 function parenthesize(expr) {
1478 var gen = make(expr);
1479 for (var i = 1; i < arguments.length; ++i) {
1480 var el = arguments[i];
1481 if ((el instanceof Function && el(expr)) || expr[0] == el)
1482 return "(" + gen + ")";
1487 function best_of(a) {
1488 if (a.length == 1) {
1491 if (a.length == 2) {
1494 return a.length <= b.length ? a : b;
1496 return best_of([ a[0], best_of(a.slice(1)) ]);
1499 function needs_parens(expr) {
1500 if (expr[0] == "function" || expr[0] == "object") {
1501 // dot/call on a literal function requires the
1502 // function literal itself to be parenthesized
1503 // only if it's the first "thing" in a
1504 // statement. This means that the parent is
1505 // "stat", but it could also be a "seq" and
1506 // we're the first in this "seq" and the
1507 // parent is "stat", and so on. Messy stuff,
1508 // but it worths the trouble.
1509 var a = slice(w.stack()), self = a.pop(), p = a.pop();
1511 if (p[0] == "stat") return true;
1512 if (((p[0] == "seq" || p[0] == "call" || p[0] == "dot" || p[0] == "sub" || p[0] == "conditional") && p[1] === self) ||
1513 ((p[0] == "binary" || p[0] == "assign" || p[0] == "unary-postfix") && p[2] === self)) {
1521 return !HOP(DOT_CALL_NO_PARENS, expr[0]);
1524 function make_num(num) {
1525 var str = num.toString(10), a = [ str.replace(/^0\./, ".") ], m;
1526 if (Math.floor(num) === num) {
1528 a.push("0x" + num.toString(16).toLowerCase(), // probably pointless
1529 "0" + num.toString(8)); // same.
1531 a.push("-0x" + (-num).toString(16).toLowerCase(), // probably pointless
1532 "-0" + (-num).toString(8)); // same.
1534 if ((m = /^(.*?)(0+)$/.exec(num))) {
1535 a.push(m[1] + "e" + m[2].length);
1537 } else if ((m = /^0?\.(0+)(.*)$/.exec(num))) {
1538 a.push(m[2] + "e-" + (m[1].length + m[2].length),
1539 str.substr(str.indexOf(".")));
1544 var w = ast_walker();
1546 return w.with_walkers({
1547 "string": encode_string,
1550 "debugger": function(){ return "debugger" },
1551 "toplevel": function(statements) {
1552 return make_block_statements(statements)
1553 .join(newline + newline);
1555 "splice": function(statements) {
1556 var parent = w.parent();
1557 if (HOP(SPLICE_NEEDS_BRACKETS, parent)) {
1558 // we need block brackets in this case
1559 return make_block.apply(this, arguments);
1561 return MAP(make_block_statements(statements, true),
1563 // the first line is already indented
1564 return i > 0 ? indent(line) : line;
1568 "block": make_block,
1569 "var": function(defs) {
1570 return "var " + add_commas(MAP(defs, make_1vardef)) + ";";
1572 "const": function(defs) {
1573 return "const " + add_commas(MAP(defs, make_1vardef)) + ";";
1575 "try": function(tr, ca, fi) {
1576 var out = [ "try", make_block(tr) ];
1577 if (ca) out.push("catch", "(" + ca[0] + ")", make_block(ca[1]));
1578 if (fi) out.push("finally", make_block(fi));
1579 return add_spaces(out);
1581 "throw": function(expr) {
1582 return add_spaces([ "throw", make(expr) ]) + ";";
1584 "new": function(ctor, args) {
1585 args = args.length > 0 ? "(" + add_commas(MAP(args, function(expr){
1586 return parenthesize(expr, "seq");
1588 return add_spaces([ "new", parenthesize(ctor, "seq", "binary", "conditional", "assign", function(expr){
1589 var w = ast_walker(), has_call = {};
1592 "call": function() { throw has_call },
1593 "function": function() { return this }
1598 if (ex === has_call)
1604 "switch": function(expr, body) {
1605 return add_spaces([ "switch", "(" + make(expr) + ")", make_switch_block(body) ]);
1607 "break": function(label) {
1610 out += " " + make_name(label);
1613 "continue": function(label) {
1614 var out = "continue";
1616 out += " " + make_name(label);
1619 "conditional": function(co, th, el) {
1620 return add_spaces([ parenthesize(co, "assign", "seq", "conditional"), "?",
1621 parenthesize(th, "seq"), ":",
1622 parenthesize(el, "seq") ]);
1624 "assign": function(op, lvalue, rvalue) {
1625 if (op && op !== true) op += "=";
1627 return add_spaces([ make(lvalue), op, parenthesize(rvalue, "seq") ]);
1629 "dot": function(expr) {
1630 var out = make(expr), i = 1;
1631 if (expr[0] == "num") {
1632 if (!/\./.test(expr[1]))
1634 } else if (needs_parens(expr))
1635 out = "(" + out + ")";
1636 while (i < arguments.length)
1637 out += "." + make_name(arguments[i++]);
1640 "call": function(func, args) {
1642 if (f.charAt(0) != "(" && needs_parens(func))
1644 return f + "(" + add_commas(MAP(args, function(expr){
1645 return parenthesize(expr, "seq");
1648 "function": make_function,
1649 "defun": make_function,
1650 "if": function(co, th, el) {
1651 var out = [ "if", "(" + make(co) + ")", el ? make_then(th) : make(th) ];
1653 out.push("else", make(el));
1655 return add_spaces(out);
1657 "for": function(init, cond, step, block) {
1658 var out = [ "for" ];
1659 init = (init != null ? make(init) : "").replace(/;*\s*$/, ";" + space);
1660 cond = (cond != null ? make(cond) : "").replace(/;*\s*$/, ";" + space);
1661 step = (step != null ? make(step) : "").replace(/;*\s*$/, "");
1662 var args = init + cond + step;
1663 if (args == "; ; ") args = ";;";
1664 out.push("(" + args + ")", make(block));
1665 return add_spaces(out);
1667 "for-in": function(vvar, key, hash, block) {
1668 return add_spaces([ "for", "(" +
1669 (vvar ? make(vvar).replace(/;+$/, "") : make(key)),
1671 make(hash) + ")", make(block) ]);
1673 "while": function(condition, block) {
1674 return add_spaces([ "while", "(" + make(condition) + ")", make(block) ]);
1676 "do": function(condition, block) {
1677 return add_spaces([ "do", make(block), "while", "(" + make(condition) + ")" ]) + ";";
1679 "return": function(expr) {
1680 var out = [ "return" ];
1681 if (expr != null) out.push(make(expr));
1682 return add_spaces(out) + ";";
1684 "binary": function(operator, lvalue, rvalue) {
1685 var left = make(lvalue), right = make(rvalue);
1686 // XXX: I'm pretty sure other cases will bite here.
1687 // we need to be smarter.
1688 // adding parens all the time is the safest bet.
1689 if (member(lvalue[0], [ "assign", "conditional", "seq" ]) ||
1690 lvalue[0] == "binary" && PRECEDENCE[operator] > PRECEDENCE[lvalue[1]] ||
1691 lvalue[0] == "function" && needs_parens(this)) {
1692 left = "(" + left + ")";
1694 if (member(rvalue[0], [ "assign", "conditional", "seq" ]) ||
1695 rvalue[0] == "binary" && PRECEDENCE[operator] >= PRECEDENCE[rvalue[1]] &&
1696 !(rvalue[1] == operator && member(operator, [ "&&", "||", "*" ]))) {
1697 right = "(" + right + ")";
1699 else if (!beautify && options.inline_script && (operator == "<" || operator == "<<")
1700 && rvalue[0] == "regexp" && /^script/i.test(rvalue[1])) {
1701 right = " " + right;
1703 return add_spaces([ left, operator, right ]);
1705 "unary-prefix": function(operator, expr) {
1706 var val = make(expr);
1707 if (!(expr[0] == "num" || (expr[0] == "unary-prefix" && !HOP(OPERATORS, operator + expr[1])) || !needs_parens(expr)))
1708 val = "(" + val + ")";
1709 return operator + (jsp.is_alphanumeric_char(operator.charAt(0)) ? " " : "") + val;
1711 "unary-postfix": function(operator, expr) {
1712 var val = make(expr);
1713 if (!(expr[0] == "num" || (expr[0] == "unary-postfix" && !HOP(OPERATORS, operator + expr[1])) || !needs_parens(expr)))
1714 val = "(" + val + ")";
1715 return val + operator;
1717 "sub": function(expr, subscript) {
1718 var hash = make(expr);
1719 if (needs_parens(expr))
1720 hash = "(" + hash + ")";
1721 return hash + "[" + make(subscript) + "]";
1723 "object": function(props) {
1724 var obj_needs_parens = needs_parens(this);
1725 if (props.length == 0)
1726 return obj_needs_parens ? "({})" : "{}";
1727 var out = "{" + newline + with_indent(function(){
1728 return MAP(props, function(p){
1729 if (p.length == 3) {
1730 // getter/setter. The name is in p[0], the arg.list in p[1][2], the
1731 // body in p[1][3] and type ("get" / "set") in p[2].
1732 return indent(make_function(p[0], p[1][2], p[1][3], p[2]));
1734 var key = p[0], val = parenthesize(p[1], "seq");
1735 if (options.quote_keys) {
1736 key = encode_string(key);
1737 } else if ((typeof key == "number" || !beautify && +key + "" == key)
1738 && parseFloat(key) >= 0) {
1739 key = make_num(+key);
1740 } else if (!is_identifier(key)) {
1741 key = encode_string(key);
1743 return indent(add_spaces(beautify && options.space_colon
1745 : [ key + ":", val ]));
1746 }).join("," + newline);
1747 }) + newline + indent("}");
1748 return obj_needs_parens ? "(" + out + ")" : out;
1750 "regexp": function(rx, mods) {
1751 return "/" + rx + "/" + mods;
1753 "array": function(elements) {
1754 if (elements.length == 0) return "[]";
1755 return add_spaces([ "[", add_commas(MAP(elements, function(el, i){
1756 if (!beautify && el[0] == "atom" && el[1] == "undefined") return i === elements.length - 1 ? "," : "";
1757 return parenthesize(el, "seq");
1760 "stat": function(stmt) {
1761 return make(stmt).replace(/;*\s*$/, ";");
1764 return add_commas(MAP(slice(arguments), make));
1766 "label": function(name, block) {
1767 return add_spaces([ make_name(name), ":", make(block) ]);
1769 "with": function(expr, block) {
1770 return add_spaces([ "with", "(" + make(expr) + ")", make(block) ]);
1772 "atom": function(name) {
1773 return make_name(name);
1775 }, function(){ return make(ast) });
1777 // The squeezer replaces "block"-s that contain only a single
1778 // statement with the statement itself; technically, the AST
1779 // is correct, but this can create problems when we output an
1780 // IF having an ELSE clause where the THEN clause ends in an
1781 // IF *without* an ELSE block (then the outer ELSE would refer
1782 // to the inner IF). This function checks for this case and
1783 // adds the block brackets if needed.
1784 function make_then(th) {
1785 if (th == null) return ";";
1786 if (th[0] == "do") {
1787 // https://github.com/mishoo/UglifyJS/issues/#issue/57
1788 // IE croaks with "syntax error" on code like this:
1789 // if (foo) do ... while(cond); else ...
1790 // we need block brackets around do/while
1791 return make_block([ th ]);
1798 // no else, we must add the block
1799 return make([ "block", [ th ]]);
1802 else if (type == "while" || type == "do") b = b[2];
1803 else if (type == "for" || type == "for-in") b = b[4];
1809 function make_function(name, args, body, keyword) {
1810 var out = keyword || "function";
1812 out += " " + make_name(name);
1814 out += "(" + add_commas(MAP(args, make_name)) + ")";
1815 out = add_spaces([ out, make_block(body) ]);
1816 return needs_parens(this) ? "(" + out + ")" : out;
1819 function must_has_semicolon(node) {
1823 return empty(node[2]); // `with' or `while' with empty body?
1826 return empty(node[4]); // `for' with empty body?
1828 if (empty(node[2]) && !node[3]) return true; // `if' with empty `then' and no `else'
1830 if (empty(node[3])) return true; // `else' present but empty
1831 return must_has_semicolon(node[3]); // dive into the `else' branch
1833 return must_has_semicolon(node[2]); // dive into the `then' branch
1837 function make_block_statements(statements, noindent) {
1838 for (var a = [], last = statements.length - 1, i = 0; i <= last; ++i) {
1839 var stat = statements[i];
1840 var code = make(stat);
1842 if (!beautify && i == last && !must_has_semicolon(stat)) {
1843 code = code.replace(/;+\s*$/, "");
1848 return noindent ? a : MAP(a, indent);
1851 function make_switch_block(body) {
1852 var n = body.length;
1853 if (n == 0) return "{}";
1854 return "{" + newline + MAP(body, function(branch, i){
1855 var has_body = branch[1].length > 0, code = with_indent(function(){
1856 return indent(branch[0]
1857 ? add_spaces([ "case", make(branch[0]) + ":" ])
1859 }, 0.5) + (has_body ? newline + with_indent(function(){
1860 return make_block_statements(branch[1]).join(newline);
1862 if (!beautify && has_body && i < n - 1)
1865 }).join(newline) + newline + indent("}");
1868 function make_block(statements) {
1869 if (!statements) return ";";
1870 if (statements.length == 0) return "{}";
1871 return "{" + newline + with_indent(function(){
1872 return make_block_statements(statements).join(newline);
1873 }) + newline + indent("}");
1876 function make_1vardef(def) {
1877 var name = def[0], val = def[1];
1879 name = add_spaces([ make_name(name), "=", parenthesize(val, "seq") ]);
1885 function split_lines(code, max_line_length) {
1887 jsp.parse(function(){
1888 var next_token = jsp.tokenizer(code);
1891 function current_length(tok) {
1892 return tok.pos - last_split;
1894 function split_here(tok) {
1895 last_split = tok.pos;
1896 splits.push(last_split);
1899 var tok = next_token.apply(this, arguments);
1902 if (prev_token.type == "keyword") break out;
1904 if (current_length(tok) > max_line_length) {
1918 custom.context = function() {
1919 return next_token.context.apply(this, arguments);
1923 return splits.map(function(pos, i){
1924 return code.substring(pos, splits[i + 1] || code.length);
1928 /* -----[ Utilities ]----- */
1930 function repeat_string(str, i) {
1931 if (i <= 0) return "";
1932 if (i == 1) return str;
1933 var d = repeat_string(str, i >> 1);
1935 if (i & 1) d += str;
1939 function defaults(args, defs) {
1943 for (var i in defs) if (HOP(defs, i)) {
1944 ret[i] = (args && HOP(args, i)) ? args[i] : defs[i];
1949 function is_identifier(name) {
1950 return /^[a-z_$][a-z0-9_$]*$/i.test(name)
1952 && !HOP(jsp.KEYWORDS_ATOM, name)
1953 && !HOP(jsp.RESERVED_WORDS, name)
1954 && !HOP(jsp.KEYWORDS, name);
1957 function HOP(obj, prop) {
1958 return Object.prototype.hasOwnProperty.call(obj, prop);
1966 MAP = function(a, f, o) {
1967 var ret = [], top = [], i;
1969 var val = f.call(o, a[i], i);
1970 if (val instanceof AtTop) {
1972 if (val instanceof Splice) {
1973 top.push.apply(top, val.v);
1978 else if (val != skip) {
1979 if (val instanceof Splice) {
1980 ret.push.apply(ret, val.v);
1986 if (a instanceof Array) for (i = 0; i < a.length; ++i) doit();
1987 else for (i in a) if (HOP(a, i)) doit();
1988 return top.concat(ret);
1990 MAP.at_top = function(val) { return new AtTop(val) };
1991 MAP.splice = function(val) { return new Splice(val) };
1992 var skip = MAP.skip = {};
1993 function AtTop(val) { this.v = val };
1994 function Splice(val) { this.v = val };
1997 /* -----[ Exports ]----- */
1999 exports.ast_walker = ast_walker;
2000 exports.ast_mangle = ast_mangle;
2001 exports.ast_squeeze = ast_squeeze;
2002 exports.ast_lift_variables = ast_lift_variables;
2003 exports.gen_code = gen_code;
2004 exports.ast_add_scope = ast_add_scope;
2005 exports.set_logger = function(logger) { warn = logger };
2006 exports.make_string = make_string;
2007 exports.split_lines = split_lines;
2011 exports.ast_squeeze_more = require("./squeeze-more").ast_squeeze_more;