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 "defun": function(name, args, body) {
143 return [ this[0], name, args.slice(), MAP(body, walk) ];
145 "if": function(conditional, t, e) {
146 return [ this[0], walk(conditional), walk(t), walk(e) ];
148 "for": function(init, cond, step, block) {
149 return [ this[0], walk(init), walk(cond), walk(step), walk(block) ];
151 "for-in": function(vvar, key, hash, block) {
152 return [ this[0], walk(vvar), walk(key), walk(hash), walk(block) ];
154 "while": function(cond, block) {
155 return [ this[0], walk(cond), walk(block) ];
157 "do": function(cond, block) {
158 return [ this[0], walk(cond), walk(block) ];
160 "return": function(expr) {
161 return [ this[0], walk(expr) ];
163 "binary": function(op, left, right) {
164 return [ this[0], op, walk(left), walk(right) ];
166 "unary-prefix": function(op, expr) {
167 return [ this[0], op, walk(expr) ];
169 "unary-postfix": function(op, expr) {
170 return [ this[0], op, walk(expr) ];
172 "sub": function(expr, subscript) {
173 return [ this[0], walk(expr), walk(subscript) ];
175 "object": function(props) {
176 return [ this[0], MAP(props, function(p){
178 ? [ p[0], walk(p[1]) ]
179 : [ p[0], walk(p[1]), p[2] ]; // get/set-ter
182 "regexp": function(rx, mods) {
183 return [ this[0], rx, mods ];
185 "array": function(elements) {
186 return [ this[0], MAP(elements, walk) ];
188 "stat": function(stat) {
189 return [ this[0], walk(stat) ];
192 return [ this[0] ].concat(MAP(slice(arguments), walk));
194 "label": function(name, block) {
195 return [ this[0], name, walk(block) ];
197 "with": function(expr, block) {
198 return [ this[0], walk(expr), walk(block) ];
200 "atom": function(name) {
201 return [ this[0], name ];
213 var gen = user[type];
215 var ret = gen.apply(ast, ast.slice(1));
220 return gen.apply(ast, ast.slice(1));
231 return walkers[ast[0]].apply(ast, ast.slice(1));
237 function with_walkers(walkers, cont){
239 for (i in walkers) if (HOP(walkers, i)) {
241 user[i] = walkers[i];
244 for (i in save) if (HOP(save, i)) {
245 if (!save[i]) delete user[i];
246 else user[i] = save[i];
254 with_walkers: with_walkers,
256 return stack[stack.length - 2]; // last one is current node
264 /* -----[ Scope and mangling ]----- */
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
277 this.level = parent.level + 1;
278 parent.children.push(this);
284 var base54 = (function(){
285 var DIGITS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_";
286 return function(num) {
289 ret = DIGITS.charAt(num % 54) + ret;
290 num = Math.floor(num / 54);
297 has: function(name) {
298 for (var s = this; s; s = s.parent)
299 if (HOP(s.names, name))
302 has_mangled: function(mname) {
303 for (var s = this; s; s = s.parent)
304 if (HOP(s.rev_mangled, mname))
310 uses_eval: this.uses_eval,
311 uses_with: this.uses_with
315 next_mangled: function() {
316 // we must be careful that the new mangled name:
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.
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!
328 // 3. doesn't shadow a name that is referenced but not
329 // defined (possibly global defined elsewhere).
331 var m = base54(++this.cname), prior;
334 prior = this.has_mangled(m);
335 if (prior && this.refs[prior.rev_mangled[m]] === prior)
340 if (prior && prior !== this && this.refs[m] === prior && !prior.has_mangled(m))
344 if (HOP(this.refs, m) && this.refs[m] == null)
347 // I got "do" once. :-/
348 if (!is_identifier(m))
354 set_mangle: function(name, m) {
355 this.rev_mangled[m] = name;
356 return this.mangled[name] = m;
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());
366 references: function(name) {
367 return name && !this.parent || this.uses_with || this.uses_eval || this.refs[name];
369 define: function(name, type) {
371 if (type == "var" || !HOP(this.names, name))
372 this.names[name] = type || "var";
378 function ast_add_scope(ast) {
380 var current_scope = null;
381 var w = ast_walker(), walk = w.walk;
382 var having_eval = [];
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;
392 function define(name, type) {
393 return current_scope.define(name, type);
396 function reference(name) {
397 current_scope.refs[name] = true;
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);
409 function _vardefs(type) {
410 return function(defs) {
411 MAP(defs, function(d){
413 if (d[1]) reference(d[0]);
418 return with_new_scope(function(){
420 var ret = w.with_walkers({
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)
430 "var": _vardefs("var"),
431 "const": _vardefs("const"),
432 "try": function(t, c, f) {
433 if (c != null) return [
436 [ define(c[0], "catch"), MAP(c[1], walk) ],
437 f != null ? MAP(f, walk) : null
440 "name": function(name) {
442 having_eval.push(current_scope);
449 // the reason why we need an additional pass here is
450 // that names can be used prior to their definition.
452 // scopes where eval was detected and their parents
453 // are marked with uses_eval, unless they define the
455 MAP(having_eval, function(scope){
456 if (!scope.has("eval")) while (scope) {
457 scope.uses_eval = true;
458 scope = scope.parent;
462 // for referenced names it might be useful to know
463 // their origin scope. current_scope here is the
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) {
473 if (s === origin) break;
477 fixrefs(current_scope);
484 /* -----[ mangle names ]----- */
486 function ast_mangle(ast, options) {
487 var w = ast_walker(), walk = w.walk, scope;
488 options = options || {};
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))
494 return scope.get_mangled(name, newMangle);
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];
510 function _lambda(name, args, body) {
511 var is_defun = this[0] == "defun", extra;
513 if (is_defun) name = get_mangled(name);
516 if (!(scope.uses_eval || scope.uses_with))
517 name = extra[name] = scope.next_mangled();
522 body = with_scope(body.scope, function(){
523 args = MAP(args, function(name){ return get_mangled(name) });
524 return MAP(body, walk);
526 return [ this[0], name, args, body ];
529 function with_scope(s, cont, extra) {
532 if (extra) for (var i in extra) if (HOP(extra, i)) {
533 s.set_mangle(i, extra[i]);
535 for (var i in s.names) if (HOP(s.names, i)) {
536 get_mangled(i, true);
544 function _vardefs(defs) {
545 return [ this[0], MAP(defs, function(d){
546 return [ get_mangled(d[0]), walk(d[1]) ];
550 return w.with_walkers({
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]) {
560 return MAP.at_top(ast);
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) ] },
569 "name": function(name) {
570 return get_define(name) || [ this[0], get_mangled(name) ];
572 "try": function(t, c, f) {
575 c != null ? [ get_mangled(c[0]), MAP(c[1], walk) ] : null,
576 f != null ? MAP(f, walk) : null ];
578 "toplevel": function(body) {
580 return with_scope(self.scope, function(){
581 return [ self[0], MAP(body, walk) ];
585 return walk(ast_add_scope(ast));
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()}
600 var warn = function(){};
602 function best_of(ast1, ast2) {
603 return gen_code(ast1).length > gen_code(ast2[0] == "stat" ? ast2[1] : ast2).length ? ast2 : ast1;
606 function last_stat(b) {
607 if (b[0] == "block" && b[1] && b[1].length > 0)
608 return b[1][b[1].length - 1];
613 if (t) switch (last_stat(t)[0]) {
622 function boolean_expr(expr) {
623 return ( (expr[0] == "unary-prefix"
624 && member(expr[1], [ "!", "delete" ])) ||
627 && member(expr[1], [ "in", "instanceof", "==", "!=", "===", "!==", "<", "<=", ">=", ">" ])) ||
630 && member(expr[1], [ "&&", "||" ])
631 && boolean_expr(expr[2])
632 && boolean_expr(expr[3])) ||
634 (expr[0] == "conditional"
635 && boolean_expr(expr[2])
636 && boolean_expr(expr[3])) ||
640 && boolean_expr(expr[3])) ||
643 && boolean_expr(expr[expr.length - 1]))
648 return !b || (b[0] == "block" && (!b[1] || b[1].length == 0));
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])));
658 var when_constant = (function(){
660 var $NOT_CONSTANT = {};
662 // this can only evaluate constant expressions. If it finds anything
663 // not constant, it throws $NOT_CONSTANT.
664 function evaluate(expr) {
672 case "true": return true;
673 case "false": return false;
674 case "null": return null;
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]);
687 var left = expr[2], right = expr[3];
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);
717 return function(expr, yes, no) {
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));
726 return yes.call(expr, ast, val);
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);
735 else if (no && expr[0] == "binary"
736 && (expr[1] == "||" || expr[1] == "&&")) {
737 // the whole expression is not constant but the lval may be...
739 var lval = evaluate(expr[2]);
740 expr = ((expr[1] == "&&" && (lval ? expr[3] : lval)) ||
741 (expr[1] == "||" && (lval ? lval : expr[3])) ||
744 // IGNORE... lval is not constant
747 return no ? no.call(expr, expr) : null;
755 function warn_unreachable(ast) {
757 warn("Dropping unreachable code: " + gen_code(ast, true));
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:
771 // is rewritten into:
779 function redo_if(statements) {
780 statements = MAP(statements, walk);
782 for (var i = 0; i < statements.length; ++i) {
783 var fi = statements[i];
784 if (fi[0] != "if") continue;
786 if (fi[3] && walk(fi[3])) continue;
789 if (!aborts(t)) continue;
791 var conditional = walk(fi[1]);
793 var e_body = statements.slice(i + 1);
794 var e = e_body.length == 1 ? e_body[0] : [ "block", e_body ];
796 var ret = statements.slice(0, i).concat([ [
798 conditional, // conditional
809 function redo_if_lambda(name, args, body) {
810 body = redo_if(body);
811 return [ this[0], name, args, body ];
814 function redo_if_block(statements) {
815 return [ this[0], statements != null ? redo_if(statements) : null ];
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) ];
826 "try": function(t, c, f) {
830 c != null ? [ c[0], redo_if(c[1]) ] : null,
831 f != null ? redo_if(f) : null
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) };
846 if (op == "++" || op == "--")
847 return found.apply(this, arguments);
849 return w.with_walkers({
865 "unary-prefix": unary,
866 "unary-postfix": unary,
873 if (ex === $stop) break;
874 if (ex === $restart) continue;
880 function ast_lift_variables(ast) {
881 var w = ast_walker(), walk = w.walk, scope;
882 function do_body(body, 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;
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"
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
906 names[i][1] = ast[3]; // definition
907 names.push(names.splice(i, 1)[0]);
911 // remove this assignment from the AST.
912 var p = walker.parent();
915 a.unshift(0, p.length);
916 p.splice.apply(p, a);
918 else if (p[0] == "stat") {
919 p.splice(0, p.length, "block"); // empty statement
928 body.unshift([ "var", names ]);
933 function _vardefs(defs) {
935 for (var i = defs.length; --i >= 0;) {
938 d = [ "assign", true, [ "name", d[0] ], d[1] ];
939 if (ret == null) ret = d;
940 else ret = [ "seq", d, ret ];
943 if (w.parent()[0] == "for-in")
944 return [ "name", defs[0][0] ];
947 return [ "stat", ret ];
949 function _toplevel(body) {
950 return [ this[0], do_body(body, this.scope) ];
952 return w.with_walkers({
953 "function": function(name, args, body){
954 for (var i = args.length; --i >= 0 && !body.scope.references(args[i]);)
956 if (!body.scope.references(name)) name = null;
957 return [ this[0], name, args, do_body(body, body.scope) ];
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]);)
963 return [ this[0], name, args, do_body(body, body.scope) ];
966 "toplevel": _toplevel
968 return walk(ast_add_scope(ast));
972 function ast_squeeze(ast, options) {
973 options = defaults(options, {
980 var w = ast_walker(), walk = w.walk, scope;
983 var not_c = [ "unary-prefix", "!", c ];
986 return c[1] == "!" && boolean_expr(c[2]) ? c[2] : not_c;
989 c[c.length - 1] = negate(c[c.length - 1]);
992 return best_of(not_c, [ "conditional", c[1], negate(c[2]), negate(c[3]) ]);
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 ];
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) ]);
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 ];
1020 [ "conditional", c, t, e ],
1021 [ "conditional", negate(c), e, t ]
1022 ) : [ "binary", "&&", c, t ];
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);
1032 function with_scope(s, cont) {
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" ];
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))
1059 return [ this[0], name, args, body ];
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);
1071 statements = statements.reduce(function(a, stat){
1072 if (stat[0] == "block") {
1074 a.push.apply(a, stat[1]);
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]);
1095 if (options.dead_code) statements = (function(a, has_quit){
1096 statements.forEach(function(st){
1098 if (st[0] == "function" || st[0] == "defun") {
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] ]);
1111 else if (!options.no_warnings)
1112 warn_unreachable(st);
1116 if (member(st[0], [ "return", "throw", "break", "continue" ]))
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] ];
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])
1137 a.splice(a.length - 2, 2,
1139 [ "seq", a[a.length-2][1], a[a.length-1][1] ]]);
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) ]));
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) ]));
1168 function make_if(c, t, e) {
1169 return when_constant(c, function(ast, val){
1172 warn_unreachable(e);
1173 return t || [ "block" ];
1176 warn_unreachable(t);
1177 return e || [ "block" ];
1180 return make_real_if(c, t, e);
1184 function make_real_if(c, t, e) {
1193 } else if (empty(e)) {
1196 // if we have both else and then, maybe it makes sense to switch them?
1198 var a = gen_code(c);
1200 var b = gen_code(n);
1201 if (b.length < a.length) {
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] ]));
1215 else if (t[0] == "stat") {
1217 if (e[0] == "stat") {
1218 ret = best_of(ret, [ "stat", make_conditional(c, t[1], e[1]) ]);
1222 ret = best_of(ret, [ "stat", make_conditional(c, t[1]) ]);
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] ) ]);
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]);
1236 ret = walk([ "block", ret ]);
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]);
1245 ret = walk([ "block", ret ]);
1250 function _do_while(cond, body) {
1251 return when_constant(cond, function(cond, val){
1253 warn_unreachable(body);
1256 return [ "for", null, null, null, walk(body) ];
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) ] ];
1272 "toplevel": function(body) {
1273 return [ "toplevel", with_scope(this.scope, function(){
1274 return tighten(body);
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])
1286 return [ branch[0] ? walk(branch[0]) : null, block ];
1289 "function": _lambda,
1291 "block": function(body) {
1292 if (body) return rmblock([ "block", tighten(body) ]);
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);
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];
1309 "conditional": function(c, t, e) {
1310 return make_conditional(walk(c), walk(t), walk(e));
1312 "try": function(t, c, f) {
1316 c != null ? [ c[0], tighten(c[1]) ] : null,
1317 f != null ? tighten(f) : null
1320 "unary-prefix": function(op, expr) {
1322 var ret = [ "unary-prefix", op, expr ];
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 });
1329 "name": function(name) {
1331 case "true": return [ "unary-prefix", "!", [ "num", 0 ]];
1332 case "false": return [ "unary-prefix", "!", [ "num", 1 ]];
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] ]
1345 return [ this[0], op, lvalue, rvalue ];
1348 for (var i = 0; i < 2; ++i) {
1349 ast = prepare_ifs(ast);
1350 ast = ast_add_scope(ast);
1357 /* -----[ re-generate code from the AST ]----- */
1359 var DOT_CALL_NO_PARENS = jsp.array_to_hash([
1371 function make_string(str, ascii_only) {
1373 str = str.replace(/[\\\b\f\n\r\t\x22\x27\u2028\u2029\0]/g, function(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";
1389 if (ascii_only) str = to_ascii(str);
1390 if (dq > sq) return "'" + str.replace(/\x27/g, "\\'") + "'";
1391 else return '"' + str.replace(/\x22/g, '\\"') + '"';
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;
1402 var SPLICE_NEEDS_BRACKETS = jsp.array_to_hash([ "if", "while", "do", "for", "for-in", "with" ]);
1404 function gen_code(ast, options) {
1405 options = defaults(options, {
1409 space_colon : false,
1412 inline_script: false
1414 var beautify = !!options.beautify;
1415 var indentation = 0,
1416 newline = beautify ? "\n" : "",
1417 space = beautify ? " " : "";
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");
1426 function make_name(name) {
1427 name = name.toString();
1428 if (options.ascii_only)
1429 name = to_ascii(name);
1433 function indent(line) {
1437 line = repeat_string(" ", options.indent_start + indentation * options.indent_level) + line;
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; }
1448 function add_spaces(a) {
1452 for (var i = 0; i < a.length; ++i) {
1453 var next = a[i + 1];
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())))) {
1464 function add_commas(a) {
1465 return a.join("," + space);
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 + ")";
1478 function best_of(a) {
1479 if (a.length == 1) {
1482 if (a.length == 2) {
1485 return a.length <= b.length ? a : b;
1487 return best_of([ a[0], best_of(a.slice(1)) ]);
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();
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)) {
1512 return !HOP(DOT_CALL_NO_PARENS, expr[0]);
1515 function make_num(num) {
1516 var str = num.toString(10), a = [ str.replace(/^0\./, ".") ], m;
1517 if (Math.floor(num) === num) {
1519 a.push("0x" + num.toString(16).toLowerCase(), // probably pointless
1520 "0" + num.toString(8)); // same.
1522 a.push("-0x" + (-num).toString(16).toLowerCase(), // probably pointless
1523 "-0" + (-num).toString(8)); // same.
1525 if ((m = /^(.*?)(0+)$/.exec(num))) {
1526 a.push(m[1] + "e" + m[2].length);
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(".")));
1535 var w = ast_walker();
1537 return w.with_walkers({
1538 "string": encode_string,
1541 "toplevel": function(statements) {
1542 return make_block_statements(statements)
1543 .join(newline + newline);
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);
1551 return MAP(make_block_statements(statements, true),
1553 // the first line is already indented
1554 return i > 0 ? indent(line) : line;
1558 "block": make_block,
1559 "var": function(defs) {
1560 return "var " + add_commas(MAP(defs, make_1vardef)) + ";";
1562 "const": function(defs) {
1563 return "const " + add_commas(MAP(defs, make_1vardef)) + ";";
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);
1571 "throw": function(expr) {
1572 return add_spaces([ "throw", make(expr) ]) + ";";
1574 "new": function(ctor, args) {
1575 args = args.length > 0 ? "(" + add_commas(MAP(args, function(expr){
1576 return parenthesize(expr, "seq");
1578 return add_spaces([ "new", parenthesize(ctor, "seq", "binary", "conditional", "assign", function(expr){
1579 var w = ast_walker(), has_call = {};
1582 "call": function() { throw has_call },
1583 "function": function() { return this }
1588 if (ex === has_call)
1594 "switch": function(expr, body) {
1595 return add_spaces([ "switch", "(" + make(expr) + ")", make_switch_block(body) ]);
1597 "break": function(label) {
1600 out += " " + make_name(label);
1603 "continue": function(label) {
1604 var out = "continue";
1606 out += " " + make_name(label);
1609 "conditional": function(co, th, el) {
1610 return add_spaces([ parenthesize(co, "assign", "seq", "conditional"), "?",
1611 parenthesize(th, "seq"), ":",
1612 parenthesize(el, "seq") ]);
1614 "assign": function(op, lvalue, rvalue) {
1615 if (op && op !== true) op += "=";
1617 return add_spaces([ make(lvalue), op, parenthesize(rvalue, "seq") ]);
1619 "dot": function(expr) {
1620 var out = make(expr), i = 1;
1621 if (expr[0] == "num") {
1622 if (!/\./.test(expr[1]))
1624 } else if (needs_parens(expr))
1625 out = "(" + out + ")";
1626 while (i < arguments.length)
1627 out += "." + make_name(arguments[i++]);
1630 "call": function(func, args) {
1632 if (f.charAt(0) != "(" && needs_parens(func))
1634 return f + "(" + add_commas(MAP(args, function(expr){
1635 return parenthesize(expr, "seq");
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) ];
1643 out.push("else", make(el));
1645 return add_spaces(out);
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);
1657 "for-in": function(vvar, key, hash, block) {
1658 return add_spaces([ "for", "(" +
1659 (vvar ? make(vvar).replace(/;+$/, "") : make(key)),
1661 make(hash) + ")", make(block) ]);
1663 "while": function(condition, block) {
1664 return add_spaces([ "while", "(" + make(condition) + ")", make(block) ]);
1666 "do": function(condition, block) {
1667 return add_spaces([ "do", make(block), "while", "(" + make(condition) + ")" ]) + ";";
1669 "return": function(expr) {
1670 var out = [ "return" ];
1671 if (expr != null) out.push(make(expr));
1672 return add_spaces(out) + ";";
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 + ")";
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 + ")";
1689 else if (!beautify && options.inline_script && (operator == "<" || operator == "<<")
1690 && rvalue[0] == "regexp" && /^script/i.test(rvalue[1])) {
1691 right = " " + right;
1693 return add_spaces([ left, operator, right ]);
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;
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;
1707 "sub": function(expr, subscript) {
1708 var hash = make(expr);
1709 if (needs_parens(expr))
1710 hash = "(" + hash + ")";
1711 return hash + "[" + make(subscript) + "]";
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]));
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);
1733 return indent(add_spaces(beautify && options.space_colon
1735 : [ key + ":", val ]));
1736 }).join("," + newline);
1737 }) + newline + indent("}");
1738 return obj_needs_parens ? "(" + out + ")" : out;
1740 "regexp": function(rx, mods) {
1741 return "/" + rx + "/" + mods;
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");
1750 "stat": function(stmt) {
1751 return make(stmt).replace(/;*\s*$/, ";");
1754 return add_commas(MAP(slice(arguments), make));
1756 "label": function(name, block) {
1757 return add_spaces([ make_name(name), ":", make(block) ]);
1759 "with": function(expr, block) {
1760 return add_spaces([ "with", "(" + make(expr) + ")", make(block) ]);
1762 "atom": function(name) {
1763 return make_name(name);
1765 }, function(){ return make(ast) });
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 ]);
1788 // no else, we must add the block
1789 return make([ "block", [ th ]]);
1792 else if (type == "while" || type == "do") b = b[2];
1793 else if (type == "for" || type == "for-in") b = b[4];
1799 function make_function(name, args, body, keyword) {
1800 var out = keyword || "function";
1802 out += " " + make_name(name);
1804 out += "(" + add_commas(MAP(args, make_name)) + ")";
1805 out = add_spaces([ out, make_block(body) ]);
1806 return needs_parens(this) ? "(" + out + ")" : out;
1809 function must_has_semicolon(node) {
1813 return empty(node[2]); // `with' or `while' with empty body?
1816 return empty(node[4]); // `for' with empty body?
1818 if (empty(node[2]) && !node[3]) return true; // `if' with empty `then' and no `else'
1820 if (empty(node[3])) return true; // `else' present but empty
1821 return must_has_semicolon(node[3]); // dive into the `else' branch
1823 return must_has_semicolon(node[2]); // dive into the `then' branch
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);
1832 if (!beautify && i == last && !must_has_semicolon(stat)) {
1833 code = code.replace(/;+\s*$/, "");
1838 return noindent ? a : MAP(a, indent);
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]) + ":" ])
1849 }, 0.5) + (has_body ? newline + with_indent(function(){
1850 return make_block_statements(branch[1]).join(newline);
1852 if (!beautify && has_body && i < n - 1)
1855 }).join(newline) + newline + indent("}");
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("}");
1866 function make_1vardef(def) {
1867 var name = def[0], val = def[1];
1869 name = add_spaces([ make_name(name), "=", parenthesize(val, "seq") ]);
1875 function split_lines(code, max_line_length) {
1877 jsp.parse(function(){
1878 var next_token = jsp.tokenizer(code);
1881 function current_length(tok) {
1882 return tok.pos - last_split;
1884 function split_here(tok) {
1885 last_split = tok.pos;
1886 splits.push(last_split);
1889 var tok = next_token.apply(this, arguments);
1892 if (prev_token.type == "keyword") break out;
1894 if (current_length(tok) > max_line_length) {
1908 custom.context = function() {
1909 return next_token.context.apply(this, arguments);
1913 return splits.map(function(pos, i){
1914 return code.substring(pos, splits[i + 1] || code.length);
1918 /* -----[ Utilities ]----- */
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);
1925 if (i & 1) d += str;
1929 function defaults(args, defs) {
1933 for (var i in defs) if (HOP(defs, i)) {
1934 ret[i] = (args && HOP(args, i)) ? args[i] : defs[i];
1939 function is_identifier(name) {
1940 return /^[a-z_$][a-z0-9_$]*$/i.test(name)
1942 && !HOP(jsp.KEYWORDS_ATOM, name)
1943 && !HOP(jsp.RESERVED_WORDS, name)
1944 && !HOP(jsp.KEYWORDS, name);
1947 function HOP(obj, prop) {
1948 return Object.prototype.hasOwnProperty.call(obj, prop);
1956 MAP = function(a, f, o) {
1957 var ret = [], top = [], i;
1959 var val = f.call(o, a[i], i);
1960 if (val instanceof AtTop) {
1962 if (val instanceof Splice) {
1963 top.push.apply(top, val.v);
1968 else if (val != skip) {
1969 if (val instanceof Splice) {
1970 ret.push.apply(ret, val.v);
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);
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 };
1987 /* -----[ Exports ]----- */
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;
2001 exports.ast_squeeze_more = require("./squeeze-more").ast_squeeze_more;