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
46function DEFNODE(type, props, methods, base) {
47    if (arguments.length < 4) base = AST_Node;
48    if (!props) props = [];
49    else props = props.split(/\s+/);
50    var self_props = props;
51    if (base && base.PROPS)
52        props = props.concat(base.PROPS);
53    var code = "return function AST_" + type + "(props){ if (props) { ";
54    for (var i = props.length; --i >= 0;) {
55        code += "this." + props[i] + " = props." + props[i] + ";";
56    }
57    var proto = base && new base;
58    if (proto && proto.initialize || (methods && methods.initialize))
59        code += "this.initialize();";
60    code += "}}";
61    var ctor = new Function(code)();
62    if (proto) {
63        ctor.prototype = proto;
64        ctor.BASE = base;
65    }
66    if (base) base.SUBCLASSES.push(ctor);
67    ctor.prototype.CTOR = ctor;
68    ctor.PROPS = props || null;
69    ctor.SELF_PROPS = self_props;
70    ctor.SUBCLASSES = [];
71    if (type) {
72        ctor.prototype.TYPE = ctor.TYPE = type;
73    }
74    if (methods) for (i in methods) if (HOP(methods, i)) {
75        if (/^\$/.test(i)) {
76            ctor[i.substr(1)] = methods[i];
77        } else {
78            ctor.prototype[i] = methods[i];
79        }
80    }
81    ctor.DEFMETHOD = function(name, method) {
82        this.prototype[name] = method;
83    };
84    if (typeof exports !== "undefined") {
85        exports["AST_" + type] = ctor;
86    }
87    return ctor;
88};
89
90var AST_Token = DEFNODE("Token", "type value line col pos endline endcol endpos nlb comments_before file raw", {
91}, null);
92
93var AST_Node = DEFNODE("Node", "start end", {
94    _clone: function(deep) {
95        if (deep) {
96            var self = this.clone();
97            return self.transform(new TreeTransformer(function(node) {
98                if (node !== self) {
99                    return node.clone(true);
100                }
101            }));
102        }
103        return new this.CTOR(this);
104    },
105    clone: function(deep) {
106        return this._clone(deep);
107    },
108    $documentation: "Base class of all AST nodes",
109    $propdoc: {
110        start: "[AST_Token] The first token of this node",
111        end: "[AST_Token] The last token of this node"
112    },
113    _walk: function(visitor) {
114        return visitor._visit(this);
115    },
116    walk: function(visitor) {
117        return this._walk(visitor); // not sure the indirection will be any help
118    }
119}, null);
120
121AST_Node.warn_function = null;
122AST_Node.warn = function(txt, props) {
123    if (AST_Node.warn_function)
124        AST_Node.warn_function(string_template(txt, props));
125};
126
127/* -----[ statements ]----- */
128
129var AST_Statement = DEFNODE("Statement", null, {
130    $documentation: "Base class of all statements",
131});
132
133var AST_Debugger = DEFNODE("Debugger", null, {
134    $documentation: "Represents a debugger statement",
135}, AST_Statement);
136
137var AST_Directive = DEFNODE("Directive", "value scope quote", {
138    $documentation: "Represents a directive, like \"use strict\";",
139    $propdoc: {
140        value: "[string] The value of this directive as a plain string (it's not an AST_String!)",
141        scope: "[AST_Scope/S] The scope that this directive affects",
142        quote: "[string] the original quote character"
143    },
144}, AST_Statement);
145
146var AST_SimpleStatement = DEFNODE("SimpleStatement", "body", {
147    $documentation: "A statement consisting of an expression, i.e. a = 1 + 2",
148    $propdoc: {
149        body: "[AST_Node] an expression node (should not be instanceof AST_Statement)"
150    },
151    _walk: function(visitor) {
152        return visitor._visit(this, function(){
153            this.body._walk(visitor);
154        });
155    }
156}, AST_Statement);
157
158function walk_body(node, visitor) {
159    var body = node.body;
160    if (body instanceof AST_Statement) {
161        body._walk(visitor);
162    }
163    else for (var i = 0, len = body.length; i < len; i++) {
164        body[i]._walk(visitor);
165    }
166};
167
168var AST_Block = DEFNODE("Block", "body", {
169    $documentation: "A body of statements (usually bracketed)",
170    $propdoc: {
171        body: "[AST_Statement*] an array of statements"
172    },
173    _walk: function(visitor) {
174        return visitor._visit(this, function(){
175            walk_body(this, visitor);
176        });
177    }
178}, AST_Statement);
179
180var AST_BlockStatement = DEFNODE("BlockStatement", null, {
181    $documentation: "A block statement",
182}, AST_Block);
183
184var AST_EmptyStatement = DEFNODE("EmptyStatement", null, {
185    $documentation: "The empty statement (empty block or simply a semicolon)",
186    _walk: function(visitor) {
187        return visitor._visit(this);
188    }
189}, AST_Statement);
190
191var AST_StatementWithBody = DEFNODE("StatementWithBody", "body", {
192    $documentation: "Base class for all statements that contain one nested body: `For`, `ForIn`, `Do`, `While`, `With`",
193    $propdoc: {
194        body: "[AST_Statement] the body; this should always be present, even if it's an AST_EmptyStatement"
195    },
196    _walk: function(visitor) {
197        return visitor._visit(this, function(){
198            this.body._walk(visitor);
199        });
200    }
201}, AST_Statement);
202
203var AST_LabeledStatement = DEFNODE("LabeledStatement", "label", {
204    $documentation: "Statement with a label",
205    $propdoc: {
206        label: "[AST_Label] a label definition"
207    },
208    _walk: function(visitor) {
209        return visitor._visit(this, function(){
210            this.label._walk(visitor);
211            this.body._walk(visitor);
212        });
213    },
214    clone: function(deep) {
215        var node = this._clone(deep);
216        if (deep) {
217            var label = node.label;
218            var def = this.label;
219            node.walk(new TreeWalker(function(node) {
220                if (node instanceof AST_LoopControl
221                    && node.label && node.label.thedef === def) {
222                    node.label.thedef = label;
223                    label.references.push(node);
224                }
225            }));
226        }
227        return node;
228    }
229}, AST_StatementWithBody);
230
231var AST_IterationStatement = DEFNODE("IterationStatement", null, {
232    $documentation: "Internal class.  All loops inherit from it."
233}, AST_StatementWithBody);
234
235var AST_DWLoop = DEFNODE("DWLoop", "condition", {
236    $documentation: "Base class for do/while statements",
237    $propdoc: {
238        condition: "[AST_Node] the loop condition.  Should not be instanceof AST_Statement"
239    }
240}, AST_IterationStatement);
241
242var AST_Do = DEFNODE("Do", null, {
243    $documentation: "A `do` statement",
244    _walk: function(visitor) {
245        return visitor._visit(this, function(){
246            this.body._walk(visitor);
247            this.condition._walk(visitor);
248        });
249    }
250}, AST_DWLoop);
251
252var AST_While = DEFNODE("While", null, {
253    $documentation: "A `while` statement",
254    _walk: function(visitor) {
255        return visitor._visit(this, function(){
256            this.condition._walk(visitor);
257            this.body._walk(visitor);
258        });
259    }
260}, AST_DWLoop);
261
262var AST_For = DEFNODE("For", "init condition step", {
263    $documentation: "A `for` statement",
264    $propdoc: {
265        init: "[AST_Node?] the `for` initialization code, or null if empty",
266        condition: "[AST_Node?] the `for` termination clause, or null if empty",
267        step: "[AST_Node?] the `for` update clause, or null if empty"
268    },
269    _walk: function(visitor) {
270        return visitor._visit(this, function(){
271            if (this.init) this.init._walk(visitor);
272            if (this.condition) this.condition._walk(visitor);
273            if (this.step) this.step._walk(visitor);
274            this.body._walk(visitor);
275        });
276    }
277}, AST_IterationStatement);
278
279var AST_ForIn = DEFNODE("ForIn", "init name object", {
280    $documentation: "A `for ... in` statement",
281    $propdoc: {
282        init: "[AST_Node] the `for/in` initialization code",
283        name: "[AST_SymbolRef?] the loop variable, only if `init` is AST_Var",
284        object: "[AST_Node] the object that we're looping through"
285    },
286    _walk: function(visitor) {
287        return visitor._visit(this, function(){
288            this.init._walk(visitor);
289            this.object._walk(visitor);
290            this.body._walk(visitor);
291        });
292    }
293}, AST_IterationStatement);
294
295var AST_With = DEFNODE("With", "expression", {
296    $documentation: "A `with` statement",
297    $propdoc: {
298        expression: "[AST_Node] the `with` expression"
299    },
300    _walk: function(visitor) {
301        return visitor._visit(this, function(){
302            this.expression._walk(visitor);
303            this.body._walk(visitor);
304        });
305    }
306}, AST_StatementWithBody);
307
308/* -----[ scope and functions ]----- */
309
310var AST_Scope = DEFNODE("Scope", "directives variables functions uses_with uses_eval parent_scope enclosed cname", {
311    $documentation: "Base class for all statements introducing a lexical scope",
312    $propdoc: {
313        directives: "[string*/S] an array of directives declared in this scope",
314        variables: "[Object/S] a map of name -> SymbolDef for all variables/functions defined in this scope",
315        functions: "[Object/S] like `variables`, but only lists function declarations",
316        uses_with: "[boolean/S] tells whether this scope uses the `with` statement",
317        uses_eval: "[boolean/S] tells whether this scope contains a direct call to the global `eval`",
318        parent_scope: "[AST_Scope?/S] link to the parent scope",
319        enclosed: "[SymbolDef*/S] a list of all symbol definitions that are accessed from this scope or any subscopes",
320        cname: "[integer/S] current index for mangling variables (used internally by the mangler)",
321    },
322}, AST_Block);
323
324var AST_Toplevel = DEFNODE("Toplevel", "globals", {
325    $documentation: "The toplevel scope",
326    $propdoc: {
327        globals: "[Object/S] a map of name -> SymbolDef for all undeclared names",
328    },
329    wrap_enclose: function(arg_parameter_pairs) {
330        var self = this;
331        var args = [];
332        var parameters = [];
333
334        arg_parameter_pairs.forEach(function(pair) {
335            var splitAt = pair.lastIndexOf(":");
336
337            args.push(pair.substr(0, splitAt));
338            parameters.push(pair.substr(splitAt + 1));
339        });
340
341        var wrapped_tl = "(function(" + parameters.join(",") + "){ '$ORIG'; })(" + args.join(",") + ")";
342        wrapped_tl = parse(wrapped_tl);
343        wrapped_tl = wrapped_tl.transform(new TreeTransformer(function before(node){
344            if (node instanceof AST_Directive && node.value == "$ORIG") {
345                return MAP.splice(self.body);
346            }
347        }));
348        return wrapped_tl;
349    },
350    wrap_commonjs: function(name, export_all) {
351        var self = this;
352        var to_export = [];
353        if (export_all) {
354            self.figure_out_scope();
355            self.walk(new TreeWalker(function(node){
356                if (node instanceof AST_SymbolDeclaration && node.definition().global) {
357                    if (!find_if(function(n){ return n.name == node.name }, to_export))
358                        to_export.push(node);
359                }
360            }));
361        }
362        var wrapped_tl = "(function(exports, global){ '$ORIG'; '$EXPORTS'; global['" + name + "'] = exports; }({}, (function(){return this}())))";
363        wrapped_tl = parse(wrapped_tl);
364        wrapped_tl = wrapped_tl.transform(new TreeTransformer(function before(node){
365            if (node instanceof AST_Directive) {
366                switch (node.value) {
367                  case "$ORIG":
368                    return MAP.splice(self.body);
369                  case "$EXPORTS":
370                    var body = [];
371                    to_export.forEach(function(sym){
372                        body.push(new AST_SimpleStatement({
373                            body: new AST_Assign({
374                                left: new AST_Sub({
375                                    expression: new AST_SymbolRef({ name: "exports" }),
376                                    property: new AST_String({ value: sym.name }),
377                                }),
378                                operator: "=",
379                                right: new AST_SymbolRef(sym),
380                            }),
381                        }));
382                    });
383                    return MAP.splice(body);
384                }
385            }
386        }));
387        return wrapped_tl;
388    }
389}, AST_Scope);
390
391var AST_Lambda = DEFNODE("Lambda", "name argnames uses_arguments", {
392    $documentation: "Base class for functions",
393    $propdoc: {
394        name: "[AST_SymbolDeclaration?] the name of this function",
395        argnames: "[AST_SymbolFunarg*] array of function arguments",
396        uses_arguments: "[boolean/S] tells whether this function accesses the arguments array"
397    },
398    _walk: function(visitor) {
399        return visitor._visit(this, function(){
400            if (this.name) this.name._walk(visitor);
401            var argnames = this.argnames;
402            for (var i = 0, len = argnames.length; i < len; i++) {
403                argnames[i]._walk(visitor);
404            }
405            walk_body(this, visitor);
406        });
407    }
408}, AST_Scope);
409
410var AST_Accessor = DEFNODE("Accessor", null, {
411    $documentation: "A setter/getter function.  The `name` property is always null."
412}, AST_Lambda);
413
414var AST_Function = DEFNODE("Function", null, {
415    $documentation: "A function expression"
416}, AST_Lambda);
417
418var AST_Defun = DEFNODE("Defun", null, {
419    $documentation: "A function definition"
420}, AST_Lambda);
421
422/* -----[ JUMPS ]----- */
423
424var AST_Jump = DEFNODE("Jump", null, {
425    $documentation: "Base class for “jumps” (for now that's `return`, `throw`, `break` and `continue`)"
426}, AST_Statement);
427
428var AST_Exit = DEFNODE("Exit", "value", {
429    $documentation: "Base class for “exits” (`return` and `throw`)",
430    $propdoc: {
431        value: "[AST_Node?] the value returned or thrown by this statement; could be null for AST_Return"
432    },
433    _walk: function(visitor) {
434        return visitor._visit(this, this.value && function(){
435            this.value._walk(visitor);
436        });
437    }
438}, AST_Jump);
439
440var AST_Return = DEFNODE("Return", null, {
441    $documentation: "A `return` statement"
442}, AST_Exit);
443
444var AST_Throw = DEFNODE("Throw", null, {
445    $documentation: "A `throw` statement"
446}, AST_Exit);
447
448var AST_LoopControl = DEFNODE("LoopControl", "label", {
449    $documentation: "Base class for loop control statements (`break` and `continue`)",
450    $propdoc: {
451        label: "[AST_LabelRef?] the label, or null if none",
452    },
453    _walk: function(visitor) {
454        return visitor._visit(this, this.label && function(){
455            this.label._walk(visitor);
456        });
457    }
458}, AST_Jump);
459
460var AST_Break = DEFNODE("Break", null, {
461    $documentation: "A `break` statement"
462}, AST_LoopControl);
463
464var AST_Continue = DEFNODE("Continue", null, {
465    $documentation: "A `continue` statement"
466}, AST_LoopControl);
467
468/* -----[ IF ]----- */
469
470var AST_If = DEFNODE("If", "condition alternative", {
471    $documentation: "A `if` statement",
472    $propdoc: {
473        condition: "[AST_Node] the `if` condition",
474        alternative: "[AST_Statement?] the `else` part, or null if not present"
475    },
476    _walk: function(visitor) {
477        return visitor._visit(this, function(){
478            this.condition._walk(visitor);
479            this.body._walk(visitor);
480            if (this.alternative) this.alternative._walk(visitor);
481        });
482    }
483}, AST_StatementWithBody);
484
485/* -----[ SWITCH ]----- */
486
487var AST_Switch = DEFNODE("Switch", "expression", {
488    $documentation: "A `switch` statement",
489    $propdoc: {
490        expression: "[AST_Node] the `switch` “discriminant”"
491    },
492    _walk: function(visitor) {
493        return visitor._visit(this, function(){
494            this.expression._walk(visitor);
495            walk_body(this, visitor);
496        });
497    }
498}, AST_Block);
499
500var AST_SwitchBranch = DEFNODE("SwitchBranch", null, {
501    $documentation: "Base class for `switch` branches",
502}, AST_Block);
503
504var AST_Default = DEFNODE("Default", null, {
505    $documentation: "A `default` switch branch",
506}, AST_SwitchBranch);
507
508var AST_Case = DEFNODE("Case", "expression", {
509    $documentation: "A `case` switch branch",
510    $propdoc: {
511        expression: "[AST_Node] the `case` expression"
512    },
513    _walk: function(visitor) {
514        return visitor._visit(this, function(){
515            this.expression._walk(visitor);
516            walk_body(this, visitor);
517        });
518    }
519}, AST_SwitchBranch);
520
521/* -----[ EXCEPTIONS ]----- */
522
523var AST_Try = DEFNODE("Try", "bcatch bfinally", {
524    $documentation: "A `try` statement",
525    $propdoc: {
526        bcatch: "[AST_Catch?] the catch block, or null if not present",
527        bfinally: "[AST_Finally?] the finally block, or null if not present"
528    },
529    _walk: function(visitor) {
530        return visitor._visit(this, function(){
531            walk_body(this, visitor);
532            if (this.bcatch) this.bcatch._walk(visitor);
533            if (this.bfinally) this.bfinally._walk(visitor);
534        });
535    }
536}, AST_Block);
537
538var AST_Catch = DEFNODE("Catch", "argname", {
539    $documentation: "A `catch` node; only makes sense as part of a `try` statement",
540    $propdoc: {
541        argname: "[AST_SymbolCatch] symbol for the exception"
542    },
543    _walk: function(visitor) {
544        return visitor._visit(this, function(){
545            this.argname._walk(visitor);
546            walk_body(this, visitor);
547        });
548    }
549}, AST_Block);
550
551var AST_Finally = DEFNODE("Finally", null, {
552    $documentation: "A `finally` node; only makes sense as part of a `try` statement"
553}, AST_Block);
554
555/* -----[ VAR/CONST ]----- */
556
557var AST_Definitions = DEFNODE("Definitions", "definitions", {
558    $documentation: "Base class for `var` or `const` nodes (variable declarations/initializations)",
559    $propdoc: {
560        definitions: "[AST_VarDef*] array of variable definitions"
561    },
562    _walk: function(visitor) {
563        return visitor._visit(this, function(){
564            var definitions = this.definitions;
565            for (var i = 0, len = definitions.length; i < len; i++) {
566                definitions[i]._walk(visitor);
567            }
568        });
569    }
570}, AST_Statement);
571
572var AST_Var = DEFNODE("Var", null, {
573    $documentation: "A `var` statement"
574}, AST_Definitions);
575
576var AST_Const = DEFNODE("Const", null, {
577    $documentation: "A `const` statement"
578}, AST_Definitions);
579
580var AST_VarDef = DEFNODE("VarDef", "name value", {
581    $documentation: "A variable declaration; only appears in a AST_Definitions node",
582    $propdoc: {
583        name: "[AST_SymbolVar|AST_SymbolConst] name of the variable",
584        value: "[AST_Node?] initializer, or null of there's no initializer"
585    },
586    _walk: function(visitor) {
587        return visitor._visit(this, function(){
588            this.name._walk(visitor);
589            if (this.value) this.value._walk(visitor);
590        });
591    }
592});
593
594/* -----[ OTHER ]----- */
595
596var AST_Call = DEFNODE("Call", "expression args", {
597    $documentation: "A function call expression",
598    $propdoc: {
599        expression: "[AST_Node] expression to invoke as function",
600        args: "[AST_Node*] array of arguments"
601    },
602    _walk: function(visitor) {
603        return visitor._visit(this, function(){
604            this.expression._walk(visitor);
605            var args = this.args;
606            for (var i = 0, len = args.length; i < len; i++) {
607                args[i]._walk(visitor);
608            }
609        });
610    }
611});
612
613var AST_New = DEFNODE("New", null, {
614    $documentation: "An object instantiation.  Derives from a function call since it has exactly the same properties"
615}, AST_Call);
616
617var AST_Seq = DEFNODE("Seq", "car cdr", {
618    $documentation: "A sequence expression (two comma-separated expressions)",
619    $propdoc: {
620        car: "[AST_Node] first element in sequence",
621        cdr: "[AST_Node] second element in sequence"
622    },
623    $cons: function(x, y) {
624        var seq = new AST_Seq(x);
625        seq.car = x;
626        seq.cdr = y;
627        return seq;
628    },
629    $from_array: function(array) {
630        if (array.length == 0) return null;
631        if (array.length == 1) return array[0].clone();
632        var list = null;
633        for (var i = array.length; --i >= 0;) {
634            list = AST_Seq.cons(array[i], list);
635        }
636        var p = list;
637        while (p) {
638            if (p.cdr && !p.cdr.cdr) {
639                p.cdr = p.cdr.car;
640                break;
641            }
642            p = p.cdr;
643        }
644        return list;
645    },
646    to_array: function() {
647        var p = this, a = [];
648        while (p) {
649            a.push(p.car);
650            if (p.cdr && !(p.cdr instanceof AST_Seq)) {
651                a.push(p.cdr);
652                break;
653            }
654            p = p.cdr;
655        }
656        return a;
657    },
658    add: function(node) {
659        var p = this;
660        while (p) {
661            if (!(p.cdr instanceof AST_Seq)) {
662                var cell = AST_Seq.cons(p.cdr, node);
663                return p.cdr = cell;
664            }
665            p = p.cdr;
666        }
667    },
668    len: function() {
669        if (this.cdr instanceof AST_Seq) {
670            return this.cdr.len() + 1;
671        } else {
672            return 2;
673        }
674    },
675    _walk: function(visitor) {
676        return visitor._visit(this, function(){
677            this.car._walk(visitor);
678            if (this.cdr) this.cdr._walk(visitor);
679        });
680    }
681});
682
683var AST_PropAccess = DEFNODE("PropAccess", "expression property", {
684    $documentation: "Base class for property access expressions, i.e. `a.foo` or `a[\"foo\"]`",
685    $propdoc: {
686        expression: "[AST_Node] the “container” expression",
687        property: "[AST_Node|string] the property to access.  For AST_Dot this is always a plain string, while for AST_Sub it's an arbitrary AST_Node"
688    }
689});
690
691var AST_Dot = DEFNODE("Dot", null, {
692    $documentation: "A dotted property access expression",
693    _walk: function(visitor) {
694        return visitor._visit(this, function(){
695            this.expression._walk(visitor);
696        });
697    }
698}, AST_PropAccess);
699
700var AST_Sub = DEFNODE("Sub", null, {
701    $documentation: "Index-style property access, i.e. `a[\"foo\"]`",
702    _walk: function(visitor) {
703        return visitor._visit(this, function(){
704            this.expression._walk(visitor);
705            this.property._walk(visitor);
706        });
707    }
708}, AST_PropAccess);
709
710var AST_Unary = DEFNODE("Unary", "operator expression", {
711    $documentation: "Base class for unary expressions",
712    $propdoc: {
713        operator: "[string] the operator",
714        expression: "[AST_Node] expression that this unary operator applies to"
715    },
716    _walk: function(visitor) {
717        return visitor._visit(this, function(){
718            this.expression._walk(visitor);
719        });
720    }
721});
722
723var AST_UnaryPrefix = DEFNODE("UnaryPrefix", null, {
724    $documentation: "Unary prefix expression, i.e. `typeof i` or `++i`"
725}, AST_Unary);
726
727var AST_UnaryPostfix = DEFNODE("UnaryPostfix", null, {
728    $documentation: "Unary postfix expression, i.e. `i++`"
729}, AST_Unary);
730
731var AST_Binary = DEFNODE("Binary", "left operator right", {
732    $documentation: "Binary expression, i.e. `a + b`",
733    $propdoc: {
734        left: "[AST_Node] left-hand side expression",
735        operator: "[string] the operator",
736        right: "[AST_Node] right-hand side expression"
737    },
738    _walk: function(visitor) {
739        return visitor._visit(this, function(){
740            this.left._walk(visitor);
741            this.right._walk(visitor);
742        });
743    }
744});
745
746var AST_Conditional = DEFNODE("Conditional", "condition consequent alternative", {
747    $documentation: "Conditional expression using the ternary operator, i.e. `a ? b : c`",
748    $propdoc: {
749        condition: "[AST_Node]",
750        consequent: "[AST_Node]",
751        alternative: "[AST_Node]"
752    },
753    _walk: function(visitor) {
754        return visitor._visit(this, function(){
755            this.condition._walk(visitor);
756            this.consequent._walk(visitor);
757            this.alternative._walk(visitor);
758        });
759    }
760});
761
762var AST_Assign = DEFNODE("Assign", null, {
763    $documentation: "An assignment expression — `a = b + 5`",
764}, AST_Binary);
765
766/* -----[ LITERALS ]----- */
767
768var AST_Array = DEFNODE("Array", "elements", {
769    $documentation: "An array literal",
770    $propdoc: {
771        elements: "[AST_Node*] array of elements"
772    },
773    _walk: function(visitor) {
774        return visitor._visit(this, function(){
775            var elements = this.elements;
776            for (var i = 0, len = elements.length; i < len; i++) {
777                elements[i]._walk(visitor);
778            }
779        });
780    }
781});
782
783var AST_Object = DEFNODE("Object", "properties", {
784    $documentation: "An object literal",
785    $propdoc: {
786        properties: "[AST_ObjectProperty*] array of properties"
787    },
788    _walk: function(visitor) {
789        return visitor._visit(this, function(){
790            var properties = this.properties;
791            for (var i = 0, len = properties.length; i < len; i++) {
792                properties[i]._walk(visitor);
793            }
794        });
795    }
796});
797
798var AST_ObjectProperty = DEFNODE("ObjectProperty", "key value", {
799    $documentation: "Base class for literal object properties",
800    $propdoc: {
801        key: "[string] the property name converted to a string for ObjectKeyVal.  For setters and getters this is an AST_SymbolAccessor.",
802        value: "[AST_Node] property value.  For setters and getters this is an AST_Accessor."
803    },
804    _walk: function(visitor) {
805        return visitor._visit(this, function(){
806            this.value._walk(visitor);
807        });
808    }
809});
810
811var AST_ObjectKeyVal = DEFNODE("ObjectKeyVal", "quote", {
812    $documentation: "A key: value object property",
813    $propdoc: {
814        quote: "[string] the original quote character"
815    }
816}, AST_ObjectProperty);
817
818var AST_ObjectSetter = DEFNODE("ObjectSetter", null, {
819    $documentation: "An object setter property",
820}, AST_ObjectProperty);
821
822var AST_ObjectGetter = DEFNODE("ObjectGetter", null, {
823    $documentation: "An object getter property",
824}, AST_ObjectProperty);
825
826var AST_Symbol = DEFNODE("Symbol", "scope name thedef", {
827    $propdoc: {
828        name: "[string] name of this symbol",
829        scope: "[AST_Scope/S] the current scope (not necessarily the definition scope)",
830        thedef: "[SymbolDef/S] the definition of this symbol"
831    },
832    $documentation: "Base class for all symbols",
833});
834
835var AST_SymbolAccessor = DEFNODE("SymbolAccessor", null, {
836    $documentation: "The name of a property accessor (setter/getter function)"
837}, AST_Symbol);
838
839var AST_SymbolDeclaration = DEFNODE("SymbolDeclaration", "init", {
840    $documentation: "A declaration symbol (symbol in var/const, function name or argument, symbol in catch)",
841}, AST_Symbol);
842
843var AST_SymbolVar = DEFNODE("SymbolVar", null, {
844    $documentation: "Symbol defining a variable",
845}, AST_SymbolDeclaration);
846
847var AST_SymbolConst = DEFNODE("SymbolConst", null, {
848    $documentation: "A constant declaration"
849}, AST_SymbolDeclaration);
850
851var AST_SymbolFunarg = DEFNODE("SymbolFunarg", null, {
852    $documentation: "Symbol naming a function argument",
853}, AST_SymbolVar);
854
855var AST_SymbolDefun = DEFNODE("SymbolDefun", null, {
856    $documentation: "Symbol defining a function",
857}, AST_SymbolDeclaration);
858
859var AST_SymbolLambda = DEFNODE("SymbolLambda", null, {
860    $documentation: "Symbol naming a function expression",
861}, AST_SymbolDeclaration);
862
863var AST_SymbolCatch = DEFNODE("SymbolCatch", null, {
864    $documentation: "Symbol naming the exception in catch",
865}, AST_SymbolDeclaration);
866
867var AST_Label = DEFNODE("Label", "references", {
868    $documentation: "Symbol naming a label (declaration)",
869    $propdoc: {
870        references: "[AST_LoopControl*] a list of nodes referring to this label"
871    },
872    initialize: function() {
873        this.references = [];
874        this.thedef = this;
875    }
876}, AST_Symbol);
877
878var AST_SymbolRef = DEFNODE("SymbolRef", null, {
879    $documentation: "Reference to some symbol (not definition/declaration)",
880}, AST_Symbol);
881
882var AST_LabelRef = DEFNODE("LabelRef", null, {
883    $documentation: "Reference to a label symbol",
884}, AST_Symbol);
885
886var AST_This = DEFNODE("This", null, {
887    $documentation: "The `this` symbol",
888}, AST_Symbol);
889
890var AST_Constant = DEFNODE("Constant", null, {
891    $documentation: "Base class for all constants",
892    getValue: function() {
893        return this.value;
894    }
895});
896
897var AST_String = DEFNODE("String", "value quote", {
898    $documentation: "A string literal",
899    $propdoc: {
900        value: "[string] the contents of this string",
901        quote: "[string] the original quote character"
902    }
903}, AST_Constant);
904
905var AST_Number = DEFNODE("Number", "value literal", {
906    $documentation: "A number literal",
907    $propdoc: {
908        value: "[number] the numeric value",
909        literal: "[string] numeric value as string (optional)"
910    }
911}, AST_Constant);
912
913var AST_RegExp = DEFNODE("RegExp", "value", {
914    $documentation: "A regexp literal",
915    $propdoc: {
916        value: "[RegExp] the actual regexp"
917    }
918}, AST_Constant);
919
920var AST_Atom = DEFNODE("Atom", null, {
921    $documentation: "Base class for atoms",
922}, AST_Constant);
923
924var AST_Null = DEFNODE("Null", null, {
925    $documentation: "The `null` atom",
926    value: null
927}, AST_Atom);
928
929var AST_NaN = DEFNODE("NaN", null, {
930    $documentation: "The impossible value",
931    value: 0/0
932}, AST_Atom);
933
934var AST_Undefined = DEFNODE("Undefined", null, {
935    $documentation: "The `undefined` value",
936    value: (function(){}())
937}, AST_Atom);
938
939var AST_Hole = DEFNODE("Hole", null, {
940    $documentation: "A hole in an array",
941    value: (function(){}())
942}, AST_Atom);
943
944var AST_Infinity = DEFNODE("Infinity", null, {
945    $documentation: "The `Infinity` value",
946    value: 1/0
947}, AST_Atom);
948
949var AST_Boolean = DEFNODE("Boolean", null, {
950    $documentation: "Base class for booleans",
951}, AST_Atom);
952
953var AST_False = DEFNODE("False", null, {
954    $documentation: "The `false` atom",
955    value: false
956}, AST_Boolean);
957
958var AST_True = DEFNODE("True", null, {
959    $documentation: "The `true` atom",
960    value: true
961}, AST_Boolean);
962
963/* -----[ TreeWalker ]----- */
964
965function TreeWalker(callback) {
966    this.visit = callback;
967    this.stack = [];
968    this.directives = Object.create(null);
969};
970TreeWalker.prototype = {
971    _visit: function(node, descend) {
972        this.push(node);
973        var ret = this.visit(node, descend ? function(){
974            descend.call(node);
975        } : noop);
976        if (!ret && descend) {
977            descend.call(node);
978        }
979        this.pop(node);
980        return ret;
981    },
982    parent: function(n) {
983        return this.stack[this.stack.length - 2 - (n || 0)];
984    },
985    push: function (node) {
986        if (node instanceof AST_Lambda) {
987            this.directives = Object.create(this.directives);
988        } else if (node instanceof AST_Directive && !this.directives[node.value]) {
989            this.directives[node.value] = node;
990        }
991        this.stack.push(node);
992    },
993    pop: function(node) {
994        this.stack.pop();
995        if (node instanceof AST_Lambda) {
996            this.directives = Object.getPrototypeOf(this.directives);
997        }
998    },
999    self: function() {
1000        return this.stack[this.stack.length - 1];
1001    },
1002    find_parent: function(type) {
1003        var stack = this.stack;
1004        for (var i = stack.length; --i >= 0;) {
1005            var x = stack[i];
1006            if (x instanceof type) return x;
1007        }
1008    },
1009    has_directive: function(type) {
1010        var dir = this.directives[type];
1011        if (dir) return dir;
1012        var node = this.stack[this.stack.length - 1];
1013        if (node instanceof AST_Scope) {
1014            for (var i = 0; i < node.body.length; ++i) {
1015                var st = node.body[i];
1016                if (!(st instanceof AST_Directive)) break;
1017                if (st.value == type) return st;
1018            }
1019        }
1020    },
1021    in_boolean_context: function() {
1022        var stack = this.stack;
1023        var i = stack.length, self = stack[--i];
1024        while (i > 0) {
1025            var p = stack[--i];
1026            if ((p instanceof AST_If           && p.condition === self) ||
1027                (p instanceof AST_Conditional  && p.condition === self) ||
1028                (p instanceof AST_DWLoop       && p.condition === self) ||
1029                (p instanceof AST_For          && p.condition === self) ||
1030                (p instanceof AST_UnaryPrefix  && p.operator == "!" && p.expression === self))
1031            {
1032                return true;
1033            }
1034            if (!(p instanceof AST_Binary && (p.operator == "&&" || p.operator == "||")))
1035                return false;
1036            self = p;
1037        }
1038    },
1039    loopcontrol_target: function(node) {
1040        var stack = this.stack;
1041        if (node.label) for (var i = stack.length; --i >= 0;) {
1042            var x = stack[i];
1043            if (x instanceof AST_LabeledStatement && x.label.name == node.label.name)
1044                return x.body;
1045        } else for (var i = stack.length; --i >= 0;) {
1046            var x = stack[i];
1047            if (x instanceof AST_IterationStatement
1048                || node instanceof AST_Break && x instanceof AST_Switch)
1049                return x;
1050        }
1051    }
1052};
1053