1/***********************************************************************
2
3  A JavaScript tokenizer / parser / beautifier / compressor.
4  https://github.com/mishoo/UglifyJS2
5
6  -------------------------------- (C) ---------------------------------
7
8                           Author: Mihai Bazon
9                         <mihai.bazon@gmail.com>
10                       http://mihai.bazon.net/blog
11
12  Distributed under the BSD license:
13
14    Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com>
15
16    Redistribution and use in source and binary forms, with or without
17    modification, are permitted provided that the following conditions
18    are met:
19
20        * Redistributions of source code must retain the above
21          copyright notice, this list of conditions and the following
22          disclaimer.
23
24        * Redistributions in binary form must reproduce the above
25          copyright notice, this list of conditions and the following
26          disclaimer in the documentation and/or other materials
27          provided with the distribution.
28
29    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
30    EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
31    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
32    PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
33    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
34    OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
35    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
36    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
37    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
38    TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
39    THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
40    SUCH DAMAGE.
41
42 ***********************************************************************/
43
44"use strict";
45
46var EXPECT_DIRECTIVE = /^$|[;{][\s\n]*$/;
47
48function is_some_comments(comment) {
49    // multiline comment
50    return comment.type == "comment2" && /@preserve|@license|@cc_on/i.test(comment.value);
51}
52
53function OutputStream(options) {
54
55    options = defaults(options, {
56        ascii_only       : false,
57        beautify         : false,
58        bracketize       : false,
59        comments         : false,
60        indent_level     : 4,
61        indent_start     : 0,
62        inline_script    : true,
63        keep_quoted_props: false,
64        max_line_len     : false,
65        preamble         : null,
66        preserve_line    : false,
67        quote_keys       : false,
68        quote_style      : 0,
69        screw_ie8        : true,
70        semicolons       : true,
71        shebang          : true,
72        source_map       : null,
73        space_colon      : true,
74        unescape_regexps : false,
75        width            : 80,
76        wrap_iife        : false,
77    }, true);
78
79    // Convert comment option to RegExp if neccessary and set up comments filter
80    var comment_filter = return_false; // Default case, throw all comments away
81    if (options.comments) {
82        var comments = options.comments;
83        if (typeof options.comments === "string" && /^\/.*\/[a-zA-Z]*$/.test(options.comments)) {
84            var regex_pos = options.comments.lastIndexOf("/");
85            comments = new RegExp(
86                options.comments.substr(1, regex_pos - 1),
87                options.comments.substr(regex_pos + 1)
88            );
89        }
90        if (comments instanceof RegExp) {
91            comment_filter = function(comment) {
92                return comment.type != "comment5" && comments.test(comment.value);
93            };
94        }
95        else if (typeof comments === "function") {
96            comment_filter = function(comment) {
97                return comment.type != "comment5" && comments(this, comment);
98            };
99        }
100        else if (comments === "some") {
101            comment_filter = is_some_comments;
102        } else { // NOTE includes "all" option
103            comment_filter = return_true;
104        }
105    }
106
107    var indentation = 0;
108    var current_col = 0;
109    var current_line = 1;
110    var current_pos = 0;
111    var OUTPUT = "";
112
113    function to_ascii(str, identifier) {
114        return str.replace(/[\u0000-\u001f\u007f-\uffff]/g, function(ch) {
115            var code = ch.charCodeAt(0).toString(16);
116            if (code.length <= 2 && !identifier) {
117                while (code.length < 2) code = "0" + code;
118                return "\\x" + code;
119            } else {
120                while (code.length < 4) code = "0" + code;
121                return "\\u" + code;
122            }
123        });
124    };
125
126    function make_string(str, quote) {
127        var dq = 0, sq = 0;
128        str = str.replace(/[\\\b\f\n\r\v\t\x22\x27\u2028\u2029\0\ufeff]/g,
129          function(s, i){
130            switch (s) {
131              case '"': ++dq; return '"';
132              case "'": ++sq; return "'";
133              case "\\": return "\\\\";
134              case "\n": return "\\n";
135              case "\r": return "\\r";
136              case "\t": return "\\t";
137              case "\b": return "\\b";
138              case "\f": return "\\f";
139              case "\x0B": return options.screw_ie8 ? "\\v" : "\\x0B";
140              case "\u2028": return "\\u2028";
141              case "\u2029": return "\\u2029";
142              case "\ufeff": return "\\ufeff";
143              case "\0":
144                  return /[0-7]/.test(str.charAt(i+1)) ? "\\x00" : "\\0";
145            }
146            return s;
147        });
148        function quote_single() {
149            return "'" + str.replace(/\x27/g, "\\'") + "'";
150        }
151        function quote_double() {
152            return '"' + str.replace(/\x22/g, '\\"') + '"';
153        }
154        if (options.ascii_only) str = to_ascii(str);
155        switch (options.quote_style) {
156          case 1:
157            return quote_single();
158          case 2:
159            return quote_double();
160          case 3:
161            return quote == "'" ? quote_single() : quote_double();
162          default:
163            return dq > sq ? quote_single() : quote_double();
164        }
165    };
166
167    function encode_string(str, quote) {
168        var ret = make_string(str, quote);
169        if (options.inline_script) {
170            ret = ret.replace(/<\x2fscript([>\/\t\n\f\r ])/gi, "<\\/script$1");
171            ret = ret.replace(/\x3c!--/g, "\\x3c!--");
172            ret = ret.replace(/--\x3e/g, "--\\x3e");
173        }
174        return ret;
175    };
176
177    function make_name(name) {
178        name = name.toString();
179        if (options.ascii_only)
180            name = to_ascii(name, true);
181        return name;
182    };
183
184    function make_indent(back) {
185        return repeat_string(" ", options.indent_start + indentation - back * options.indent_level);
186    };
187
188    /* -----[ beautification/minification ]----- */
189
190    var might_need_space = false;
191    var might_need_semicolon = false;
192    var might_add_newline = 0;
193    var last = "";
194
195    var ensure_line_len = options.max_line_len ? function() {
196        if (current_col > options.max_line_len) {
197            if (might_add_newline) {
198                var left = OUTPUT.slice(0, might_add_newline);
199                var right = OUTPUT.slice(might_add_newline);
200                OUTPUT = left + "\n" + right;
201                current_line++;
202                current_pos++;
203                current_col = right.length;
204            }
205            if (current_col > options.max_line_len) {
206                AST_Node.warn("Output exceeds {max_line_len} characters", options);
207            }
208        }
209        might_add_newline = 0;
210    } : noop;
211
212    var requireSemicolonChars = makePredicate("( [ + * / - , .");
213
214    function print(str) {
215        str = String(str);
216        var ch = str.charAt(0);
217        var prev = last.charAt(last.length - 1);
218        if (might_need_semicolon) {
219            might_need_semicolon = false;
220
221            if (prev == ":" && ch == "}" || (!ch || ";}".indexOf(ch) < 0) && prev != ";") {
222                if (options.semicolons || requireSemicolonChars(ch)) {
223                    OUTPUT += ";";
224                    current_col++;
225                    current_pos++;
226                } else {
227                    ensure_line_len();
228                    OUTPUT += "\n";
229                    current_pos++;
230                    current_line++;
231                    current_col = 0;
232
233                    if (/^\s+$/.test(str)) {
234                        // reset the semicolon flag, since we didn't print one
235                        // now and might still have to later
236                        might_need_semicolon = true;
237                    }
238                }
239
240                if (!options.beautify)
241                    might_need_space = false;
242            }
243        }
244
245        if (!options.beautify && options.preserve_line && stack[stack.length - 1]) {
246            var target_line = stack[stack.length - 1].start.line;
247            while (current_line < target_line) {
248                ensure_line_len();
249                OUTPUT += "\n";
250                current_pos++;
251                current_line++;
252                current_col = 0;
253                might_need_space = false;
254            }
255        }
256
257        if (might_need_space) {
258            if ((is_identifier_char(prev)
259                    && (is_identifier_char(ch) || ch == "\\"))
260                || (ch == "/" && ch == prev)
261                || ((ch == "+" || ch == "-") && ch == last))
262            {
263                OUTPUT += " ";
264                current_col++;
265                current_pos++;
266            }
267            might_need_space = false;
268        }
269        OUTPUT += str;
270        current_pos += str.length;
271        var a = str.split(/\r?\n/), n = a.length - 1;
272        current_line += n;
273        current_col += a[0].length;
274        if (n > 0) {
275            ensure_line_len();
276            current_col = a[n].length;
277        }
278        last = str;
279    };
280
281    var space = options.beautify ? function() {
282        print(" ");
283    } : function() {
284        might_need_space = true;
285    };
286
287    var indent = options.beautify ? function(half) {
288        if (options.beautify) {
289            print(make_indent(half ? 0.5 : 0));
290        }
291    } : noop;
292
293    var with_indent = options.beautify ? function(col, cont) {
294        if (col === true) col = next_indent();
295        var save_indentation = indentation;
296        indentation = col;
297        var ret = cont();
298        indentation = save_indentation;
299        return ret;
300    } : function(col, cont) { return cont() };
301
302    var newline = options.beautify ? function() {
303        print("\n");
304    } : options.max_line_len ? function() {
305        ensure_line_len();
306        might_add_newline = OUTPUT.length;
307    } : noop;
308
309    var semicolon = options.beautify ? function() {
310        print(";");
311    } : function() {
312        might_need_semicolon = true;
313    };
314
315    function force_semicolon() {
316        might_need_semicolon = false;
317        print(";");
318    };
319
320    function next_indent() {
321        return indentation + options.indent_level;
322    };
323
324    function with_block(cont) {
325        var ret;
326        print("{");
327        newline();
328        with_indent(next_indent(), function(){
329            ret = cont();
330        });
331        indent();
332        print("}");
333        return ret;
334    };
335
336    function with_parens(cont) {
337        print("(");
338        //XXX: still nice to have that for argument lists
339        //var ret = with_indent(current_col, cont);
340        var ret = cont();
341        print(")");
342        return ret;
343    };
344
345    function with_square(cont) {
346        print("[");
347        //var ret = with_indent(current_col, cont);
348        var ret = cont();
349        print("]");
350        return ret;
351    };
352
353    function comma() {
354        print(",");
355        space();
356    };
357
358    function colon() {
359        print(":");
360        if (options.space_colon) space();
361    };
362
363    var add_mapping = options.source_map ? function(token, name) {
364        try {
365            if (token) options.source_map.add(
366                token.file || "?",
367                current_line, current_col,
368                token.line, token.col,
369                (!name && token.type == "name") ? token.value : name
370            );
371        } catch(ex) {
372            AST_Node.warn("Couldn't figure out mapping for {file}:{line},{col} → {cline},{ccol} [{name}]", {
373                file: token.file,
374                line: token.line,
375                col: token.col,
376                cline: current_line,
377                ccol: current_col,
378                name: name || ""
379            })
380        }
381    } : noop;
382
383    function get() {
384        if (might_add_newline) {
385            ensure_line_len();
386        }
387        return OUTPUT;
388    };
389
390    var stack = [];
391    return {
392        get             : get,
393        toString        : get,
394        indent          : indent,
395        indentation     : function() { return indentation },
396        current_width   : function() { return current_col - indentation },
397        should_break    : function() { return options.width && this.current_width() >= options.width },
398        newline         : newline,
399        print           : print,
400        space           : space,
401        comma           : comma,
402        colon           : colon,
403        last            : function() { return last },
404        semicolon       : semicolon,
405        force_semicolon : force_semicolon,
406        to_ascii        : to_ascii,
407        print_name      : function(name) { print(make_name(name)) },
408        print_string    : function(str, quote, escape_directive) {
409            var encoded = encode_string(str, quote);
410            if (escape_directive === true && encoded.indexOf("\\") === -1) {
411                // Insert semicolons to break directive prologue
412                if (!EXPECT_DIRECTIVE.test(OUTPUT)) {
413                    force_semicolon();
414                }
415                force_semicolon();
416            }
417            print(encoded);
418        },
419        encode_string   : encode_string,
420        next_indent     : next_indent,
421        with_indent     : with_indent,
422        with_block      : with_block,
423        with_parens     : with_parens,
424        with_square     : with_square,
425        add_mapping     : add_mapping,
426        option          : function(opt) { return options[opt] },
427        comment_filter  : comment_filter,
428        line            : function() { return current_line },
429        col             : function() { return current_col },
430        pos             : function() { return current_pos },
431        push_node       : function(node) { stack.push(node) },
432        pop_node        : function() { return stack.pop() },
433        parent          : function(n) {
434            return stack[stack.length - 2 - (n || 0)];
435        }
436    };
437
438};
439
440/* -----[ code generators ]----- */
441
442(function(){
443
444    /* -----[ utils ]----- */
445
446    function DEFPRINT(nodetype, generator) {
447        nodetype.DEFMETHOD("_codegen", generator);
448    };
449
450    var use_asm = false;
451    var in_directive = false;
452
453    AST_Node.DEFMETHOD("print", function(stream, force_parens){
454        var self = this, generator = self._codegen, prev_use_asm = use_asm;
455        if (self instanceof AST_Directive && self.value == "use asm" && stream.parent() instanceof AST_Scope) {
456            use_asm = true;
457        }
458        function doit() {
459            self.add_comments(stream);
460            self.add_source_map(stream);
461            generator(self, stream);
462        }
463        stream.push_node(self);
464        if (force_parens || self.needs_parens(stream)) {
465            stream.with_parens(doit);
466        } else {
467            doit();
468        }
469        stream.pop_node();
470        if (self instanceof AST_Scope) {
471            use_asm = prev_use_asm;
472        }
473    });
474
475    AST_Node.DEFMETHOD("print_to_string", function(options){
476        var s = OutputStream(options);
477        if (!options) s._readonly = true;
478        this.print(s);
479        return s.get();
480    });
481
482    /* -----[ comments ]----- */
483
484    AST_Node.DEFMETHOD("add_comments", function(output){
485        if (output._readonly) return;
486        var self = this;
487        var start = self.start;
488        if (start && !start._comments_dumped) {
489            start._comments_dumped = true;
490            var comments = start.comments_before || [];
491
492            // XXX: ugly fix for https://github.com/mishoo/UglifyJS2/issues/112
493            //               and https://github.com/mishoo/UglifyJS2/issues/372
494            if (self instanceof AST_Exit && self.value) {
495                self.value.walk(new TreeWalker(function(node){
496                    if (node.start && node.start.comments_before) {
497                        comments = comments.concat(node.start.comments_before);
498                        node.start.comments_before = [];
499                    }
500                    if (node instanceof AST_Function ||
501                        node instanceof AST_Array ||
502                        node instanceof AST_Object)
503                    {
504                        return true; // don't go inside.
505                    }
506                }));
507            }
508
509            if (output.pos() == 0) {
510                if (comments.length > 0 && output.option("shebang") && comments[0].type == "comment5") {
511                    output.print("#!" + comments.shift().value + "\n");
512                    output.indent();
513                }
514                var preamble = output.option("preamble");
515                if (preamble) {
516                    output.print(preamble.replace(/\r\n?|[\n\u2028\u2029]|\s*$/g, "\n"));
517                }
518            }
519
520            comments = comments.filter(output.comment_filter, self);
521
522            // Keep single line comments after nlb, after nlb
523            if (!output.option("beautify") && comments.length > 0 &&
524                /comment[134]/.test(comments[0].type) &&
525                output.col() !== 0 && comments[0].nlb)
526            {
527                output.print("\n");
528            }
529
530            comments.forEach(function(c){
531                if (/comment[134]/.test(c.type)) {
532                    output.print("//" + c.value + "\n");
533                    output.indent();
534                }
535                else if (c.type == "comment2") {
536                    output.print("/*" + c.value + "*/");
537                    if (start.nlb) {
538                        output.print("\n");
539                        output.indent();
540                    } else {
541                        output.space();
542                    }
543                }
544            });
545        }
546    });
547
548    /* -----[ PARENTHESES ]----- */
549
550    function PARENS(nodetype, func) {
551        if (Array.isArray(nodetype)) {
552            nodetype.forEach(function(nodetype){
553                PARENS(nodetype, func);
554            });
555        } else {
556            nodetype.DEFMETHOD("needs_parens", func);
557        }
558    };
559
560    PARENS(AST_Node, function(){
561        return false;
562    });
563
564    // a function expression needs parens around it when it's provably
565    // the first token to appear in a statement.
566    PARENS(AST_Function, function(output){
567        if (first_in_statement(output)) {
568            return true;
569        }
570
571        if (output.option('wrap_iife')) {
572            var p = output.parent();
573            return p instanceof AST_Call && p.expression === this;
574        }
575
576        return false;
577    });
578
579    // same goes for an object literal, because otherwise it would be
580    // interpreted as a block of code.
581    PARENS(AST_Object, function(output){
582        return first_in_statement(output);
583    });
584
585    PARENS(AST_Unary, function(output){
586        var p = output.parent();
587        return p instanceof AST_PropAccess && p.expression === this
588            || p instanceof AST_Call && p.expression === this;
589    });
590
591    PARENS(AST_Seq, function(output){
592        var p = output.parent();
593        return p instanceof AST_Call             // (foo, bar)() or foo(1, (2, 3), 4)
594            || p instanceof AST_Unary            // !(foo, bar, baz)
595            || p instanceof AST_Binary           // 1 + (2, 3) + 4 ==> 8
596            || p instanceof AST_VarDef           // var a = (1, 2), b = a + a; ==> b == 4
597            || p instanceof AST_PropAccess       // (1, {foo:2}).foo or (1, {foo:2})["foo"] ==> 2
598            || p instanceof AST_Array            // [ 1, (2, 3), 4 ] ==> [ 1, 3, 4 ]
599            || p instanceof AST_ObjectProperty   // { foo: (1, 2) }.foo ==> 2
600            || p instanceof AST_Conditional      /* (false, true) ? (a = 10, b = 20) : (c = 30)
601                                                  * ==> 20 (side effect, set a := 10 and b := 20) */
602        ;
603    });
604
605    PARENS(AST_Binary, function(output){
606        var p = output.parent();
607        // (foo && bar)()
608        if (p instanceof AST_Call && p.expression === this)
609            return true;
610        // typeof (foo && bar)
611        if (p instanceof AST_Unary)
612            return true;
613        // (foo && bar)["prop"], (foo && bar).prop
614        if (p instanceof AST_PropAccess && p.expression === this)
615            return true;
616        // this deals with precedence: 3 * (2 + 1)
617        if (p instanceof AST_Binary) {
618            var po = p.operator, pp = PRECEDENCE[po];
619            var so = this.operator, sp = PRECEDENCE[so];
620            if (pp > sp
621                || (pp == sp
622                    && this === p.right)) {
623                return true;
624            }
625        }
626    });
627
628    PARENS(AST_PropAccess, function(output){
629        var p = output.parent();
630        if (p instanceof AST_New && p.expression === this) {
631            // i.e. new (foo.bar().baz)
632            //
633            // if there's one call into this subtree, then we need
634            // parens around it too, otherwise the call will be
635            // interpreted as passing the arguments to the upper New
636            // expression.
637            try {
638                this.walk(new TreeWalker(function(node){
639                    if (node instanceof AST_Call) throw p;
640                }));
641            } catch(ex) {
642                if (ex !== p) throw ex;
643                return true;
644            }
645        }
646    });
647
648    PARENS(AST_Call, function(output){
649        var p = output.parent(), p1;
650        if (p instanceof AST_New && p.expression === this)
651            return true;
652
653        // workaround for Safari bug.
654        // https://bugs.webkit.org/show_bug.cgi?id=123506
655        return this.expression instanceof AST_Function
656            && p instanceof AST_PropAccess
657            && p.expression === this
658            && (p1 = output.parent(1)) instanceof AST_Assign
659            && p1.left === p;
660    });
661
662    PARENS(AST_New, function(output){
663        var p = output.parent();
664        if (!need_constructor_parens(this, output)
665            && (p instanceof AST_PropAccess // (new Date).getTime(), (new Date)["getTime"]()
666                || p instanceof AST_Call && p.expression === this)) // (new foo)(bar)
667            return true;
668    });
669
670    PARENS(AST_Number, function(output){
671        var p = output.parent();
672        if (p instanceof AST_PropAccess && p.expression === this) {
673            var value = this.getValue();
674            if (value < 0 || /^0/.test(make_num(value))) {
675                return true;
676            }
677        }
678    });
679
680    PARENS([ AST_Assign, AST_Conditional ], function (output){
681        var p = output.parent();
682        // !(a = false) → true
683        if (p instanceof AST_Unary)
684            return true;
685        // 1 + (a = 2) + 3 → 6, side effect setting a = 2
686        if (p instanceof AST_Binary && !(p instanceof AST_Assign))
687            return true;
688        // (a = func)() —or— new (a = Object)()
689        if (p instanceof AST_Call && p.expression === this)
690            return true;
691        // (a = foo) ? bar : baz
692        if (p instanceof AST_Conditional && p.condition === this)
693            return true;
694        // (a = foo)["prop"] —or— (a = foo).prop
695        if (p instanceof AST_PropAccess && p.expression === this)
696            return true;
697    });
698
699    /* -----[ PRINTERS ]----- */
700
701    DEFPRINT(AST_Directive, function(self, output){
702        output.print_string(self.value, self.quote);
703        output.semicolon();
704    });
705    DEFPRINT(AST_Debugger, function(self, output){
706        output.print("debugger");
707        output.semicolon();
708    });
709
710    /* -----[ statements ]----- */
711
712    function display_body(body, is_toplevel, output, allow_directives) {
713        var last = body.length - 1;
714        in_directive = allow_directives;
715        body.forEach(function(stmt, i){
716            if (in_directive === true && !(stmt instanceof AST_Directive ||
717                stmt instanceof AST_EmptyStatement ||
718                (stmt instanceof AST_SimpleStatement && stmt.body instanceof AST_String)
719            )) {
720                in_directive = false;
721            }
722            if (!(stmt instanceof AST_EmptyStatement)) {
723                output.indent();
724                stmt.print(output);
725                if (!(i == last && is_toplevel)) {
726                    output.newline();
727                    if (is_toplevel) output.newline();
728                }
729            }
730            if (in_directive === true &&
731                stmt instanceof AST_SimpleStatement &&
732                stmt.body instanceof AST_String
733            ) {
734                in_directive = false;
735            }
736        });
737        in_directive = false;
738    };
739
740    AST_StatementWithBody.DEFMETHOD("_do_print_body", function(output){
741        force_statement(this.body, output);
742    });
743
744    DEFPRINT(AST_Statement, function(self, output){
745        self.body.print(output);
746        output.semicolon();
747    });
748    DEFPRINT(AST_Toplevel, function(self, output){
749        display_body(self.body, true, output, true);
750        output.print("");
751    });
752    DEFPRINT(AST_LabeledStatement, function(self, output){
753        self.label.print(output);
754        output.colon();
755        self.body.print(output);
756    });
757    DEFPRINT(AST_SimpleStatement, function(self, output){
758        self.body.print(output);
759        output.semicolon();
760    });
761    function print_bracketed(body, output, allow_directives) {
762        if (body.length > 0) output.with_block(function(){
763            display_body(body, false, output, allow_directives);
764        });
765        else output.print("{}");
766    };
767    DEFPRINT(AST_BlockStatement, function(self, output){
768        print_bracketed(self.body, output);
769    });
770    DEFPRINT(AST_EmptyStatement, function(self, output){
771        output.semicolon();
772    });
773    DEFPRINT(AST_Do, function(self, output){
774        output.print("do");
775        output.space();
776        make_block(self.body, output);
777        output.space();
778        output.print("while");
779        output.space();
780        output.with_parens(function(){
781            self.condition.print(output);
782        });
783        output.semicolon();
784    });
785    DEFPRINT(AST_While, function(self, output){
786        output.print("while");
787        output.space();
788        output.with_parens(function(){
789            self.condition.print(output);
790        });
791        output.space();
792        self._do_print_body(output);
793    });
794    DEFPRINT(AST_For, function(self, output){
795        output.print("for");
796        output.space();
797        output.with_parens(function(){
798            if (self.init) {
799                if (self.init instanceof AST_Definitions) {
800                    self.init.print(output);
801                } else {
802                    parenthesize_for_noin(self.init, output, true);
803                }
804                output.print(";");
805                output.space();
806            } else {
807                output.print(";");
808            }
809            if (self.condition) {
810                self.condition.print(output);
811                output.print(";");
812                output.space();
813            } else {
814                output.print(";");
815            }
816            if (self.step) {
817                self.step.print(output);
818            }
819        });
820        output.space();
821        self._do_print_body(output);
822    });
823    DEFPRINT(AST_ForIn, function(self, output){
824        output.print("for");
825        output.space();
826        output.with_parens(function(){
827            self.init.print(output);
828            output.space();
829            output.print("in");
830            output.space();
831            self.object.print(output);
832        });
833        output.space();
834        self._do_print_body(output);
835    });
836    DEFPRINT(AST_With, function(self, output){
837        output.print("with");
838        output.space();
839        output.with_parens(function(){
840            self.expression.print(output);
841        });
842        output.space();
843        self._do_print_body(output);
844    });
845
846    /* -----[ functions ]----- */
847    AST_Lambda.DEFMETHOD("_do_print", function(output, nokeyword){
848        var self = this;
849        if (!nokeyword) {
850            output.print("function");
851        }
852        if (self.name) {
853            output.space();
854            self.name.print(output);
855        }
856        output.with_parens(function(){
857            self.argnames.forEach(function(arg, i){
858                if (i) output.comma();
859                arg.print(output);
860            });
861        });
862        output.space();
863        print_bracketed(self.body, output, true);
864    });
865    DEFPRINT(AST_Lambda, function(self, output){
866        self._do_print(output);
867    });
868
869    /* -----[ exits ]----- */
870    AST_Exit.DEFMETHOD("_do_print", function(output, kind){
871        output.print(kind);
872        if (this.value) {
873            output.space();
874            this.value.print(output);
875        }
876        output.semicolon();
877    });
878    DEFPRINT(AST_Return, function(self, output){
879        self._do_print(output, "return");
880    });
881    DEFPRINT(AST_Throw, function(self, output){
882        self._do_print(output, "throw");
883    });
884
885    /* -----[ loop control ]----- */
886    AST_LoopControl.DEFMETHOD("_do_print", function(output, kind){
887        output.print(kind);
888        if (this.label) {
889            output.space();
890            this.label.print(output);
891        }
892        output.semicolon();
893    });
894    DEFPRINT(AST_Break, function(self, output){
895        self._do_print(output, "break");
896    });
897    DEFPRINT(AST_Continue, function(self, output){
898        self._do_print(output, "continue");
899    });
900
901    /* -----[ if ]----- */
902    function make_then(self, output) {
903        var b = self.body;
904        if (output.option("bracketize")
905            || !output.option("screw_ie8") && b instanceof AST_Do)
906            return make_block(b, output);
907        // The squeezer replaces "block"-s that contain only a single
908        // statement with the statement itself; technically, the AST
909        // is correct, but this can create problems when we output an
910        // IF having an ELSE clause where the THEN clause ends in an
911        // IF *without* an ELSE block (then the outer ELSE would refer
912        // to the inner IF).  This function checks for this case and
913        // adds the block brackets if needed.
914        if (!b) return output.force_semicolon();
915        while (true) {
916            if (b instanceof AST_If) {
917                if (!b.alternative) {
918                    make_block(self.body, output);
919                    return;
920                }
921                b = b.alternative;
922            }
923            else if (b instanceof AST_StatementWithBody) {
924                b = b.body;
925            }
926            else break;
927        }
928        force_statement(self.body, output);
929    };
930    DEFPRINT(AST_If, function(self, output){
931        output.print("if");
932        output.space();
933        output.with_parens(function(){
934            self.condition.print(output);
935        });
936        output.space();
937        if (self.alternative) {
938            make_then(self, output);
939            output.space();
940            output.print("else");
941            output.space();
942            if (self.alternative instanceof AST_If)
943                self.alternative.print(output);
944            else
945                force_statement(self.alternative, output);
946        } else {
947            self._do_print_body(output);
948        }
949    });
950
951    /* -----[ switch ]----- */
952    DEFPRINT(AST_Switch, function(self, output){
953        output.print("switch");
954        output.space();
955        output.with_parens(function(){
956            self.expression.print(output);
957        });
958        output.space();
959        var last = self.body.length - 1;
960        if (last < 0) output.print("{}");
961        else output.with_block(function(){
962            self.body.forEach(function(branch, i){
963                output.indent(true);
964                branch.print(output);
965                if (i < last && branch.body.length > 0)
966                    output.newline();
967            });
968        });
969    });
970    AST_SwitchBranch.DEFMETHOD("_do_print_body", function(output){
971        output.newline();
972        this.body.forEach(function(stmt){
973            output.indent();
974            stmt.print(output);
975            output.newline();
976        });
977    });
978    DEFPRINT(AST_Default, function(self, output){
979        output.print("default:");
980        self._do_print_body(output);
981    });
982    DEFPRINT(AST_Case, function(self, output){
983        output.print("case");
984        output.space();
985        self.expression.print(output);
986        output.print(":");
987        self._do_print_body(output);
988    });
989
990    /* -----[ exceptions ]----- */
991    DEFPRINT(AST_Try, function(self, output){
992        output.print("try");
993        output.space();
994        print_bracketed(self.body, output);
995        if (self.bcatch) {
996            output.space();
997            self.bcatch.print(output);
998        }
999        if (self.bfinally) {
1000            output.space();
1001            self.bfinally.print(output);
1002        }
1003    });
1004    DEFPRINT(AST_Catch, function(self, output){
1005        output.print("catch");
1006        output.space();
1007        output.with_parens(function(){
1008            self.argname.print(output);
1009        });
1010        output.space();
1011        print_bracketed(self.body, output);
1012    });
1013    DEFPRINT(AST_Finally, function(self, output){
1014        output.print("finally");
1015        output.space();
1016        print_bracketed(self.body, output);
1017    });
1018
1019    /* -----[ var/const ]----- */
1020    AST_Definitions.DEFMETHOD("_do_print", function(output, kind){
1021        output.print(kind);
1022        output.space();
1023        this.definitions.forEach(function(def, i){
1024            if (i) output.comma();
1025            def.print(output);
1026        });
1027        var p = output.parent();
1028        var in_for = p instanceof AST_For || p instanceof AST_ForIn;
1029        var avoid_semicolon = in_for && p.init === this;
1030        if (!avoid_semicolon)
1031            output.semicolon();
1032    });
1033    DEFPRINT(AST_Var, function(self, output){
1034        self._do_print(output, "var");
1035    });
1036    DEFPRINT(AST_Const, function(self, output){
1037        self._do_print(output, "const");
1038    });
1039
1040    function parenthesize_for_noin(node, output, noin) {
1041        if (!noin) node.print(output);
1042        else try {
1043            // need to take some precautions here:
1044            //    https://github.com/mishoo/UglifyJS2/issues/60
1045            node.walk(new TreeWalker(function(node){
1046                if (node instanceof AST_Binary && node.operator == "in")
1047                    throw output;
1048            }));
1049            node.print(output);
1050        } catch(ex) {
1051            if (ex !== output) throw ex;
1052            node.print(output, true);
1053        }
1054    };
1055
1056    DEFPRINT(AST_VarDef, function(self, output){
1057        self.name.print(output);
1058        if (self.value) {
1059            output.space();
1060            output.print("=");
1061            output.space();
1062            var p = output.parent(1);
1063            var noin = p instanceof AST_For || p instanceof AST_ForIn;
1064            parenthesize_for_noin(self.value, output, noin);
1065        }
1066    });
1067
1068    /* -----[ other expressions ]----- */
1069    DEFPRINT(AST_Call, function(self, output){
1070        self.expression.print(output);
1071        if (self instanceof AST_New && !need_constructor_parens(self, output))
1072            return;
1073        output.with_parens(function(){
1074            self.args.forEach(function(expr, i){
1075                if (i) output.comma();
1076                expr.print(output);
1077            });
1078        });
1079    });
1080    DEFPRINT(AST_New, function(self, output){
1081        output.print("new");
1082        output.space();
1083        AST_Call.prototype._codegen(self, output);
1084    });
1085
1086    AST_Seq.DEFMETHOD("_do_print", function(output){
1087        this.car.print(output);
1088        if (this.cdr) {
1089            output.comma();
1090            if (output.should_break()) {
1091                output.newline();
1092                output.indent();
1093            }
1094            this.cdr.print(output);
1095        }
1096    });
1097    DEFPRINT(AST_Seq, function(self, output){
1098        self._do_print(output);
1099        // var p = output.parent();
1100        // if (p instanceof AST_Statement) {
1101        //     output.with_indent(output.next_indent(), function(){
1102        //         self._do_print(output);
1103        //     });
1104        // } else {
1105        //     self._do_print(output);
1106        // }
1107    });
1108    DEFPRINT(AST_Dot, function(self, output){
1109        var expr = self.expression;
1110        expr.print(output);
1111        if (expr instanceof AST_Number && expr.getValue() >= 0) {
1112            if (!/[xa-f.)]/i.test(output.last())) {
1113                output.print(".");
1114            }
1115        }
1116        output.print(".");
1117        // the name after dot would be mapped about here.
1118        output.add_mapping(self.end);
1119        output.print_name(self.property);
1120    });
1121    DEFPRINT(AST_Sub, function(self, output){
1122        self.expression.print(output);
1123        output.print("[");
1124        self.property.print(output);
1125        output.print("]");
1126    });
1127    DEFPRINT(AST_UnaryPrefix, function(self, output){
1128        var op = self.operator;
1129        output.print(op);
1130        if (/^[a-z]/i.test(op)
1131            || (/[+-]$/.test(op)
1132                && self.expression instanceof AST_UnaryPrefix
1133                && /^[+-]/.test(self.expression.operator))) {
1134            output.space();
1135        }
1136        self.expression.print(output);
1137    });
1138    DEFPRINT(AST_UnaryPostfix, function(self, output){
1139        self.expression.print(output);
1140        output.print(self.operator);
1141    });
1142    DEFPRINT(AST_Binary, function(self, output){
1143        var op = self.operator;
1144        self.left.print(output);
1145        if (op[0] == ">" /* ">>" ">>>" ">" ">=" */
1146            && self.left instanceof AST_UnaryPostfix
1147            && self.left.operator == "--") {
1148            // space is mandatory to avoid outputting -->
1149            output.print(" ");
1150        } else {
1151            // the space is optional depending on "beautify"
1152            output.space();
1153        }
1154        output.print(op);
1155        if ((op == "<" || op == "<<")
1156            && self.right instanceof AST_UnaryPrefix
1157            && self.right.operator == "!"
1158            && self.right.expression instanceof AST_UnaryPrefix
1159            && self.right.expression.operator == "--") {
1160            // space is mandatory to avoid outputting <!--
1161            output.print(" ");
1162        } else {
1163            // the space is optional depending on "beautify"
1164            output.space();
1165        }
1166        self.right.print(output);
1167    });
1168    DEFPRINT(AST_Conditional, function(self, output){
1169        self.condition.print(output);
1170        output.space();
1171        output.print("?");
1172        output.space();
1173        self.consequent.print(output);
1174        output.space();
1175        output.colon();
1176        self.alternative.print(output);
1177    });
1178
1179    /* -----[ literals ]----- */
1180    DEFPRINT(AST_Array, function(self, output){
1181        output.with_square(function(){
1182            var a = self.elements, len = a.length;
1183            if (len > 0) output.space();
1184            a.forEach(function(exp, i){
1185                if (i) output.comma();
1186                exp.print(output);
1187                // If the final element is a hole, we need to make sure it
1188                // doesn't look like a trailing comma, by inserting an actual
1189                // trailing comma.
1190                if (i === len - 1 && exp instanceof AST_Hole)
1191                  output.comma();
1192            });
1193            if (len > 0) output.space();
1194        });
1195    });
1196    DEFPRINT(AST_Object, function(self, output){
1197        if (self.properties.length > 0) output.with_block(function(){
1198            self.properties.forEach(function(prop, i){
1199                if (i) {
1200                    output.print(",");
1201                    output.newline();
1202                }
1203                output.indent();
1204                prop.print(output);
1205            });
1206            output.newline();
1207        });
1208        else output.print("{}");
1209    });
1210
1211    function print_property_name(key, quote, output) {
1212        if (output.option("quote_keys")) {
1213            output.print_string(key + "");
1214        } else if ((typeof key == "number"
1215                    || !output.option("beautify")
1216                    && +key + "" == key)
1217                   && parseFloat(key) >= 0) {
1218            output.print(make_num(key));
1219        } else if (RESERVED_WORDS(key) ? output.option("screw_ie8") : is_identifier_string(key)) {
1220            if (quote && output.option("keep_quoted_props")) {
1221                output.print_string(key, quote);
1222            } else {
1223                output.print_name(key);
1224            }
1225        } else {
1226            output.print_string(key, quote);
1227        }
1228    }
1229
1230    DEFPRINT(AST_ObjectKeyVal, function(self, output){
1231        print_property_name(self.key, self.quote, output);
1232        output.colon();
1233        self.value.print(output);
1234    });
1235    AST_ObjectProperty.DEFMETHOD("_print_getter_setter", function(type, output) {
1236        output.print(type);
1237        output.space();
1238        print_property_name(this.key.name, this.quote, output);
1239        this.value._do_print(output, true);
1240    });
1241    DEFPRINT(AST_ObjectSetter, function(self, output){
1242        self._print_getter_setter("set", output);
1243    });
1244    DEFPRINT(AST_ObjectGetter, function(self, output){
1245        self._print_getter_setter("get", output);
1246    });
1247    DEFPRINT(AST_Symbol, function(self, output){
1248        var def = self.definition();
1249        output.print_name(def ? def.mangled_name || def.name : self.name);
1250    });
1251    DEFPRINT(AST_Hole, noop);
1252    DEFPRINT(AST_This, function(self, output){
1253        output.print("this");
1254    });
1255    DEFPRINT(AST_Constant, function(self, output){
1256        output.print(self.getValue());
1257    });
1258    DEFPRINT(AST_String, function(self, output){
1259        output.print_string(self.getValue(), self.quote, in_directive);
1260    });
1261    DEFPRINT(AST_Number, function(self, output){
1262        if (use_asm && self.start && self.start.raw != null) {
1263            output.print(self.start.raw);
1264        } else {
1265            output.print(make_num(self.getValue()));
1266        }
1267    });
1268
1269    function regexp_safe_literal(code) {
1270        return [
1271            0x5c   , // \
1272            0x2f   , // /
1273            0x2e   , // .
1274            0x2b   , // +
1275            0x2a   , // *
1276            0x3f   , // ?
1277            0x28   , // (
1278            0x29   , // )
1279            0x5b   , // [
1280            0x5d   , // ]
1281            0x7b   , // {
1282            0x7d   , // }
1283            0x24   , // $
1284            0x5e   , // ^
1285            0x3a   , // :
1286            0x7c   , // |
1287            0x21   , // !
1288            0x0a   , // \n
1289            0x0d   , // \r
1290            0x00   , // \0
1291            0xfeff , // Unicode BOM
1292            0x2028 , // unicode "line separator"
1293            0x2029 , // unicode "paragraph separator"
1294        ].indexOf(code) < 0;
1295    };
1296
1297    DEFPRINT(AST_RegExp, function(self, output){
1298        var str = self.getValue().toString();
1299        if (output.option("ascii_only")) {
1300            str = output.to_ascii(str);
1301        } else if (output.option("unescape_regexps")) {
1302            str = str.split("\\\\").map(function(str){
1303                return str.replace(/\\u[0-9a-fA-F]{4}|\\x[0-9a-fA-F]{2}/g, function(s){
1304                    var code = parseInt(s.substr(2), 16);
1305                    return regexp_safe_literal(code) ? String.fromCharCode(code) : s;
1306                });
1307            }).join("\\\\");
1308        }
1309        output.print(str);
1310        var p = output.parent();
1311        if (p instanceof AST_Binary && /^in/.test(p.operator) && p.left === self)
1312            output.print(" ");
1313    });
1314
1315    function force_statement(stat, output) {
1316        if (output.option("bracketize")) {
1317            make_block(stat, output);
1318        } else {
1319            if (!stat || stat instanceof AST_EmptyStatement)
1320                output.force_semicolon();
1321            else
1322                stat.print(output);
1323        }
1324    };
1325
1326    // self should be AST_New.  decide if we want to show parens or not.
1327    function need_constructor_parens(self, output) {
1328        // Always print parentheses with arguments
1329        if (self.args.length > 0) return true;
1330
1331        return output.option("beautify");
1332    };
1333
1334    function best_of(a) {
1335        var best = a[0], len = best.length;
1336        for (var i = 1; i < a.length; ++i) {
1337            if (a[i].length < len) {
1338                best = a[i];
1339                len = best.length;
1340            }
1341        }
1342        return best;
1343    };
1344
1345    function make_num(num) {
1346        var str = num.toString(10), a = [ str.replace(/^0\./, ".").replace('e+', 'e') ], m;
1347        if (Math.floor(num) === num) {
1348            if (num >= 0) {
1349                a.push("0x" + num.toString(16).toLowerCase(), // probably pointless
1350                       "0" + num.toString(8)); // same.
1351            } else {
1352                a.push("-0x" + (-num).toString(16).toLowerCase(), // probably pointless
1353                       "-0" + (-num).toString(8)); // same.
1354            }
1355            if ((m = /^(.*?)(0+)$/.exec(num))) {
1356                a.push(m[1] + "e" + m[2].length);
1357            }
1358        } else if ((m = /^0?\.(0+)(.*)$/.exec(num))) {
1359            a.push(m[2] + "e-" + (m[1].length + m[2].length),
1360                   str.substr(str.indexOf(".")));
1361        }
1362        return best_of(a);
1363    };
1364
1365    function make_block(stmt, output) {
1366        if (!stmt || stmt instanceof AST_EmptyStatement)
1367            output.print("{}");
1368        else if (stmt instanceof AST_BlockStatement)
1369            stmt.print(output);
1370        else output.with_block(function(){
1371            output.indent();
1372            stmt.print(output);
1373            output.newline();
1374        });
1375    };
1376
1377    /* -----[ source map generators ]----- */
1378
1379    function DEFMAP(nodetype, generator) {
1380        nodetype.DEFMETHOD("add_source_map", function(stream){
1381            generator(this, stream);
1382        });
1383    };
1384
1385    // We could easily add info for ALL nodes, but it seems to me that
1386    // would be quite wasteful, hence this noop in the base class.
1387    DEFMAP(AST_Node, noop);
1388
1389    function basic_sourcemap_gen(self, output) {
1390        output.add_mapping(self.start);
1391    };
1392
1393    // XXX: I'm not exactly sure if we need it for all of these nodes,
1394    // or if we should add even more.
1395
1396    DEFMAP(AST_Directive, basic_sourcemap_gen);
1397    DEFMAP(AST_Debugger, basic_sourcemap_gen);
1398    DEFMAP(AST_Symbol, basic_sourcemap_gen);
1399    DEFMAP(AST_Jump, basic_sourcemap_gen);
1400    DEFMAP(AST_StatementWithBody, basic_sourcemap_gen);
1401    DEFMAP(AST_LabeledStatement, noop); // since the label symbol will mark it
1402    DEFMAP(AST_Lambda, basic_sourcemap_gen);
1403    DEFMAP(AST_Switch, basic_sourcemap_gen);
1404    DEFMAP(AST_SwitchBranch, basic_sourcemap_gen);
1405    DEFMAP(AST_BlockStatement, basic_sourcemap_gen);
1406    DEFMAP(AST_Toplevel, noop);
1407    DEFMAP(AST_New, basic_sourcemap_gen);
1408    DEFMAP(AST_Try, basic_sourcemap_gen);
1409    DEFMAP(AST_Catch, basic_sourcemap_gen);
1410    DEFMAP(AST_Finally, basic_sourcemap_gen);
1411    DEFMAP(AST_Definitions, basic_sourcemap_gen);
1412    DEFMAP(AST_Constant, basic_sourcemap_gen);
1413    DEFMAP(AST_ObjectSetter, function(self, output){
1414        output.add_mapping(self.start, self.key.name);
1415    });
1416    DEFMAP(AST_ObjectGetter, function(self, output){
1417        output.add_mapping(self.start, self.key.name);
1418    });
1419    DEFMAP(AST_ObjectProperty, function(self, output){
1420        output.add_mapping(self.start, self.key);
1421    });
1422
1423})();
1424