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"),
65 is_identifier_char = jsp.is_identifier_char,
66 PRECEDENCE = jsp.PRECEDENCE,
67 OPERATORS = jsp.OPERATORS;
69 /* -----[ helper for AST traversal ]----- */
71 function ast_walker() {
72 function _vardefs(defs) {
73 return [ this[0], MAP(defs, function(def){
80 function _block(statements) {
81 var out = [ this[0] ];
82 if (statements != null)
83 out.push(MAP(statements, walk));
87 "string": function(str) {
88 return [ this[0], str ];
90 "num": function(num) {
91 return [ this[0], num ];
93 "name": function(name) {
94 return [ this[0], name ];
96 "toplevel": function(statements) {
97 return [ this[0], MAP(statements, walk) ];
103 "try": function(t, c, f) {
107 c != null ? [ c[0], MAP(c[1], walk) ] : null,
108 f != null ? MAP(f, walk) : null
111 "throw": function(expr) {
112 return [ this[0], walk(expr) ];
114 "new": function(ctor, args) {
115 return [ this[0], walk(ctor), MAP(args, walk) ];
117 "switch": function(expr, body) {
118 return [ this[0], walk(expr), MAP(body, function(branch){
119 return [ branch[0] ? walk(branch[0]) : null,
120 MAP(branch[1], walk) ];
123 "break": function(label) {
124 return [ this[0], label ];
126 "continue": function(label) {
127 return [ this[0], label ];
129 "conditional": function(cond, t, e) {
130 return [ this[0], walk(cond), walk(t), walk(e) ];
132 "assign": function(op, lvalue, rvalue) {
133 return [ this[0], op, walk(lvalue), walk(rvalue) ];
135 "dot": function(expr) {
136 return [ this[0], walk(expr) ].concat(slice(arguments, 1));
138 "call": function(expr, args) {
139 return [ this[0], walk(expr), MAP(args, walk) ];
141 "function": function(name, args, body) {
142 return [ this[0], name, args.slice(), MAP(body, walk) ];
144 "debugger": function() {
147 "defun": function(name, args, body) {
148 return [ this[0], name, args.slice(), MAP(body, walk) ];
150 "if": function(conditional, t, e) {
151 return [ this[0], walk(conditional), walk(t), walk(e) ];
153 "for": function(init, cond, step, block) {
154 return [ this[0], walk(init), walk(cond), walk(step), walk(block) ];
156 "for-in": function(vvar, key, hash, block) {
157 return [ this[0], walk(vvar), walk(key), walk(hash), walk(block) ];
159 "while": function(cond, block) {
160 return [ this[0], walk(cond), walk(block) ];
162 "do": function(cond, block) {
163 return [ this[0], walk(cond), walk(block) ];
165 "return": function(expr) {
166 return [ this[0], walk(expr) ];
168 "binary": function(op, left, right) {
169 return [ this[0], op, walk(left), walk(right) ];
171 "unary-prefix": function(op, expr) {
172 return [ this[0], op, walk(expr) ];
174 "unary-postfix": function(op, expr) {
175 return [ this[0], op, walk(expr) ];
177 "sub": function(expr, subscript) {
178 return [ this[0], walk(expr), walk(subscript) ];
180 "object": function(props) {
181 return [ this[0], MAP(props, function(p){
183 ? [ p[0], walk(p[1]) ]
184 : [ p[0], walk(p[1]), p[2] ]; // get/set-ter
187 "regexp": function(rx, mods) {
188 return [ this[0], rx, mods ];
190 "array": function(elements) {
191 return [ this[0], MAP(elements, walk) ];
193 "stat": function(stat) {
194 return [ this[0], walk(stat) ];
197 return [ this[0] ].concat(MAP(slice(arguments), walk));
199 "label": function(name, block) {
200 return [ this[0], name, walk(block) ];
202 "with": function(expr, block) {
203 return [ this[0], walk(expr), walk(block) ];
205 "atom": function(name) {
206 return [ this[0], name ];
208 "directive": function(dir) {
209 return [ this[0], dir ];
221 var gen = user[type];
223 var ret = gen.apply(ast, ast.slice(1));
228 return gen.apply(ast, ast.slice(1));
239 return walkers[ast[0]].apply(ast, ast.slice(1));
245 function with_walkers(walkers, cont){
247 for (i in walkers) if (HOP(walkers, i)) {
249 user[i] = walkers[i];
252 for (i in save) if (HOP(save, i)) {
253 if (!save[i]) delete user[i];
254 else user[i] = save[i];
262 with_walkers: with_walkers,
264 return stack[stack.length - 2]; // last one is current node
272 /* -----[ Scope and mangling ]----- */
274 function Scope(parent) {
275 this.names = {}; // names defined in this scope
276 this.mangled = {}; // mangled names (orig.name => mangled)
277 this.rev_mangled = {}; // reverse lookup (mangled => orig.name)
278 this.cname = -1; // current mangled name
279 this.refs = {}; // names referenced from this scope
280 this.uses_with = false; // will become TRUE if with() is detected in this or any subscopes
281 this.uses_eval = false; // will become TRUE if eval() is detected in this or any subscopes
282 this.directives = []; // directives activated from this scope
283 this.parent = parent; // parent scope
284 this.children = []; // sub-scopes
286 this.level = parent.level + 1;
287 parent.children.push(this);
293 function base54_digits() {
294 if (typeof DIGITS_OVERRIDE_FOR_TESTING != "undefined")
295 return DIGITS_OVERRIDE_FOR_TESTING;
297 return "etnrisouaflchpdvmgybwESxTNCkLAOM_DPHBjFIqRUzWXV$JKQGYZ0516372984";
300 var base54 = (function(){
301 var DIGITS = base54_digits();
302 return function(num) {
303 var ret = "", base = 54;
305 ret += DIGITS.charAt(num % base);
306 num = Math.floor(num / base);
314 has: function(name) {
315 for (var s = this; s; s = s.parent)
316 if (HOP(s.names, name))
319 has_mangled: function(mname) {
320 for (var s = this; s; s = s.parent)
321 if (HOP(s.rev_mangled, mname))
327 uses_eval: this.uses_eval,
328 uses_with: this.uses_with
332 next_mangled: function() {
333 // we must be careful that the new mangled name:
335 // 1. doesn't shadow a mangled name from a parent
336 // scope, unless we don't reference the original
337 // name from this scope OR from any sub-scopes!
338 // This will get slow.
340 // 2. doesn't shadow an original name from a parent
341 // scope, in the event that the name is not mangled
342 // in the parent scope and we reference that name
343 // here OR IN ANY SUBSCOPES!
345 // 3. doesn't shadow a name that is referenced but not
346 // defined (possibly global defined elsewhere).
348 var m = base54(++this.cname), prior;
351 prior = this.has_mangled(m);
352 if (prior && this.refs[prior.rev_mangled[m]] === prior)
357 if (prior && prior !== this && this.refs[m] === prior && !prior.has_mangled(m))
361 if (HOP(this.refs, m) && this.refs[m] == null)
364 // I got "do" once. :-/
365 if (!is_identifier(m))
371 set_mangle: function(name, m) {
372 this.rev_mangled[m] = name;
373 return this.mangled[name] = m;
375 get_mangled: function(name, newMangle) {
376 if (this.uses_eval || this.uses_with) return name; // no mangle if eval or with is in use
377 var s = this.has(name);
378 if (!s) return name; // not in visible scope, no mangle
379 if (HOP(s.mangled, name)) return s.mangled[name]; // already mangled in this scope
380 if (!newMangle) return name; // not found and no mangling requested
381 return s.set_mangle(name, s.next_mangled());
383 references: function(name) {
384 return name && !this.parent || this.uses_with || this.uses_eval || this.refs[name];
386 define: function(name, type) {
388 if (type == "var" || !HOP(this.names, name))
389 this.names[name] = type || "var";
393 active_directive: function(dir) {
394 return member(dir, this.directives) || this.parent && this.parent.active_directive(dir);
398 function ast_add_scope(ast) {
400 var current_scope = null;
401 var w = ast_walker(), walk = w.walk;
402 var having_eval = [];
404 function with_new_scope(cont) {
405 current_scope = new Scope(current_scope);
406 current_scope.labels = new Scope();
407 var ret = current_scope.body = cont();
408 ret.scope = current_scope;
409 current_scope = current_scope.parent;
413 function define(name, type) {
414 return current_scope.define(name, type);
417 function reference(name) {
418 current_scope.refs[name] = true;
421 function _lambda(name, args, body) {
422 var is_defun = this[0] == "defun";
423 return [ this[0], is_defun ? define(name, "defun") : name, args, with_new_scope(function(){
424 if (!is_defun) define(name, "lambda");
425 MAP(args, function(name){ define(name, "arg") });
426 return MAP(body, walk);
430 function _vardefs(type) {
431 return function(defs) {
432 MAP(defs, function(d){
434 if (d[1]) reference(d[0]);
439 function _breacont(label) {
441 current_scope.labels.refs[label] = true;
444 return with_new_scope(function(){
446 var ret = w.with_walkers({
449 "label": function(name, stat) { current_scope.labels.define(name) },
451 "continue": _breacont,
452 "with": function(expr, block) {
453 for (var s = current_scope; s; s = s.parent)
456 "var": _vardefs("var"),
457 "const": _vardefs("const"),
458 "try": function(t, c, f) {
459 if (c != null) return [
462 [ define(c[0], "catch"), MAP(c[1], walk) ],
463 f != null ? MAP(f, walk) : null
466 "name": function(name) {
468 having_eval.push(current_scope);
475 // the reason why we need an additional pass here is
476 // that names can be used prior to their definition.
478 // scopes where eval was detected and their parents
479 // are marked with uses_eval, unless they define the
481 MAP(having_eval, function(scope){
482 if (!scope.has("eval")) while (scope) {
483 scope.uses_eval = true;
484 scope = scope.parent;
488 // for referenced names it might be useful to know
489 // their origin scope. current_scope here is the
491 function fixrefs(scope, i) {
492 // do children first; order shouldn't matter
493 for (i = scope.children.length; --i >= 0;)
494 fixrefs(scope.children[i]);
495 for (i in scope.refs) if (HOP(scope.refs, i)) {
496 // find origin scope and propagate the reference to origin
497 for (var origin = scope.has(i), s = scope; s; s = s.parent) {
499 if (s === origin) break;
503 fixrefs(current_scope);
510 /* -----[ mangle names ]----- */
512 function ast_mangle(ast, options) {
513 var w = ast_walker(), walk = w.walk, scope;
514 options = defaults(options, {
522 function get_mangled(name, newMangle) {
523 if (!options.mangle) return name;
524 if (!options.toplevel && !scope.parent) return name; // don't mangle toplevel
525 if (options.except && member(name, options.except))
527 if (options.no_functions && HOP(scope.names, name) &&
528 (scope.names[name] == 'defun' || scope.names[name] == 'lambda'))
530 return scope.get_mangled(name, newMangle);
533 function get_define(name) {
534 if (options.defines) {
535 // we always lookup a defined symbol for the current scope FIRST, so declared
536 // vars trump a DEFINE symbol, but if no such var is found, then match a DEFINE value
537 if (!scope.has(name)) {
538 if (HOP(options.defines, name)) {
539 return options.defines[name];
546 function _lambda(name, args, body) {
547 if (!options.no_functions && options.mangle) {
548 var is_defun = this[0] == "defun", extra;
550 if (is_defun) name = get_mangled(name);
551 else if (body.scope.references(name)) {
553 if (!(scope.uses_eval || scope.uses_with))
554 name = extra[name] = scope.next_mangled();
561 body = with_scope(body.scope, function(){
562 args = MAP(args, function(name){ return get_mangled(name) });
563 return MAP(body, walk);
565 return [ this[0], name, args, body ];
568 function with_scope(s, cont, extra) {
571 if (extra) for (var i in extra) if (HOP(extra, i)) {
572 s.set_mangle(i, extra[i]);
574 for (var i in s.names) if (HOP(s.names, i)) {
575 get_mangled(i, true);
583 function _vardefs(defs) {
584 return [ this[0], MAP(defs, function(d){
585 return [ get_mangled(d[0]), walk(d[1]) ];
589 function _breacont(label) {
590 if (label) return [ this[0], scope.labels.get_mangled(label) ];
593 return w.with_walkers({
595 "defun": function() {
596 // move function declarations to the top when
597 // they are not in some block.
598 var ast = _lambda.apply(this, arguments);
599 switch (w.parent()[0]) {
603 return MAP.at_top(ast);
607 "label": function(label, stat) {
608 if (scope.labels.refs[label]) return [
610 scope.labels.get_mangled(label, true),
616 "continue": _breacont,
619 "name": function(name) {
620 return get_define(name) || [ this[0], get_mangled(name) ];
622 "try": function(t, c, f) {
625 c != null ? [ get_mangled(c[0]), MAP(c[1], walk) ] : null,
626 f != null ? MAP(f, walk) : null ];
628 "toplevel": function(body) {
630 return with_scope(self.scope, function(){
631 return [ self[0], MAP(body, walk) ];
634 "directive": function() {
635 return MAP.at_top(this);
638 return walk(ast_add_scope(ast));
643 - compress foo["bar"] into foo.bar,
644 - remove block brackets {} where possible
645 - join consecutive var declarations
646 - various optimizations for IFs:
647 - if (cond) foo(); else bar(); ==> cond?foo():bar();
648 - if (cond) foo(); ==> cond&&foo();
649 - if (foo) return bar(); else return baz(); ==> return foo?bar():baz(); // also for throw
650 - if (foo) return bar(); else something(); ==> {if(foo)return bar();something()}
653 var warn = function(){};
655 function best_of(ast1, ast2) {
656 return gen_code(ast1).length > gen_code(ast2[0] == "stat" ? ast2[1] : ast2).length ? ast2 : ast1;
659 function last_stat(b) {
660 if (b[0] == "block" && b[1] && b[1].length > 0)
661 return b[1][b[1].length - 1];
666 if (t) switch (last_stat(t)[0]) {
675 function boolean_expr(expr) {
676 return ( (expr[0] == "unary-prefix"
677 && member(expr[1], [ "!", "delete" ])) ||
680 && member(expr[1], [ "in", "instanceof", "==", "!=", "===", "!==", "<", "<=", ">=", ">" ])) ||
683 && member(expr[1], [ "&&", "||" ])
684 && boolean_expr(expr[2])
685 && boolean_expr(expr[3])) ||
687 (expr[0] == "conditional"
688 && boolean_expr(expr[2])
689 && boolean_expr(expr[3])) ||
693 && boolean_expr(expr[3])) ||
696 && boolean_expr(expr[expr.length - 1]))
701 return !b || (b[0] == "block" && (!b[1] || b[1].length == 0));
704 function is_string(node) {
705 return (node[0] == "string" ||
706 node[0] == "unary-prefix" && node[1] == "typeof" ||
707 node[0] == "binary" && node[1] == "+" &&
708 (is_string(node[2]) || is_string(node[3])));
711 var when_constant = (function(){
713 var $NOT_CONSTANT = {};
715 // this can only evaluate constant expressions. If it finds anything
716 // not constant, it throws $NOT_CONSTANT.
717 function evaluate(expr) {
725 case "true": return true;
726 case "false": return false;
727 case "null": return null;
732 case "!": return !evaluate(expr[2]);
733 case "typeof": return typeof evaluate(expr[2]);
734 case "~": return ~evaluate(expr[2]);
735 case "-": return -evaluate(expr[2]);
736 case "+": return +evaluate(expr[2]);
740 var left = expr[2], right = expr[3];
742 case "&&" : return evaluate(left) && evaluate(right);
743 case "||" : return evaluate(left) || evaluate(right);
744 case "|" : return evaluate(left) | evaluate(right);
745 case "&" : return evaluate(left) & evaluate(right);
746 case "^" : return evaluate(left) ^ evaluate(right);
747 case "+" : return evaluate(left) + evaluate(right);
748 case "*" : return evaluate(left) * evaluate(right);
749 case "/" : return evaluate(left) / evaluate(right);
750 case "%" : return evaluate(left) % evaluate(right);
751 case "-" : return evaluate(left) - evaluate(right);
752 case "<<" : return evaluate(left) << evaluate(right);
753 case ">>" : return evaluate(left) >> evaluate(right);
754 case ">>>" : return evaluate(left) >>> evaluate(right);
755 case "==" : return evaluate(left) == evaluate(right);
756 case "===" : return evaluate(left) === evaluate(right);
757 case "!=" : return evaluate(left) != evaluate(right);
758 case "!==" : return evaluate(left) !== evaluate(right);
759 case "<" : return evaluate(left) < evaluate(right);
760 case "<=" : return evaluate(left) <= evaluate(right);
761 case ">" : return evaluate(left) > evaluate(right);
762 case ">=" : return evaluate(left) >= evaluate(right);
763 case "in" : return evaluate(left) in evaluate(right);
764 case "instanceof" : return evaluate(left) instanceof evaluate(right);
770 return function(expr, yes, no) {
772 var val = evaluate(expr), ast;
773 switch (typeof val) {
774 case "string": ast = [ "string", val ]; break;
775 case "number": ast = [ "num", val ]; break;
776 case "boolean": ast = [ "name", String(val) ]; break;
778 if (val === null) { ast = [ "atom", "null" ]; break; }
779 throw new Error("Can't handle constant of type: " + (typeof val));
781 return yes.call(expr, ast, val);
783 if (ex === $NOT_CONSTANT) {
784 if (expr[0] == "binary"
785 && (expr[1] == "===" || expr[1] == "!==")
786 && ((is_string(expr[2]) && is_string(expr[3]))
787 || (boolean_expr(expr[2]) && boolean_expr(expr[3])))) {
788 expr[1] = expr[1].substr(0, 2);
790 else if (no && expr[0] == "binary"
791 && (expr[1] == "||" || expr[1] == "&&")) {
792 // the whole expression is not constant but the lval may be...
794 var lval = evaluate(expr[2]);
795 expr = ((expr[1] == "&&" && (lval ? expr[3] : lval)) ||
796 (expr[1] == "||" && (lval ? lval : expr[3])) ||
799 // IGNORE... lval is not constant
802 return no ? no.call(expr, expr) : null;
810 function warn_unreachable(ast) {
812 warn("Dropping unreachable code: " + gen_code(ast, true));
815 function prepare_ifs(ast) {
816 var w = ast_walker(), walk = w.walk;
817 // In this first pass, we rewrite ifs which abort with no else with an
818 // if-else. For example:
826 // is rewritten into:
834 function redo_if(statements) {
835 statements = MAP(statements, walk);
837 for (var i = 0; i < statements.length; ++i) {
838 var fi = statements[i];
839 if (fi[0] != "if") continue;
844 if (!aborts(t)) continue;
846 var conditional = walk(fi[1]);
848 var e_body = redo_if(statements.slice(i + 1));
849 var e = e_body.length == 1 ? e_body[0] : [ "block", e_body ];
851 return statements.slice(0, i).concat([ [
853 conditional, // conditional
862 function redo_if_lambda(name, args, body) {
863 body = redo_if(body);
864 return [ this[0], name, args, body ];
867 function redo_if_block(statements) {
868 return [ this[0], statements != null ? redo_if(statements) : null ];
871 return w.with_walkers({
872 "defun": redo_if_lambda,
873 "function": redo_if_lambda,
874 "block": redo_if_block,
875 "splice": redo_if_block,
876 "toplevel": function(statements) {
877 return [ this[0], redo_if(statements) ];
879 "try": function(t, c, f) {
883 c != null ? [ c[0], redo_if(c[1]) ] : null,
884 f != null ? redo_if(f) : null
892 function for_side_effects(ast, handler) {
893 var w = ast_walker(), walk = w.walk;
894 var $stop = {}, $restart = {};
895 function stop() { throw $stop };
896 function restart() { throw $restart };
897 function found(){ return handler.call(this, this, w, stop, restart) };
899 if (op == "++" || op == "--")
900 return found.apply(this, arguments);
902 function binary(op) {
903 if (op == "&&" || op == "||")
904 return found.apply(this, arguments);
906 return w.with_walkers({
922 "unary-prefix": unary,
923 "unary-postfix": unary,
924 "conditional": found,
932 if (ex === $stop) break;
933 if (ex === $restart) continue;
939 function ast_lift_variables(ast) {
940 var w = ast_walker(), walk = w.walk, scope;
941 function do_body(body, env) {
944 body = MAP(body, walk);
945 var hash = {}, names = MAP(env.names, function(type, name){
946 if (type != "var") return MAP.skip;
947 if (!env.references(name)) return MAP.skip;
951 if (names.length > 0) {
952 // looking for assignments to any of these variables.
953 // we can save considerable space by moving the definitions
954 // in the var declaration.
955 for_side_effects([ "block", body ], function(ast, walker, stop, restart) {
956 if (ast[0] == "assign"
958 && ast[2][0] == "name"
959 && HOP(hash, ast[2][1])) {
960 // insert the definition into the var declaration
961 for (var i = names.length; --i >= 0;) {
962 if (names[i][0] == ast[2][1]) {
963 if (names[i][1]) // this name already defined, we must stop
965 names[i][1] = ast[3]; // definition
966 names.push(names.splice(i, 1)[0]);
970 // remove this assignment from the AST.
971 var p = walker.parent();
974 a.unshift(0, p.length);
975 p.splice.apply(p, a);
977 else if (p[0] == "stat") {
978 p.splice(0, p.length, "block"); // empty statement
987 body.unshift([ "var", names ]);
992 function _vardefs(defs) {
994 for (var i = defs.length; --i >= 0;) {
997 d = [ "assign", true, [ "name", d[0] ], d[1] ];
998 if (ret == null) ret = d;
999 else ret = [ "seq", d, ret ];
1001 if (ret == null && w.parent()[0] != "for") {
1002 if (w.parent()[0] == "for-in")
1003 return [ "name", defs[0][0] ];
1006 return [ "stat", ret ];
1008 function _toplevel(body) {
1009 return [ this[0], do_body(body, this.scope) ];
1011 return w.with_walkers({
1012 "function": function(name, args, body){
1013 for (var i = args.length; --i >= 0 && !body.scope.references(args[i]);)
1015 if (!body.scope.references(name)) name = null;
1016 return [ this[0], name, args, do_body(body, body.scope) ];
1018 "defun": function(name, args, body){
1019 if (!scope.references(name)) return MAP.skip;
1020 for (var i = args.length; --i >= 0 && !body.scope.references(args[i]);)
1022 return [ this[0], name, args, do_body(body, body.scope) ];
1025 "toplevel": _toplevel
1027 return walk(ast_add_scope(ast));
1031 function ast_squeeze(ast, options) {
1032 ast = squeeze_1(ast, options);
1033 ast = squeeze_2(ast, options);
1037 function squeeze_1(ast, options) {
1038 options = defaults(options, {
1041 no_warnings : false,
1046 var w = ast_walker(), walk = w.walk, scope;
1048 function negate(c) {
1049 var not_c = [ "unary-prefix", "!", c ];
1051 case "unary-prefix":
1052 return c[1] == "!" && boolean_expr(c[2]) ? c[2] : not_c;
1055 c[c.length - 1] = negate(c[c.length - 1]);
1058 return best_of(not_c, [ "conditional", c[1], negate(c[2]), negate(c[3]) ]);
1060 var op = c[1], left = c[2], right = c[3];
1061 if (!options.keep_comps) switch (op) {
1062 case "<=" : return [ "binary", ">", left, right ];
1063 case "<" : return [ "binary", ">=", left, right ];
1064 case ">=" : return [ "binary", "<", left, right ];
1065 case ">" : return [ "binary", "<=", left, right ];
1068 case "==" : return [ "binary", "!=", left, right ];
1069 case "!=" : return [ "binary", "==", left, right ];
1070 case "===" : return [ "binary", "!==", left, right ];
1071 case "!==" : return [ "binary", "===", left, right ];
1072 case "&&" : return best_of(not_c, [ "binary", "||", negate(left), negate(right) ]);
1073 case "||" : return best_of(not_c, [ "binary", "&&", negate(left), negate(right) ]);
1080 function make_conditional(c, t, e) {
1081 var make_real_conditional = function() {
1082 if (c[0] == "unary-prefix" && c[1] == "!") {
1083 return e ? [ "conditional", c[2], e, t ] : [ "binary", "||", c[2], t ];
1086 [ "conditional", c, t, e ],
1087 [ "conditional", negate(c), e, t ]
1088 ) : [ "binary", "&&", c, t ];
1091 // shortcut the conditional if the expression has a constant value
1092 return when_constant(c, function(ast, val){
1093 warn_unreachable(val ? e : t);
1094 return (val ? t : e);
1095 }, make_real_conditional);
1098 function rmblock(block) {
1099 if (block != null && block[0] == "block" && block[1]) {
1100 if (block[1].length == 1)
1101 block = block[1][0];
1102 else if (block[1].length == 0)
1103 block = [ "block" ];
1108 function _lambda(name, args, body) {
1109 return [ this[0], name, args, tighten(body, "lambda") ];
1112 // this function does a few things:
1113 // 1. discard useless blocks
1114 // 2. join consecutive var declarations
1115 // 3. remove obviously dead code
1116 // 4. transform consecutive statements using the comma operator
1117 // 5. if block_type == "lambda" and it detects constructs like if(foo) return ... - rewrite like if (!foo) { ... }
1118 function tighten(statements, block_type) {
1119 statements = MAP(statements, walk);
1121 statements = statements.reduce(function(a, stat){
1122 if (stat[0] == "block") {
1124 a.push.apply(a, stat[1]);
1132 statements = (function(a, prev){
1133 statements.forEach(function(cur){
1134 if (prev && ((cur[0] == "var" && prev[0] == "var") ||
1135 (cur[0] == "const" && prev[0] == "const"))) {
1136 prev[1] = prev[1].concat(cur[1]);
1145 if (options.dead_code) statements = (function(a, has_quit){
1146 statements.forEach(function(st){
1148 if (st[0] == "function" || st[0] == "defun") {
1151 else if (st[0] == "var" || st[0] == "const") {
1152 if (!options.no_warnings)
1153 warn("Variables declared in unreachable code");
1154 st[1] = MAP(st[1], function(def){
1155 if (def[1] && !options.no_warnings)
1156 warn_unreachable([ "assign", true, [ "name", def[0] ], def[1] ]);
1161 else if (!options.no_warnings)
1162 warn_unreachable(st);
1166 if (member(st[0], [ "return", "throw", "break", "continue" ]))
1173 if (options.make_seqs) statements = (function(a, prev) {
1174 statements.forEach(function(cur){
1175 if (prev && prev[0] == "stat" && cur[0] == "stat") {
1176 prev[1] = [ "seq", prev[1], cur[1] ];
1183 && a[a.length-2][0] == "stat"
1184 && (a[a.length-1][0] == "return" || a[a.length-1][0] == "throw")
1185 && a[a.length-1][1])
1187 a.splice(a.length - 2, 2,
1189 [ "seq", a[a.length-2][1], a[a.length-1][1] ]]);
1194 // this increases jQuery by 1K. Probably not such a good idea after all..
1195 // part of this is done in prepare_ifs anyway.
1196 // if (block_type == "lambda") statements = (function(i, a, stat){
1197 // while (i < statements.length) {
1198 // stat = statements[i++];
1199 // if (stat[0] == "if" && !stat[3]) {
1200 // if (stat[2][0] == "return" && stat[2][1] == null) {
1201 // a.push(make_if(negate(stat[1]), [ "block", statements.slice(i) ]));
1204 // var last = last_stat(stat[2]);
1205 // if (last[0] == "return" && last[1] == null) {
1206 // a.push(make_if(stat[1], [ "block", stat[2][1].slice(0, -1) ], [ "block", statements.slice(i) ]));
1218 function make_if(c, t, e) {
1219 return when_constant(c, function(ast, val){
1222 warn_unreachable(e);
1223 return t || [ "block" ];
1226 warn_unreachable(t);
1227 return e || [ "block" ];
1230 return make_real_if(c, t, e);
1234 function abort_else(c, t, e) {
1235 var ret = [ [ "if", negate(c), e ] ];
1236 if (t[0] == "block") {
1237 if (t[1]) ret = ret.concat(t[1]);
1241 return walk([ "block", ret ]);
1244 function make_real_if(c, t, e) {
1249 if (empty(e) && empty(t))
1250 return [ "stat", c ];
1256 } else if (empty(e)) {
1259 // if we have both else and then, maybe it makes sense to switch them?
1261 var a = gen_code(c);
1263 var b = gen_code(n);
1264 if (b.length < a.length) {
1272 var ret = [ "if", c, t, e ];
1273 if (t[0] == "if" && empty(t[3]) && empty(e)) {
1274 ret = best_of(ret, walk([ "if", [ "binary", "&&", c, t[1] ], t[2] ]));
1276 else if (t[0] == "stat") {
1279 ret = best_of(ret, [ "stat", make_conditional(c, t[1], e[1]) ]);
1281 ret = abort_else(c, t, e);
1284 ret = best_of(ret, [ "stat", make_conditional(c, t[1]) ]);
1287 else if (e && t[0] == e[0] && (t[0] == "return" || t[0] == "throw") && t[1] && e[1]) {
1288 ret = best_of(ret, [ t[0], make_conditional(c, t[1], e[1] ) ]);
1290 else if (e && aborts(t)) {
1291 ret = [ [ "if", c, t ] ];
1292 if (e[0] == "block") {
1293 if (e[1]) ret = ret.concat(e[1]);
1298 ret = walk([ "block", ret ]);
1300 else if (t && aborts(e)) {
1301 ret = abort_else(c, t, e);
1306 function _do_while(cond, body) {
1307 return when_constant(cond, function(cond, val){
1309 warn_unreachable(body);
1312 return [ "for", null, null, null, walk(body) ];
1317 return w.with_walkers({
1318 "sub": function(expr, subscript) {
1319 if (subscript[0] == "string") {
1320 var name = subscript[1];
1321 if (is_identifier(name))
1322 return [ "dot", walk(expr), name ];
1323 else if (/^[1-9][0-9]*$/.test(name) || name === "0")
1324 return [ "sub", walk(expr), [ "num", parseInt(name, 10) ] ];
1328 "toplevel": function(body) {
1329 return [ "toplevel", tighten(body) ];
1331 "switch": function(expr, body) {
1332 var last = body.length - 1;
1333 return [ "switch", walk(expr), MAP(body, function(branch, i){
1334 var block = tighten(branch[1]);
1335 if (i == last && block.length > 0) {
1336 var node = block[block.length - 1];
1337 if (node[0] == "break" && !node[1])
1340 return [ branch[0] ? walk(branch[0]) : null, block ];
1343 "function": _lambda,
1345 "block": function(body) {
1346 if (body) return rmblock([ "block", tighten(body) ]);
1348 "binary": function(op, left, right) {
1349 return when_constant([ "binary", op, walk(left), walk(right) ], function yes(c){
1350 return best_of(walk(c), this);
1353 if(op != "==" && op != "!=") return;
1354 var l = walk(left), r = walk(right);
1355 if(l && l[0] == "unary-prefix" && l[1] == "!" && l[2][0] == "num")
1356 left = ['num', +!l[2][1]];
1357 else if (r && r[0] == "unary-prefix" && r[1] == "!" && r[2][0] == "num")
1358 right = ['num', +!r[2][1]];
1359 return ["binary", op, left, right];
1363 "conditional": function(c, t, e) {
1364 return make_conditional(walk(c), walk(t), walk(e));
1366 "try": function(t, c, f) {
1370 c != null ? [ c[0], tighten(c[1]) ] : null,
1371 f != null ? tighten(f) : null
1374 "unary-prefix": function(op, expr) {
1376 var ret = [ "unary-prefix", op, expr ];
1378 ret = best_of(ret, negate(expr));
1379 return when_constant(ret, function(ast, val){
1380 return walk(ast); // it's either true or false, so minifies to !0 or !1
1381 }, function() { return ret });
1383 "name": function(name) {
1385 case "true": return [ "unary-prefix", "!", [ "num", 0 ]];
1386 case "false": return [ "unary-prefix", "!", [ "num", 1 ]];
1390 "assign": function(op, lvalue, rvalue) {
1391 lvalue = walk(lvalue);
1392 rvalue = walk(rvalue);
1393 var okOps = [ '+', '-', '/', '*', '%', '>>', '<<', '>>>', '|', '^', '&' ];
1394 if (op === true && lvalue[0] === "name" && rvalue[0] === "binary" &&
1395 ~okOps.indexOf(rvalue[1]) && rvalue[2][0] === "name" &&
1396 rvalue[2][1] === lvalue[1]) {
1397 return [ this[0], rvalue[1], lvalue, rvalue[3] ]
1399 return [ this[0], op, lvalue, rvalue ];
1401 "call": function(expr, args) {
1403 if (options.unsafe && expr[0] == "dot" && expr[1][0] == "string" && expr[2] == "toString") {
1406 return [ this[0], expr, MAP(args, walk) ];
1408 "num": function (num) {
1410 return [ "binary", "/", num === 1 / 0
1411 ? [ "num", 1 ] : num === -1 / 0
1412 ? [ "unary-prefix", "-", [ "num", 1 ] ]
1413 : [ "num", 0 ], [ "num", 0 ] ];
1415 return [ this[0], num ];
1418 return walk(prepare_ifs(walk(prepare_ifs(ast))));
1422 function squeeze_2(ast, options) {
1423 var w = ast_walker(), walk = w.walk, scope;
1424 function with_scope(s, cont) {
1425 var save = scope, ret;
1431 function lambda(name, args, body) {
1432 return [ this[0], name, args, with_scope(body.scope, curry(MAP, body, walk)) ];
1434 return w.with_walkers({
1435 "directive": function(dir) {
1436 if (scope.active_directive(dir))
1438 scope.directives.push(dir);
1440 "toplevel": function(body) {
1441 return [ this[0], with_scope(this.scope, curry(MAP, body, walk)) ];
1446 return walk(ast_add_scope(ast));
1450 /* -----[ re-generate code from the AST ]----- */
1452 var DOT_CALL_NO_PARENS = jsp.array_to_hash([
1464 function make_string(str, ascii_only) {
1466 str = str.replace(/[\\\b\f\n\r\t\x22\x27\u2028\u2029\0]/g, function(s){
1468 case "\\": return "\\\\";
1469 case "\b": return "\\b";
1470 case "\f": return "\\f";
1471 case "\n": return "\\n";
1472 case "\r": return "\\r";
1473 case "\u2028": return "\\u2028";
1474 case "\u2029": return "\\u2029";
1475 case '"': ++dq; return '"';
1476 case "'": ++sq; return "'";
1477 case "\0": return "\\0";
1481 if (ascii_only) str = to_ascii(str);
1482 if (dq > sq) return "'" + str.replace(/\x27/g, "\\'") + "'";
1483 else return '"' + str.replace(/\x22/g, '\\"') + '"';
1486 function to_ascii(str) {
1487 return str.replace(/[\u0080-\uffff]/g, function(ch) {
1488 var code = ch.charCodeAt(0).toString(16);
1489 while (code.length < 4) code = "0" + code;
1490 return "\\u" + code;
1494 var SPLICE_NEEDS_BRACKETS = jsp.array_to_hash([ "if", "while", "do", "for", "for-in", "with" ]);
1496 function gen_code(ast, options) {
1497 options = defaults(options, {
1501 space_colon : false,
1504 inline_script: false
1506 var beautify = !!options.beautify;
1507 var indentation = 0,
1508 newline = beautify ? "\n" : "",
1509 space = beautify ? " " : "";
1511 function encode_string(str) {
1512 var ret = make_string(str, options.ascii_only);
1513 if (options.inline_script)
1514 ret = ret.replace(/<\x2fscript([>\/\t\n\f\r ])/gi, "<\\/script$1");
1518 function make_name(name) {
1519 name = name.toString();
1520 if (options.ascii_only)
1521 name = to_ascii(name);
1525 function indent(line) {
1529 line = repeat_string(" ", options.indent_start + indentation * options.indent_level) + line;
1533 function with_indent(cont, incr) {
1534 if (incr == null) incr = 1;
1535 indentation += incr;
1536 try { return cont.apply(null, slice(arguments, 1)); }
1537 finally { indentation -= incr; }
1540 function last_char(str) {
1541 str = str.toString();
1542 return str.charAt(str.length - 1);
1545 function first_char(str) {
1546 return str.toString().charAt(0);
1549 function add_spaces(a) {
1553 for (var i = 0; i < a.length; ++i) {
1554 var next = a[i + 1];
1557 ((is_identifier_char(last_char(a[i])) && (is_identifier_char(first_char(next))
1558 || first_char(next) == "\\")) ||
1559 (/[\+\-]$/.test(a[i].toString()) && /^[\+\-]/.test(next.toString()) ||
1560 last_char(a[i]) == "/" && first_char(next) == "/"))) {
1567 function add_commas(a) {
1568 return a.join("," + space);
1571 function parenthesize(expr) {
1572 var gen = make(expr);
1573 for (var i = 1; i < arguments.length; ++i) {
1574 var el = arguments[i];
1575 if ((el instanceof Function && el(expr)) || expr[0] == el)
1576 return "(" + gen + ")";
1581 function best_of(a) {
1582 if (a.length == 1) {
1585 if (a.length == 2) {
1588 return a.length <= b.length ? a : b;
1590 return best_of([ a[0], best_of(a.slice(1)) ]);
1593 function needs_parens(expr) {
1594 if (expr[0] == "function" || expr[0] == "object") {
1595 // dot/call on a literal function requires the
1596 // function literal itself to be parenthesized
1597 // only if it's the first "thing" in a
1598 // statement. This means that the parent is
1599 // "stat", but it could also be a "seq" and
1600 // we're the first in this "seq" and the
1601 // parent is "stat", and so on. Messy stuff,
1602 // but it worths the trouble.
1603 var a = slice(w.stack()), self = a.pop(), p = a.pop();
1605 if (p[0] == "stat") return true;
1606 if (((p[0] == "seq" || p[0] == "call" || p[0] == "dot" || p[0] == "sub" || p[0] == "conditional") && p[1] === self) ||
1607 ((p[0] == "binary" || p[0] == "assign" || p[0] == "unary-postfix") && p[2] === self)) {
1615 return !HOP(DOT_CALL_NO_PARENS, expr[0]);
1618 function make_num(num) {
1619 var str = num.toString(10), a = [ str.replace(/^0\./, ".").replace('e+', 'e') ], m;
1620 if (Math.floor(num) === num) {
1622 a.push("0x" + num.toString(16).toLowerCase(), // probably pointless
1623 "0" + num.toString(8)); // same.
1625 a.push("-0x" + (-num).toString(16).toLowerCase(), // probably pointless
1626 "-0" + (-num).toString(8)); // same.
1628 if ((m = /^(.*?)(0+)$/.exec(num))) {
1629 a.push(m[1] + "e" + m[2].length);
1631 } else if ((m = /^0?\.(0+)(.*)$/.exec(num))) {
1632 a.push(m[2] + "e-" + (m[1].length + m[2].length),
1633 str.substr(str.indexOf(".")));
1638 var w = ast_walker();
1640 return w.with_walkers({
1641 "string": encode_string,
1644 "debugger": function(){ return "debugger;" },
1645 "toplevel": function(statements) {
1646 return make_block_statements(statements)
1647 .join(newline + newline);
1649 "splice": function(statements) {
1650 var parent = w.parent();
1651 if (HOP(SPLICE_NEEDS_BRACKETS, parent)) {
1652 // we need block brackets in this case
1653 return make_block.apply(this, arguments);
1655 return MAP(make_block_statements(statements, true),
1657 // the first line is already indented
1658 return i > 0 ? indent(line) : line;
1662 "block": make_block,
1663 "var": function(defs) {
1664 return "var " + add_commas(MAP(defs, make_1vardef)) + ";";
1666 "const": function(defs) {
1667 return "const " + add_commas(MAP(defs, make_1vardef)) + ";";
1669 "try": function(tr, ca, fi) {
1670 var out = [ "try", make_block(tr) ];
1671 if (ca) out.push("catch", "(" + ca[0] + ")", make_block(ca[1]));
1672 if (fi) out.push("finally", make_block(fi));
1673 return add_spaces(out);
1675 "throw": function(expr) {
1676 return add_spaces([ "throw", make(expr) ]) + ";";
1678 "new": function(ctor, args) {
1679 args = args.length > 0 ? "(" + add_commas(MAP(args, function(expr){
1680 return parenthesize(expr, "seq");
1682 return add_spaces([ "new", parenthesize(ctor, "seq", "binary", "conditional", "assign", function(expr){
1683 var w = ast_walker(), has_call = {};
1686 "call": function() { throw has_call },
1687 "function": function() { return this }
1692 if (ex === has_call)
1698 "switch": function(expr, body) {
1699 return add_spaces([ "switch", "(" + make(expr) + ")", make_switch_block(body) ]);
1701 "break": function(label) {
1704 out += " " + make_name(label);
1707 "continue": function(label) {
1708 var out = "continue";
1710 out += " " + make_name(label);
1713 "conditional": function(co, th, el) {
1714 return add_spaces([ parenthesize(co, "assign", "seq", "conditional"), "?",
1715 parenthesize(th, "seq"), ":",
1716 parenthesize(el, "seq") ]);
1718 "assign": function(op, lvalue, rvalue) {
1719 if (op && op !== true) op += "=";
1721 return add_spaces([ make(lvalue), op, parenthesize(rvalue, "seq") ]);
1723 "dot": function(expr) {
1724 var out = make(expr), i = 1;
1725 if (expr[0] == "num") {
1726 if (!/[a-f.]/i.test(out))
1728 } else if (expr[0] != "function" && needs_parens(expr))
1729 out = "(" + out + ")";
1730 while (i < arguments.length)
1731 out += "." + make_name(arguments[i++]);
1734 "call": function(func, args) {
1736 if (f.charAt(0) != "(" && needs_parens(func))
1738 return f + "(" + add_commas(MAP(args, function(expr){
1739 return parenthesize(expr, "seq");
1742 "function": make_function,
1743 "defun": make_function,
1744 "if": function(co, th, el) {
1745 var out = [ "if", "(" + make(co) + ")", el ? make_then(th) : make(th) ];
1747 out.push("else", make(el));
1749 return add_spaces(out);
1751 "for": function(init, cond, step, block) {
1752 var out = [ "for" ];
1753 init = (init != null ? make(init) : "").replace(/;*\s*$/, ";" + space);
1754 cond = (cond != null ? make(cond) : "").replace(/;*\s*$/, ";" + space);
1755 step = (step != null ? make(step) : "").replace(/;*\s*$/, "");
1756 var args = init + cond + step;
1757 if (args == "; ; ") args = ";;";
1758 out.push("(" + args + ")", make(block));
1759 return add_spaces(out);
1761 "for-in": function(vvar, key, hash, block) {
1762 return add_spaces([ "for", "(" +
1763 (vvar ? make(vvar).replace(/;+$/, "") : make(key)),
1765 make(hash) + ")", make(block) ]);
1767 "while": function(condition, block) {
1768 return add_spaces([ "while", "(" + make(condition) + ")", make(block) ]);
1770 "do": function(condition, block) {
1771 return add_spaces([ "do", make(block), "while", "(" + make(condition) + ")" ]) + ";";
1773 "return": function(expr) {
1774 var out = [ "return" ];
1775 if (expr != null) out.push(make(expr));
1776 return add_spaces(out) + ";";
1778 "binary": function(operator, lvalue, rvalue) {
1779 var left = make(lvalue), right = make(rvalue);
1780 // XXX: I'm pretty sure other cases will bite here.
1781 // we need to be smarter.
1782 // adding parens all the time is the safest bet.
1783 if (member(lvalue[0], [ "assign", "conditional", "seq" ]) ||
1784 lvalue[0] == "binary" && PRECEDENCE[operator] > PRECEDENCE[lvalue[1]] ||
1785 lvalue[0] == "function" && needs_parens(this)) {
1786 left = "(" + left + ")";
1788 if (member(rvalue[0], [ "assign", "conditional", "seq" ]) ||
1789 rvalue[0] == "binary" && PRECEDENCE[operator] >= PRECEDENCE[rvalue[1]] &&
1790 !(rvalue[1] == operator && member(operator, [ "&&", "||", "*" ]))) {
1791 right = "(" + right + ")";
1793 else if (!beautify && options.inline_script && (operator == "<" || operator == "<<")
1794 && rvalue[0] == "regexp" && /^script/i.test(rvalue[1])) {
1795 right = " " + right;
1797 return add_spaces([ left, operator, right ]);
1799 "unary-prefix": function(operator, expr) {
1800 var val = make(expr);
1801 if (!(expr[0] == "num" || (expr[0] == "unary-prefix" && !HOP(OPERATORS, operator + expr[1])) || !needs_parens(expr)))
1802 val = "(" + val + ")";
1803 return operator + (jsp.is_alphanumeric_char(operator.charAt(0)) ? " " : "") + val;
1805 "unary-postfix": function(operator, expr) {
1806 var val = make(expr);
1807 if (!(expr[0] == "num" || (expr[0] == "unary-postfix" && !HOP(OPERATORS, operator + expr[1])) || !needs_parens(expr)))
1808 val = "(" + val + ")";
1809 return val + operator;
1811 "sub": function(expr, subscript) {
1812 var hash = make(expr);
1813 if (needs_parens(expr))
1814 hash = "(" + hash + ")";
1815 return hash + "[" + make(subscript) + "]";
1817 "object": function(props) {
1818 var obj_needs_parens = needs_parens(this);
1819 if (props.length == 0)
1820 return obj_needs_parens ? "({})" : "{}";
1821 var out = "{" + newline + with_indent(function(){
1822 return MAP(props, function(p){
1823 if (p.length == 3) {
1824 // getter/setter. The name is in p[0], the arg.list in p[1][2], the
1825 // body in p[1][3] and type ("get" / "set") in p[2].
1826 return indent(make_function(p[0], p[1][2], p[1][3], p[2], true));
1828 var key = p[0], val = parenthesize(p[1], "seq");
1829 if (options.quote_keys) {
1830 key = encode_string(key);
1831 } else if ((typeof key == "number" || !beautify && +key + "" == key)
1832 && parseFloat(key) >= 0) {
1833 key = make_num(+key);
1834 } else if (!is_identifier(key)) {
1835 key = encode_string(key);
1837 return indent(add_spaces(beautify && options.space_colon
1839 : [ key + ":", val ]));
1840 }).join("," + newline);
1841 }) + newline + indent("}");
1842 return obj_needs_parens ? "(" + out + ")" : out;
1844 "regexp": function(rx, mods) {
1845 if (options.ascii_only) rx = to_ascii(rx);
1846 return "/" + rx + "/" + mods;
1848 "array": function(elements) {
1849 if (elements.length == 0) return "[]";
1850 return add_spaces([ "[", add_commas(MAP(elements, function(el, i){
1851 if (!beautify && el[0] == "atom" && el[1] == "undefined") return i === elements.length - 1 ? "," : "";
1852 return parenthesize(el, "seq");
1855 "stat": function(stmt) {
1857 ? make(stmt).replace(/;*\s*$/, ";")
1861 return add_commas(MAP(slice(arguments), make));
1863 "label": function(name, block) {
1864 return add_spaces([ make_name(name), ":", make(block) ]);
1866 "with": function(expr, block) {
1867 return add_spaces([ "with", "(" + make(expr) + ")", make(block) ]);
1869 "atom": function(name) {
1870 return make_name(name);
1872 "directive": function(dir) {
1873 return make_string(dir) + ";";
1875 }, function(){ return make(ast) });
1877 // The squeezer replaces "block"-s that contain only a single
1878 // statement with the statement itself; technically, the AST
1879 // is correct, but this can create problems when we output an
1880 // IF having an ELSE clause where the THEN clause ends in an
1881 // IF *without* an ELSE block (then the outer ELSE would refer
1882 // to the inner IF). This function checks for this case and
1883 // adds the block brackets if needed.
1884 function make_then(th) {
1885 if (th == null) return ";";
1886 if (th[0] == "do") {
1887 // https://github.com/mishoo/UglifyJS/issues/#issue/57
1888 // IE croaks with "syntax error" on code like this:
1889 // if (foo) do ... while(cond); else ...
1890 // we need block brackets around do/while
1891 return make_block([ th ]);
1898 // no else, we must add the block
1899 return make([ "block", [ th ]]);
1902 else if (type == "while" || type == "do") b = b[2];
1903 else if (type == "for" || type == "for-in") b = b[4];
1909 function make_function(name, args, body, keyword, no_parens) {
1910 var out = keyword || "function";
1912 out += " " + make_name(name);
1914 out += "(" + add_commas(MAP(args, make_name)) + ")";
1915 out = add_spaces([ out, make_block(body) ]);
1916 return (!no_parens && needs_parens(this)) ? "(" + out + ")" : out;
1919 function must_has_semicolon(node) {
1923 return empty(node[2]) || must_has_semicolon(node[2]);
1926 return empty(node[4]) || must_has_semicolon(node[4]);
1928 if (empty(node[2]) && !node[3]) return true; // `if' with empty `then' and no `else'
1930 if (empty(node[3])) return true; // `else' present but empty
1931 return must_has_semicolon(node[3]); // dive into the `else' branch
1933 return must_has_semicolon(node[2]); // dive into the `then' branch
1939 function make_block_statements(statements, noindent) {
1940 for (var a = [], last = statements.length - 1, i = 0; i <= last; ++i) {
1941 var stat = statements[i];
1942 var code = make(stat);
1944 if (!beautify && i == last && !must_has_semicolon(stat)) {
1945 code = code.replace(/;+\s*$/, "");
1950 return noindent ? a : MAP(a, indent);
1953 function make_switch_block(body) {
1954 var n = body.length;
1955 if (n == 0) return "{}";
1956 return "{" + newline + MAP(body, function(branch, i){
1957 var has_body = branch[1].length > 0, code = with_indent(function(){
1958 return indent(branch[0]
1959 ? add_spaces([ "case", make(branch[0]) + ":" ])
1961 }, 0.5) + (has_body ? newline + with_indent(function(){
1962 return make_block_statements(branch[1]).join(newline);
1964 if (!beautify && has_body && i < n - 1)
1967 }).join(newline) + newline + indent("}");
1970 function make_block(statements) {
1971 if (!statements) return ";";
1972 if (statements.length == 0) return "{}";
1973 return "{" + newline + with_indent(function(){
1974 return make_block_statements(statements).join(newline);
1975 }) + newline + indent("}");
1978 function make_1vardef(def) {
1979 var name = def[0], val = def[1];
1981 name = add_spaces([ make_name(name), "=", parenthesize(val, "seq") ]);
1987 function split_lines(code, max_line_length) {
1989 jsp.parse(function(){
1990 var next_token = jsp.tokenizer(code);
1993 function current_length(tok) {
1994 return tok.pos - last_split;
1996 function split_here(tok) {
1997 last_split = tok.pos;
1998 splits.push(last_split);
2001 var tok = next_token.apply(this, arguments);
2004 if (prev_token.type == "keyword") break out;
2006 if (current_length(tok) > max_line_length) {
2020 custom.context = function() {
2021 return next_token.context.apply(this, arguments);
2025 return splits.map(function(pos, i){
2026 return code.substring(pos, splits[i + 1] || code.length);
2030 /* -----[ Utilities ]----- */
2032 function repeat_string(str, i) {
2033 if (i <= 0) return "";
2034 if (i == 1) return str;
2035 var d = repeat_string(str, i >> 1);
2037 if (i & 1) d += str;
2041 function defaults(args, defs) {
2045 for (var i in defs) if (HOP(defs, i)) {
2046 ret[i] = (args && HOP(args, i)) ? args[i] : defs[i];
2051 function is_identifier(name) {
2052 return /^[a-z_$][a-z0-9_$]*$/i.test(name)
2054 && !HOP(jsp.KEYWORDS_ATOM, name)
2055 && !HOP(jsp.RESERVED_WORDS, name)
2056 && !HOP(jsp.KEYWORDS, name);
2059 function HOP(obj, prop) {
2060 return Object.prototype.hasOwnProperty.call(obj, prop);
2068 MAP = function(a, f, o) {
2069 var ret = [], top = [], i;
2071 var val = f.call(o, a[i], i);
2072 if (val instanceof AtTop) {
2074 if (val instanceof Splice) {
2075 top.push.apply(top, val.v);
2080 else if (val != skip) {
2081 if (val instanceof Splice) {
2082 ret.push.apply(ret, val.v);
2088 if (a instanceof Array) for (i = 0; i < a.length; ++i) doit();
2089 else for (i in a) if (HOP(a, i)) doit();
2090 return top.concat(ret);
2092 MAP.at_top = function(val) { return new AtTop(val) };
2093 MAP.splice = function(val) { return new Splice(val) };
2094 var skip = MAP.skip = {};
2095 function AtTop(val) { this.v = val };
2096 function Splice(val) { this.v = val };
2099 /* -----[ Exports ]----- */
2101 exports.ast_walker = ast_walker;
2102 exports.ast_mangle = ast_mangle;
2103 exports.ast_squeeze = ast_squeeze;
2104 exports.ast_lift_variables = ast_lift_variables;
2105 exports.gen_code = gen_code;
2106 exports.ast_add_scope = ast_add_scope;
2107 exports.set_logger = function(logger) { warn = logger };
2108 exports.make_string = make_string;
2109 exports.split_lines = split_lines;
2113 exports.ast_squeeze_more = require("./squeeze-more").ast_squeeze_more;
2116 // js-indent-level: 4