1/***********************************************************************
2
3  A JavaScript tokenizer / parser / beautifier / compressor.
4  https://github.com/mishoo/UglifyJS
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 (typeof base === "undefined") base = AST_Node;
48    props = props ? props.split(/\s+/) : [];
49    var self_props = props;
50    if (base && base.PROPS) props = props.concat(base.PROPS);
51    var code = [
52        "return function AST_", type, "(props){",
53        // not essential, but speeds up compress by a few percent
54        "this._bits=0;",
55        "if(props){",
56    ];
57    props.forEach(function(prop) {
58        code.push("this.", prop, "=props.", prop, ";");
59    });
60    code.push("}");
61    var proto = Object.create(base && base.prototype);
62    if (methods.initialize || proto.initialize) code.push("this.initialize();");
63    code.push("};");
64    var ctor = new Function(code.join(""))();
65    ctor.prototype = proto;
66    ctor.prototype.CTOR = ctor;
67    ctor.prototype.TYPE = ctor.TYPE = type;
68    if (base) {
69        ctor.BASE = base;
70        base.SUBCLASSES.push(ctor);
71    }
72    ctor.DEFMETHOD = function(name, method) {
73        this.prototype[name] = method;
74    };
75    ctor.PROPS = props;
76    ctor.SELF_PROPS = self_props;
77    ctor.SUBCLASSES = [];
78    for (var name in methods) if (HOP(methods, name)) {
79        if (/^\$/.test(name)) {
80            ctor[name.substr(1)] = methods[name];
81        } else {
82            ctor.DEFMETHOD(name, methods[name]);
83        }
84    }
85    if (typeof exports !== "undefined") exports["AST_" + type] = ctor;
86    return ctor;
87}
88
89var AST_Token = DEFNODE("Token", "type value line col pos endline endcol endpos nlb comments_before comments_after file raw", {
90}, null);
91
92var AST_Node = DEFNODE("Node", "start end", {
93    _clone: function(deep) {
94        if (deep) {
95            var self = this.clone();
96            return self.transform(new TreeTransformer(function(node) {
97                if (node !== self) {
98                    return node.clone(true);
99                }
100            }));
101        }
102        return new this.CTOR(this);
103    },
104    clone: function(deep) {
105        return this._clone(deep);
106    },
107    $documentation: "Base class of all AST nodes",
108    $propdoc: {
109        start: "[AST_Token] The first token of this node",
110        end: "[AST_Token] The last token of this node"
111    },
112    equals: function(node) {
113        return this.TYPE == node.TYPE && this._equals(node);
114    },
115    walk: function(visitor) {
116        visitor.visit(this);
117    },
118    _validate: function() {
119        if (this.TYPE == "Node") throw new Error("should not instantiate AST_Node");
120    },
121    validate: function() {
122        var ctor = this.CTOR;
123        do {
124            ctor.prototype._validate.call(this);
125        } while (ctor = ctor.BASE);
126    },
127    validate_ast: function() {
128        var marker = {};
129        this.walk(new TreeWalker(function(node) {
130            if (node.validate_visited === marker) {
131                throw new Error(string_template("cannot reuse AST_{TYPE} from [{start}]", node));
132            }
133            node.validate_visited = marker;
134        }));
135    },
136}, null);
137
138DEF_BITPROPS(AST_Node, [
139    // AST_Node
140    "_optimized",
141    "_squeezed",
142    // AST_Call
143    "call_only",
144    // AST_Lambda
145    "collapse_scanning",
146    // AST_SymbolRef
147    "defined",
148    "evaluating",
149    "falsy",
150    // AST_SymbolRef
151    "in_arg",
152    // AST_Return
153    "in_bool",
154    // AST_SymbolRef
155    "is_undefined",
156    // AST_LambdaExpression
157    // AST_LambdaDefinition
158    "inlined",
159    // AST_Lambda
160    "length_read",
161    // AST_Yield
162    "nested",
163    // AST_Lambda
164    "new",
165    // AST_Call
166    // AST_PropAccess
167    "optional",
168    // AST_ClassProperty
169    "private",
170    // AST_Call
171    "pure",
172    // AST_Assign
173    "redundant",
174    // AST_Node
175    "single_use",
176    // AST_ClassProperty
177    "static",
178    // AST_Call
179    // AST_PropAccess
180    "terminal",
181    "truthy",
182    // AST_Scope
183    "uses_eval",
184    // AST_Scope
185    "uses_with",
186]);
187
188(AST_Node.log_function = function(fn, verbose) {
189    if (typeof fn != "function") {
190        AST_Node.info = AST_Node.warn = noop;
191        return;
192    }
193    var printed = Object.create(null);
194    AST_Node.info = verbose ? function(text, props) {
195        log("INFO: " + string_template(text, props));
196    } : noop;
197    AST_Node.warn = function(text, props) {
198        log("WARN: " + string_template(text, props));
199    };
200
201    function log(msg) {
202        if (printed[msg]) return;
203        printed[msg] = true;
204        fn(msg);
205    }
206})();
207
208var restore_transforms = [];
209AST_Node.enable_validation = function() {
210    AST_Node.disable_validation();
211    (function validate_transform(ctor) {
212        ctor.SUBCLASSES.forEach(validate_transform);
213        if (!HOP(ctor.prototype, "transform")) return;
214        var transform = ctor.prototype.transform;
215        ctor.prototype.transform = function(tw, in_list) {
216            var node = transform.call(this, tw, in_list);
217            if (node instanceof AST_Node) {
218                node.validate();
219            } else if (!(node === null || in_list && List.is_op(node))) {
220                throw new Error("invalid transformed value: " + node);
221            }
222            return node;
223        };
224        restore_transforms.push(function() {
225            ctor.prototype.transform = transform;
226        });
227    })(this);
228};
229
230AST_Node.disable_validation = function() {
231    var restore;
232    while (restore = restore_transforms.pop()) restore();
233};
234
235function all_equals(k, l) {
236    return k.length == l.length && all(k, function(m, i) {
237        return m.equals(l[i]);
238    });
239}
240
241function list_equals(s, t) {
242    return s.length == t.length && all(s, function(u, i) {
243        return u == t[i];
244    });
245}
246
247function prop_equals(u, v) {
248    if (u === v) return true;
249    if (u == null) return v == null;
250    return u instanceof AST_Node && v instanceof AST_Node && u.equals(v);
251}
252
253/* -----[ statements ]----- */
254
255var AST_Statement = DEFNODE("Statement", null, {
256    $documentation: "Base class of all statements",
257    _validate: function() {
258        if (this.TYPE == "Statement") throw new Error("should not instantiate AST_Statement");
259    },
260});
261
262var AST_Debugger = DEFNODE("Debugger", null, {
263    $documentation: "Represents a debugger statement",
264    _equals: return_true,
265}, AST_Statement);
266
267var AST_Directive = DEFNODE("Directive", "quote value", {
268    $documentation: "Represents a directive, like \"use strict\";",
269    $propdoc: {
270        quote: "[string?] the original quote character",
271        value: "[string] The value of this directive as a plain string (it's not an AST_String!)",
272    },
273    _equals: function(node) {
274        return this.value == node.value;
275    },
276    _validate: function() {
277        if (this.quote != null) {
278            if (typeof this.quote != "string") throw new Error("quote must be string");
279            if (!/^["']$/.test(this.quote)) throw new Error("invalid quote: " + this.quote);
280        }
281        if (typeof this.value != "string") throw new Error("value must be string");
282    },
283}, AST_Statement);
284
285var AST_EmptyStatement = DEFNODE("EmptyStatement", null, {
286    $documentation: "The empty statement (empty block or simply a semicolon)",
287    _equals: return_true,
288}, AST_Statement);
289
290function is_statement(node) {
291    return node instanceof AST_Statement
292        && !(node instanceof AST_ClassExpression)
293        && !(node instanceof AST_LambdaExpression);
294}
295
296function validate_expression(value, prop, multiple, allow_spread, allow_hole) {
297    multiple = multiple ? "contain" : "be";
298    if (!(value instanceof AST_Node)) throw new Error(prop + " must " + multiple + " AST_Node");
299    if (value instanceof AST_DefaultValue) throw new Error(prop + " cannot " + multiple + " AST_DefaultValue");
300    if (value instanceof AST_Destructured) throw new Error(prop + " cannot " + multiple + " AST_Destructured");
301    if (value instanceof AST_Hole && !allow_hole) throw new Error(prop + " cannot " + multiple + " AST_Hole");
302    if (value instanceof AST_Spread && !allow_spread) throw new Error(prop + " cannot " + multiple + " AST_Spread");
303    if (is_statement(value)) throw new Error(prop + " cannot " + multiple + " AST_Statement");
304    if (value instanceof AST_SymbolDeclaration) {
305        throw new Error(prop + " cannot " + multiple + " AST_SymbolDeclaration");
306    }
307}
308
309function must_be_expression(node, prop) {
310    validate_expression(node[prop], prop);
311}
312
313var AST_SimpleStatement = DEFNODE("SimpleStatement", "body", {
314    $documentation: "A statement consisting of an expression, i.e. a = 1 + 2",
315    $propdoc: {
316        body: "[AST_Node] an expression node (should not be instanceof AST_Statement)",
317    },
318    _equals: function(node) {
319        return this.body.equals(node.body);
320    },
321    walk: function(visitor) {
322        var node = this;
323        visitor.visit(node, function() {
324            node.body.walk(visitor);
325        });
326    },
327    _validate: function() {
328        must_be_expression(this, "body");
329    },
330}, AST_Statement);
331
332var AST_BlockScope = DEFNODE("BlockScope", "_var_names enclosed functions make_def parent_scope variables", {
333    $documentation: "Base class for all statements introducing a lexical scope",
334    $propdoc: {
335        enclosed: "[SymbolDef*/S] a list of all symbol definitions that are accessed from this scope or any inner scopes",
336        functions: "[Dictionary/S] like `variables`, but only lists function declarations",
337        parent_scope: "[AST_Scope?/S] link to the parent scope",
338        variables: "[Dictionary/S] a map of name ---> SymbolDef for all variables/functions defined in this scope",
339    },
340    clone: function(deep) {
341        var node = this._clone(deep);
342        if (this.enclosed) node.enclosed = this.enclosed.slice();
343        if (this.functions) node.functions = this.functions.clone();
344        if (this.variables) node.variables = this.variables.clone();
345        return node;
346    },
347    pinned: function() {
348        return this.resolve().pinned();
349    },
350    resolve: function() {
351        return this.parent_scope.resolve();
352    },
353    _validate: function() {
354        if (this.TYPE == "BlockScope") throw new Error("should not instantiate AST_BlockScope");
355        if (this.parent_scope == null) return;
356        if (!(this.parent_scope instanceof AST_BlockScope)) throw new Error("parent_scope must be AST_BlockScope");
357        if (!(this.resolve() instanceof AST_Scope)) throw new Error("must be contained within AST_Scope");
358    },
359}, AST_Statement);
360
361function walk_body(node, visitor) {
362    node.body.forEach(function(node) {
363        node.walk(visitor);
364    });
365}
366
367var AST_Block = DEFNODE("Block", "body", {
368    $documentation: "A body of statements (usually braced)",
369    $propdoc: {
370        body: "[AST_Statement*] an array of statements"
371    },
372    _equals: function(node) {
373        return all_equals(this.body, node.body);
374    },
375    walk: function(visitor) {
376        var node = this;
377        visitor.visit(node, function() {
378            walk_body(node, visitor);
379        });
380    },
381    _validate: function() {
382        if (this.TYPE == "Block") throw new Error("should not instantiate AST_Block");
383        this.body.forEach(function(node) {
384            if (!is_statement(node)) throw new Error("body must contain AST_Statement");
385        });
386    },
387}, AST_BlockScope);
388
389var AST_BlockStatement = DEFNODE("BlockStatement", null, {
390    $documentation: "A block statement",
391}, AST_Block);
392
393var AST_StatementWithBody = DEFNODE("StatementWithBody", "body", {
394    $documentation: "Base class for all statements that contain one nested body: `For`, `ForIn`, `Do`, `While`, `With`",
395    $propdoc: {
396        body: "[AST_Statement] the body; this should always be present, even if it's an AST_EmptyStatement"
397    },
398    _validate: function() {
399        if (this.TYPE == "StatementWithBody") throw new Error("should not instantiate AST_StatementWithBody");
400        if (!is_statement(this.body)) throw new Error("body must be AST_Statement");
401    },
402}, AST_BlockScope);
403
404var AST_LabeledStatement = DEFNODE("LabeledStatement", "label", {
405    $documentation: "Statement with a label",
406    $propdoc: {
407        label: "[AST_Label] a label definition"
408    },
409    _equals: function(node) {
410        return this.label.equals(node.label)
411            && this.body.equals(node.body);
412    },
413    walk: function(visitor) {
414        var node = this;
415        visitor.visit(node, function() {
416            node.label.walk(visitor);
417            node.body.walk(visitor);
418        });
419    },
420    clone: function(deep) {
421        var node = this._clone(deep);
422        if (deep) {
423            var label = node.label;
424            var def = this.label;
425            node.walk(new TreeWalker(function(node) {
426                if (node instanceof AST_LoopControl) {
427                    if (!node.label || node.label.thedef !== def) return;
428                    node.label.thedef = label;
429                    label.references.push(node);
430                    return true;
431                }
432                if (node instanceof AST_Scope) return true;
433            }));
434        }
435        return node;
436    },
437    _validate: function() {
438        if (!(this.label instanceof AST_Label)) throw new Error("label must be AST_Label");
439    },
440}, AST_StatementWithBody);
441
442var AST_IterationStatement = DEFNODE("IterationStatement", null, {
443    $documentation: "Internal class.  All loops inherit from it.",
444    _validate: function() {
445        if (this.TYPE == "IterationStatement") throw new Error("should not instantiate AST_IterationStatement");
446    },
447}, AST_StatementWithBody);
448
449var AST_DWLoop = DEFNODE("DWLoop", "condition", {
450    $documentation: "Base class for do/while statements",
451    $propdoc: {
452        condition: "[AST_Node] the loop condition.  Should not be instanceof AST_Statement"
453    },
454    _equals: function(node) {
455        return this.body.equals(node.body)
456            && this.condition.equals(node.condition);
457    },
458    _validate: function() {
459        if (this.TYPE == "DWLoop") throw new Error("should not instantiate AST_DWLoop");
460        must_be_expression(this, "condition");
461    },
462}, AST_IterationStatement);
463
464var AST_Do = DEFNODE("Do", null, {
465    $documentation: "A `do` statement",
466    walk: function(visitor) {
467        var node = this;
468        visitor.visit(node, function() {
469            node.body.walk(visitor);
470            node.condition.walk(visitor);
471        });
472    },
473}, AST_DWLoop);
474
475var AST_While = DEFNODE("While", null, {
476    $documentation: "A `while` statement",
477    walk: function(visitor) {
478        var node = this;
479        visitor.visit(node, function() {
480            node.condition.walk(visitor);
481            node.body.walk(visitor);
482        });
483    },
484}, AST_DWLoop);
485
486var AST_For = DEFNODE("For", "init condition step", {
487    $documentation: "A `for` statement",
488    $propdoc: {
489        init: "[AST_Node?] the `for` initialization code, or null if empty",
490        condition: "[AST_Node?] the `for` termination clause, or null if empty",
491        step: "[AST_Node?] the `for` update clause, or null if empty"
492    },
493    _equals: function(node) {
494        return prop_equals(this.init, node.init)
495            && prop_equals(this.condition, node.condition)
496            && prop_equals(this.step, node.step)
497            && this.body.equals(node.body);
498    },
499    walk: function(visitor) {
500        var node = this;
501        visitor.visit(node, function() {
502            if (node.init) node.init.walk(visitor);
503            if (node.condition) node.condition.walk(visitor);
504            if (node.step) node.step.walk(visitor);
505            node.body.walk(visitor);
506        });
507    },
508    _validate: function() {
509        if (this.init != null) {
510            if (!(this.init instanceof AST_Node)) throw new Error("init must be AST_Node");
511            if (is_statement(this.init) && !(this.init instanceof AST_Definitions)) {
512                throw new Error("init cannot be AST_Statement");
513            }
514        }
515        if (this.condition != null) must_be_expression(this, "condition");
516        if (this.step != null) must_be_expression(this, "step");
517    },
518}, AST_IterationStatement);
519
520var AST_ForEnumeration = DEFNODE("ForEnumeration", "init object", {
521    $documentation: "Base class for enumeration loops, i.e. `for ... in`, `for ... of` & `for await ... of`",
522    $propdoc: {
523        init: "[AST_Node] the assignment target during iteration",
524        object: "[AST_Node] the object to iterate over"
525    },
526    _equals: function(node) {
527        return this.init.equals(node.init)
528            && this.object.equals(node.object)
529            && this.body.equals(node.body);
530    },
531    walk: function(visitor) {
532        var node = this;
533        visitor.visit(node, function() {
534            node.init.walk(visitor);
535            node.object.walk(visitor);
536            node.body.walk(visitor);
537        });
538    },
539    _validate: function() {
540        if (this.TYPE == "ForEnumeration") throw new Error("should not instantiate AST_ForEnumeration");
541        if (this.init instanceof AST_Definitions) {
542            if (this.init.definitions.length != 1) throw new Error("init must have single declaration");
543        } else {
544            validate_destructured(this.init, function(node) {
545                if (!(node instanceof AST_PropAccess || node instanceof AST_SymbolRef)) {
546                    throw new Error("init must be assignable: " + node.TYPE);
547                }
548            });
549        }
550        must_be_expression(this, "object");
551    },
552}, AST_IterationStatement);
553
554var AST_ForIn = DEFNODE("ForIn", null, {
555    $documentation: "A `for ... in` statement",
556}, AST_ForEnumeration);
557
558var AST_ForOf = DEFNODE("ForOf", null, {
559    $documentation: "A `for ... of` statement",
560}, AST_ForEnumeration);
561
562var AST_ForAwaitOf = DEFNODE("ForAwaitOf", null, {
563    $documentation: "A `for await ... of` statement",
564}, AST_ForOf);
565
566var AST_With = DEFNODE("With", "expression", {
567    $documentation: "A `with` statement",
568    $propdoc: {
569        expression: "[AST_Node] the `with` expression"
570    },
571    _equals: function(node) {
572        return this.expression.equals(node.expression)
573            && this.body.equals(node.body);
574    },
575    walk: function(visitor) {
576        var node = this;
577        visitor.visit(node, function() {
578            node.expression.walk(visitor);
579            node.body.walk(visitor);
580        });
581    },
582    _validate: function() {
583        must_be_expression(this, "expression");
584    },
585}, AST_StatementWithBody);
586
587/* -----[ scope and functions ]----- */
588
589var AST_Scope = DEFNODE("Scope", "fn_defs may_call_this uses_eval uses_with", {
590    $documentation: "Base class for all statements introducing a lambda scope",
591    $propdoc: {
592        uses_eval: "[boolean/S] tells whether this scope contains a direct call to the global `eval`",
593        uses_with: "[boolean/S] tells whether this scope uses the `with` statement",
594    },
595    pinned: function() {
596        return this.uses_eval || this.uses_with;
597    },
598    resolve: return_this,
599    _validate: function() {
600        if (this.TYPE == "Scope") throw new Error("should not instantiate AST_Scope");
601    },
602}, AST_Block);
603
604var AST_Toplevel = DEFNODE("Toplevel", "globals", {
605    $documentation: "The toplevel scope",
606    $propdoc: {
607        globals: "[Dictionary/S] a map of name ---> SymbolDef for all undeclared names",
608    },
609    wrap: function(name) {
610        var body = this.body;
611        return parse([
612            "(function(exports){'$ORIG';})(typeof ",
613            name,
614            "=='undefined'?(",
615            name,
616            "={}):",
617            name,
618            ");"
619        ].join(""), {
620            filename: "wrap=" + JSON.stringify(name)
621        }).transform(new TreeTransformer(function(node) {
622            if (node instanceof AST_Directive && node.value == "$ORIG") {
623                return List.splice(body);
624            }
625        }));
626    },
627    enclose: function(args_values) {
628        if (typeof args_values != "string") args_values = "";
629        var index = args_values.indexOf(":");
630        if (index < 0) index = args_values.length;
631        var body = this.body;
632        return parse([
633            "(function(",
634            args_values.slice(0, index),
635            '){"$ORIG"})(',
636            args_values.slice(index + 1),
637            ")"
638        ].join(""), {
639            filename: "enclose=" + JSON.stringify(args_values)
640        }).transform(new TreeTransformer(function(node) {
641            if (node instanceof AST_Directive && node.value == "$ORIG") {
642                return List.splice(body);
643            }
644        }));
645    }
646}, AST_Scope);
647
648var AST_ClassInitBlock = DEFNODE("ClassInitBlock", null, {
649    $documentation: "Value for `class` static initialization blocks",
650}, AST_Scope);
651
652var AST_Lambda = DEFNODE("Lambda", "argnames length_read rest safe_ids uses_arguments", {
653    $documentation: "Base class for functions",
654    $propdoc: {
655        argnames: "[(AST_DefaultValue|AST_Destructured|AST_SymbolFunarg)*] array of function arguments and/or destructured literals",
656        length_read: "[boolean/S] whether length property of this function is accessed",
657        rest: "[(AST_Destructured|AST_SymbolFunarg)?] rest parameter, or null if absent",
658        uses_arguments: "[boolean|number/S] whether this function accesses the arguments array",
659    },
660    each_argname: function(visit) {
661        var tw = new TreeWalker(function(node) {
662            if (node instanceof AST_DefaultValue) {
663                node.name.walk(tw);
664                return true;
665            }
666            if (node instanceof AST_DestructuredKeyVal) {
667                node.value.walk(tw);
668                return true;
669            }
670            if (node instanceof AST_SymbolFunarg) visit(node);
671        });
672        this.argnames.forEach(function(argname) {
673            argname.walk(tw);
674        });
675        if (this.rest) this.rest.walk(tw);
676    },
677    _equals: function(node) {
678        return prop_equals(this.rest, node.rest)
679            && prop_equals(this.name, node.name)
680            && prop_equals(this.value, node.value)
681            && all_equals(this.argnames, node.argnames)
682            && all_equals(this.body, node.body);
683    },
684    walk: function(visitor) {
685        var node = this;
686        visitor.visit(node, function() {
687            if (node.name) node.name.walk(visitor);
688            node.argnames.forEach(function(argname) {
689                argname.walk(visitor);
690            });
691            if (node.rest) node.rest.walk(visitor);
692            walk_body(node, visitor);
693        });
694    },
695    _validate: function() {
696        if (this.TYPE == "Lambda") throw new Error("should not instantiate AST_Lambda");
697        this.argnames.forEach(function(node) {
698            validate_destructured(node, function(node) {
699                if (!(node instanceof AST_SymbolFunarg)) throw new Error("argnames must be AST_SymbolFunarg[]");
700            }, true);
701        });
702        if (this.rest != null) validate_destructured(this.rest, function(node) {
703            if (!(node instanceof AST_SymbolFunarg)) throw new Error("rest must be AST_SymbolFunarg");
704        });
705    },
706}, AST_Scope);
707
708var AST_Accessor = DEFNODE("Accessor", null, {
709    $documentation: "A getter/setter function",
710    _validate: function() {
711        if (this.name != null) throw new Error("name must be null");
712    },
713}, AST_Lambda);
714
715var AST_LambdaExpression = DEFNODE("LambdaExpression", "inlined", {
716    $documentation: "Base class for function expressions",
717    $propdoc: {
718        inlined: "[boolean/S] whether this function has been inlined",
719    },
720    _validate: function() {
721        if (this.TYPE == "LambdaExpression") throw new Error("should not instantiate AST_LambdaExpression");
722    },
723}, AST_Lambda);
724
725function is_arrow(node) {
726    return node instanceof AST_Arrow || node instanceof AST_AsyncArrow;
727}
728
729function is_async(node) {
730    return node instanceof AST_AsyncArrow
731        || node instanceof AST_AsyncDefun
732        || node instanceof AST_AsyncFunction
733        || node instanceof AST_AsyncGeneratorDefun
734        || node instanceof AST_AsyncGeneratorFunction;
735}
736
737function is_generator(node) {
738    return node instanceof AST_AsyncGeneratorDefun
739        || node instanceof AST_AsyncGeneratorFunction
740        || node instanceof AST_GeneratorDefun
741        || node instanceof AST_GeneratorFunction;
742}
743
744function walk_lambda(node, tw) {
745    if (is_arrow(node) && node.value) {
746        node.value.walk(tw);
747    } else {
748        walk_body(node, tw);
749    }
750}
751
752var AST_Arrow = DEFNODE("Arrow", "value", {
753    $documentation: "An arrow function expression",
754    $propdoc: {
755        value: "[AST_Node?] simple return expression, or null if using function body.",
756    },
757    walk: function(visitor) {
758        var node = this;
759        visitor.visit(node, function() {
760            node.argnames.forEach(function(argname) {
761                argname.walk(visitor);
762            });
763            if (node.rest) node.rest.walk(visitor);
764            if (node.value) {
765                node.value.walk(visitor);
766            } else {
767                walk_body(node, visitor);
768            }
769        });
770    },
771    _validate: function() {
772        if (this.name != null) throw new Error("name must be null");
773        if (this.uses_arguments) throw new Error("uses_arguments must be false");
774        if (this.value != null) {
775            must_be_expression(this, "value");
776            if (this.body.length) throw new Error("body must be empty if value exists");
777        }
778    },
779}, AST_LambdaExpression);
780
781var AST_AsyncArrow = DEFNODE("AsyncArrow", "value", {
782    $documentation: "An asynchronous arrow function expression",
783    $propdoc: {
784        value: "[AST_Node?] simple return expression, or null if using function body.",
785    },
786    walk: function(visitor) {
787        var node = this;
788        visitor.visit(node, function() {
789            node.argnames.forEach(function(argname) {
790                argname.walk(visitor);
791            });
792            if (node.rest) node.rest.walk(visitor);
793            if (node.value) {
794                node.value.walk(visitor);
795            } else {
796                walk_body(node, visitor);
797            }
798        });
799    },
800    _validate: function() {
801        if (this.name != null) throw new Error("name must be null");
802        if (this.uses_arguments) throw new Error("uses_arguments must be false");
803        if (this.value != null) {
804            must_be_expression(this, "value");
805            if (this.body.length) throw new Error("body must be empty if value exists");
806        }
807    },
808}, AST_LambdaExpression);
809
810var AST_AsyncFunction = DEFNODE("AsyncFunction", "name", {
811    $documentation: "An asynchronous function expression",
812    $propdoc: {
813        name: "[AST_SymbolLambda?] the name of this function, or null if not specified",
814    },
815    _validate: function() {
816        if (this.name != null) {
817            if (!(this.name instanceof AST_SymbolLambda)) throw new Error("name must be AST_SymbolLambda");
818        }
819    },
820}, AST_LambdaExpression);
821
822var AST_AsyncGeneratorFunction = DEFNODE("AsyncGeneratorFunction", "name", {
823    $documentation: "An asynchronous generator function expression",
824    $propdoc: {
825        name: "[AST_SymbolLambda?] the name of this function, or null if not specified",
826    },
827    _validate: function() {
828        if (this.name != null) {
829            if (!(this.name instanceof AST_SymbolLambda)) throw new Error("name must be AST_SymbolLambda");
830        }
831    },
832}, AST_LambdaExpression);
833
834var AST_Function = DEFNODE("Function", "name", {
835    $documentation: "A function expression",
836    $propdoc: {
837        name: "[AST_SymbolLambda?] the name of this function, or null if not specified",
838    },
839    _validate: function() {
840        if (this.name != null) {
841            if (!(this.name instanceof AST_SymbolLambda)) throw new Error("name must be AST_SymbolLambda");
842        }
843    },
844}, AST_LambdaExpression);
845
846var AST_GeneratorFunction = DEFNODE("GeneratorFunction", "name", {
847    $documentation: "A generator function expression",
848    $propdoc: {
849        name: "[AST_SymbolLambda?] the name of this function, or null if not specified",
850    },
851    _validate: function() {
852        if (this.name != null) {
853            if (!(this.name instanceof AST_SymbolLambda)) throw new Error("name must be AST_SymbolLambda");
854        }
855    },
856}, AST_LambdaExpression);
857
858var AST_LambdaDefinition = DEFNODE("LambdaDefinition", "inlined name", {
859    $documentation: "Base class for function definitions",
860    $propdoc: {
861        inlined: "[boolean/S] whether this function has been inlined",
862        name: "[AST_SymbolDefun] the name of this function",
863    },
864    _validate: function() {
865        if (this.TYPE == "LambdaDefinition") throw new Error("should not instantiate AST_LambdaDefinition");
866        if (!(this.name instanceof AST_SymbolDefun)) throw new Error("name must be AST_SymbolDefun");
867    },
868}, AST_Lambda);
869
870var AST_AsyncDefun = DEFNODE("AsyncDefun", null, {
871    $documentation: "An asynchronous function definition",
872}, AST_LambdaDefinition);
873
874var AST_AsyncGeneratorDefun = DEFNODE("AsyncGeneratorDefun", null, {
875    $documentation: "An asynchronous generator function definition",
876}, AST_LambdaDefinition);
877
878var AST_Defun = DEFNODE("Defun", null, {
879    $documentation: "A function definition",
880}, AST_LambdaDefinition);
881
882var AST_GeneratorDefun = DEFNODE("GeneratorDefun", null, {
883    $documentation: "A generator function definition",
884}, AST_LambdaDefinition);
885
886/* -----[ classes ]----- */
887
888var AST_Class = DEFNODE("Class", "extends name properties", {
889    $documentation: "Base class for class literals",
890    $propdoc: {
891        extends: "[AST_Node?] the super class, or null if not specified",
892        properties: "[AST_ClassProperty*] array of class properties",
893    },
894    _equals: function(node) {
895        return prop_equals(this.name, node.name)
896            && prop_equals(this.extends, node.extends)
897            && all_equals(this.properties, node.properties);
898    },
899    resolve: function(def_class) {
900        return def_class ? this : this.parent_scope.resolve();
901    },
902    walk: function(visitor) {
903        var node = this;
904        visitor.visit(node, function() {
905            if (node.name) node.name.walk(visitor);
906            if (node.extends) node.extends.walk(visitor);
907            node.properties.forEach(function(prop) {
908                prop.walk(visitor);
909            });
910        });
911    },
912    _validate: function() {
913        if (this.TYPE == "Class") throw new Error("should not instantiate AST_Class");
914        if (this.extends != null) must_be_expression(this, "extends");
915        this.properties.forEach(function(node) {
916            if (!(node instanceof AST_ClassProperty)) throw new Error("properties must contain AST_ClassProperty");
917        });
918    },
919}, AST_BlockScope);
920
921var AST_DefClass = DEFNODE("DefClass", null, {
922    $documentation: "A class definition",
923    $propdoc: {
924        name: "[AST_SymbolDefClass] the name of this class",
925    },
926    _validate: function() {
927        if (!(this.name instanceof AST_SymbolDefClass)) throw new Error("name must be AST_SymbolDefClass");
928    },
929}, AST_Class);
930
931var AST_ClassExpression = DEFNODE("ClassExpression", null, {
932    $documentation: "A class expression",
933    $propdoc: {
934        name: "[AST_SymbolClass?] the name of this class, or null if not specified",
935    },
936    _validate: function() {
937        if (this.name != null) {
938            if (!(this.name instanceof AST_SymbolClass)) throw new Error("name must be AST_SymbolClass");
939        }
940    },
941}, AST_Class);
942
943var AST_ClassProperty = DEFNODE("ClassProperty", "key private static value", {
944    $documentation: "Base class for `class` properties",
945    $propdoc: {
946        key: "[string|AST_Node?] property name (AST_Node for computed property, null for initialization block)",
947        private: "[boolean] whether this is a private property",
948        static: "[boolean] whether this is a static property",
949        value: "[AST_Node?] property value (AST_Accessor for getters/setters, AST_LambdaExpression for methods, null if not specified for fields)",
950    },
951    _equals: function(node) {
952        return !this.private == !node.private
953            && !this.static == !node.static
954            && prop_equals(this.key, node.key)
955            && prop_equals(this.value, node.value);
956    },
957    walk: function(visitor) {
958        var node = this;
959        visitor.visit(node, function() {
960            if (node.key instanceof AST_Node) node.key.walk(visitor);
961            if (node.value) node.value.walk(visitor);
962        });
963    },
964    _validate: function() {
965        if (this.TYPE == "ClassProperty") throw new Error("should not instantiate AST_ClassProperty");
966        if (this instanceof AST_ClassInit) {
967            if (this.key != null) throw new Error("key must be null");
968        } else if (typeof this.key != "string") {
969            if (!(this.key instanceof AST_Node)) throw new Error("key must be string or AST_Node");
970            must_be_expression(this, "key");
971        }
972        if(this.value != null) {
973            if (!(this.value instanceof AST_Node)) throw new Error("value must be AST_Node");
974        }
975    },
976});
977
978var AST_ClassField = DEFNODE("ClassField", null, {
979    $documentation: "A `class` field",
980    _validate: function() {
981        if(this.value != null) must_be_expression(this, "value");
982    },
983}, AST_ClassProperty);
984
985var AST_ClassGetter = DEFNODE("ClassGetter", null, {
986    $documentation: "A `class` getter",
987    _validate: function() {
988        if (!(this.value instanceof AST_Accessor)) throw new Error("value must be AST_Accessor");
989    },
990}, AST_ClassProperty);
991
992var AST_ClassSetter = DEFNODE("ClassSetter", null, {
993    $documentation: "A `class` setter",
994    _validate: function() {
995        if (!(this.value instanceof AST_Accessor)) throw new Error("value must be AST_Accessor");
996    },
997}, AST_ClassProperty);
998
999var AST_ClassMethod = DEFNODE("ClassMethod", null, {
1000    $documentation: "A `class` method",
1001    _validate: function() {
1002        if (!(this.value instanceof AST_LambdaExpression)) throw new Error("value must be AST_LambdaExpression");
1003        if (is_arrow(this.value)) throw new Error("value cannot be AST_Arrow or AST_AsyncArrow");
1004        if (this.value.name != null) throw new Error("name of class method's lambda must be null");
1005    },
1006}, AST_ClassProperty);
1007
1008var AST_ClassInit = DEFNODE("ClassInit", null, {
1009    $documentation: "A `class` static initialization block",
1010    _validate: function() {
1011        if (!this.static) throw new Error("static must be true");
1012        if (!(this.value instanceof AST_ClassInitBlock)) throw new Error("value must be AST_ClassInitBlock");
1013    },
1014    initialize: function() {
1015        this.static = true;
1016    },
1017}, AST_ClassProperty);
1018
1019/* -----[ JUMPS ]----- */
1020
1021var AST_Jump = DEFNODE("Jump", null, {
1022    $documentation: "Base class forjumps” (for now that's `return`, `throw`, `break` and `continue`)",
1023    _validate: function() {
1024        if (this.TYPE == "Jump") throw new Error("should not instantiate AST_Jump");
1025    },
1026}, AST_Statement);
1027
1028var AST_Exit = DEFNODE("Exit", "value", {
1029    $documentation: "Base class for “exits” (`return` and `throw`)",
1030    $propdoc: {
1031        value: "[AST_Node?] the value returned or thrown by this statement; could be null for AST_Return"
1032    },
1033    _equals: function(node) {
1034        return prop_equals(this.value, node.value);
1035    },
1036    walk: function(visitor) {
1037        var node = this;
1038        visitor.visit(node, function() {
1039            if (node.value) node.value.walk(visitor);
1040        });
1041    },
1042    _validate: function() {
1043        if (this.TYPE == "Exit") throw new Error("should not instantiate AST_Exit");
1044    },
1045}, AST_Jump);
1046
1047var AST_Return = DEFNODE("Return", null, {
1048    $documentation: "A `return` statement",
1049    _validate: function() {
1050        if (this.value != null) must_be_expression(this, "value");
1051    },
1052}, AST_Exit);
1053
1054var AST_Throw = DEFNODE("Throw", null, {
1055    $documentation: "A `throw` statement",
1056    _validate: function() {
1057        must_be_expression(this, "value");
1058    },
1059}, AST_Exit);
1060
1061var AST_LoopControl = DEFNODE("LoopControl", "label", {
1062    $documentation: "Base class for loop control statements (`break` and `continue`)",
1063    $propdoc: {
1064        label: "[AST_LabelRef?] the label, or null if none",
1065    },
1066    _equals: function(node) {
1067        return prop_equals(this.label, node.label);
1068    },
1069    walk: function(visitor) {
1070        var node = this;
1071        visitor.visit(node, function() {
1072            if (node.label) node.label.walk(visitor);
1073        });
1074    },
1075    _validate: function() {
1076        if (this.TYPE == "LoopControl") throw new Error("should not instantiate AST_LoopControl");
1077        if (this.label != null) {
1078            if (!(this.label instanceof AST_LabelRef)) throw new Error("label must be AST_LabelRef");
1079        }
1080    },
1081}, AST_Jump);
1082
1083var AST_Break = DEFNODE("Break", null, {
1084    $documentation: "A `break` statement"
1085}, AST_LoopControl);
1086
1087var AST_Continue = DEFNODE("Continue", null, {
1088    $documentation: "A `continue` statement"
1089}, AST_LoopControl);
1090
1091/* -----[ IF ]----- */
1092
1093var AST_If = DEFNODE("If", "condition alternative", {
1094    $documentation: "A `if` statement",
1095    $propdoc: {
1096        condition: "[AST_Node] the `if` condition",
1097        alternative: "[AST_Statement?] the `else` part, or null if not present"
1098    },
1099    _equals: function(node) {
1100        return this.body.equals(node.body)
1101            && this.condition.equals(node.condition)
1102            && prop_equals(this.alternative, node.alternative);
1103    },
1104    walk: function(visitor) {
1105        var node = this;
1106        visitor.visit(node, function() {
1107            node.condition.walk(visitor);
1108            node.body.walk(visitor);
1109            if (node.alternative) node.alternative.walk(visitor);
1110        });
1111    },
1112    _validate: function() {
1113        must_be_expression(this, "condition");
1114        if (this.alternative != null) {
1115            if (!is_statement(this.alternative)) throw new Error("alternative must be AST_Statement");
1116        }
1117    },
1118}, AST_StatementWithBody);
1119
1120/* -----[ SWITCH ]----- */
1121
1122var AST_Switch = DEFNODE("Switch", "expression", {
1123    $documentation: "A `switch` statement",
1124    $propdoc: {
1125        expression: "[AST_Node] the `switch` “discriminant”"
1126    },
1127    _equals: function(node) {
1128        return this.expression.equals(node.expression)
1129            && all_equals(this.body, node.body);
1130    },
1131    walk: function(visitor) {
1132        var node = this;
1133        visitor.visit(node, function() {
1134            node.expression.walk(visitor);
1135            walk_body(node, visitor);
1136        });
1137    },
1138    _validate: function() {
1139        must_be_expression(this, "expression");
1140        this.body.forEach(function(node) {
1141            if (!(node instanceof AST_SwitchBranch)) throw new Error("body must be AST_SwitchBranch[]");
1142        });
1143    },
1144}, AST_Block);
1145
1146var AST_SwitchBranch = DEFNODE("SwitchBranch", null, {
1147    $documentation: "Base class for `switch` branches",
1148    _validate: function() {
1149        if (this.TYPE == "SwitchBranch") throw new Error("should not instantiate AST_SwitchBranch");
1150    },
1151}, AST_Block);
1152
1153var AST_Default = DEFNODE("Default", null, {
1154    $documentation: "A `default` switch branch",
1155}, AST_SwitchBranch);
1156
1157var AST_Case = DEFNODE("Case", "expression", {
1158    $documentation: "A `case` switch branch",
1159    $propdoc: {
1160        expression: "[AST_Node] the `case` expression"
1161    },
1162    _equals: function(node) {
1163        return this.expression.equals(node.expression)
1164            && all_equals(this.body, node.body);
1165    },
1166    walk: function(visitor) {
1167        var node = this;
1168        visitor.visit(node, function() {
1169            node.expression.walk(visitor);
1170            walk_body(node, visitor);
1171        });
1172    },
1173    _validate: function() {
1174        must_be_expression(this, "expression");
1175    },
1176}, AST_SwitchBranch);
1177
1178/* -----[ EXCEPTIONS ]----- */
1179
1180var AST_Try = DEFNODE("Try", "bcatch bfinally", {
1181    $documentation: "A `try` statement",
1182    $propdoc: {
1183        bcatch: "[AST_Catch?] the catch block, or null if not present",
1184        bfinally: "[AST_Finally?] the finally block, or null if not present"
1185    },
1186    _equals: function(node) {
1187        return all_equals(this.body, node.body)
1188            && prop_equals(this.bcatch, node.bcatch)
1189            && prop_equals(this.bfinally, node.bfinally);
1190    },
1191    walk: function(visitor) {
1192        var node = this;
1193        visitor.visit(node, function() {
1194            walk_body(node, visitor);
1195            if (node.bcatch) node.bcatch.walk(visitor);
1196            if (node.bfinally) node.bfinally.walk(visitor);
1197        });
1198    },
1199    _validate: function() {
1200        if (this.bcatch != null) {
1201            if (!(this.bcatch instanceof AST_Catch)) throw new Error("bcatch must be AST_Catch");
1202        }
1203        if (this.bfinally != null) {
1204            if (!(this.bfinally instanceof AST_Finally)) throw new Error("bfinally must be AST_Finally");
1205        }
1206    },
1207}, AST_Block);
1208
1209var AST_Catch = DEFNODE("Catch", "argname", {
1210    $documentation: "A `catch` node; only makes sense as part of a `try` statement",
1211    $propdoc: {
1212        argname: "[(AST_Destructured|AST_SymbolCatch)?] symbol for the exception, or null if not present",
1213    },
1214    _equals: function(node) {
1215        return prop_equals(this.argname, node.argname)
1216            && all_equals(this.body, node.body);
1217    },
1218    walk: function(visitor) {
1219        var node = this;
1220        visitor.visit(node, function() {
1221            if (node.argname) node.argname.walk(visitor);
1222            walk_body(node, visitor);
1223        });
1224    },
1225    _validate: function() {
1226        if (this.argname != null) validate_destructured(this.argname, function(node) {
1227            if (!(node instanceof AST_SymbolCatch)) throw new Error("argname must be AST_SymbolCatch");
1228        });
1229    },
1230}, AST_Block);
1231
1232var AST_Finally = DEFNODE("Finally", null, {
1233    $documentation: "A `finally` node; only makes sense as part of a `try` statement"
1234}, AST_Block);
1235
1236/* -----[ VAR ]----- */
1237
1238var AST_Definitions = DEFNODE("Definitions", "definitions", {
1239    $documentation: "Base class for `var` nodes (variable declarations/initializations)",
1240    $propdoc: {
1241        definitions: "[AST_VarDef*] array of variable definitions"
1242    },
1243    _equals: function(node) {
1244        return all_equals(this.definitions, node.definitions);
1245    },
1246    walk: function(visitor) {
1247        var node = this;
1248        visitor.visit(node, function() {
1249            node.definitions.forEach(function(defn) {
1250                defn.walk(visitor);
1251            });
1252        });
1253    },
1254    _validate: function() {
1255        if (this.TYPE == "Definitions") throw new Error("should not instantiate AST_Definitions");
1256        if (this.definitions.length < 1) throw new Error("must have at least one definition");
1257    },
1258}, AST_Statement);
1259
1260var AST_Const = DEFNODE("Const", null, {
1261    $documentation: "A `const` statement",
1262    _validate: function() {
1263        this.definitions.forEach(function(node) {
1264            if (!(node instanceof AST_VarDef)) throw new Error("definitions must be AST_VarDef[]");
1265            validate_destructured(node.name, function(node) {
1266                if (!(node instanceof AST_SymbolConst)) throw new Error("name must be AST_SymbolConst");
1267            });
1268        });
1269    },
1270}, AST_Definitions);
1271
1272var AST_Let = DEFNODE("Let", null, {
1273    $documentation: "A `let` statement",
1274    _validate: function() {
1275        this.definitions.forEach(function(node) {
1276            if (!(node instanceof AST_VarDef)) throw new Error("definitions must be AST_VarDef[]");
1277            validate_destructured(node.name, function(node) {
1278                if (!(node instanceof AST_SymbolLet)) throw new Error("name must be AST_SymbolLet");
1279            });
1280        });
1281    },
1282}, AST_Definitions);
1283
1284var AST_Var = DEFNODE("Var", null, {
1285    $documentation: "A `var` statement",
1286    _validate: function() {
1287        this.definitions.forEach(function(node) {
1288            if (!(node instanceof AST_VarDef)) throw new Error("definitions must be AST_VarDef[]");
1289            validate_destructured(node.name, function(node) {
1290                if (!(node instanceof AST_SymbolVar)) throw new Error("name must be AST_SymbolVar");
1291            });
1292        });
1293    },
1294}, AST_Definitions);
1295
1296var AST_VarDef = DEFNODE("VarDef", "name value", {
1297    $documentation: "A variable declaration; only appears in a AST_Definitions node",
1298    $propdoc: {
1299        name: "[AST_Destructured|AST_SymbolVar] name of the variable",
1300        value: "[AST_Node?] initializer, or null of there's no initializer",
1301    },
1302    _equals: function(node) {
1303        return this.name.equals(node.name)
1304            && prop_equals(this.value, node.value);
1305    },
1306    walk: function(visitor) {
1307        var node = this;
1308        visitor.visit(node, function() {
1309            node.name.walk(visitor);
1310            if (node.value) node.value.walk(visitor);
1311        });
1312    },
1313    _validate: function() {
1314        if (this.value != null) must_be_expression(this, "value");
1315    },
1316});
1317
1318/* -----[ OTHER ]----- */
1319
1320var AST_ExportDeclaration = DEFNODE("ExportDeclaration", "body", {
1321    $documentation: "An `export` statement",
1322    $propdoc: {
1323        body: "[AST_DefClass|AST_Definitions|AST_LambdaDefinition] the statement to export",
1324    },
1325    _equals: function(node) {
1326        return this.body.equals(node.body);
1327    },
1328    walk: function(visitor) {
1329        var node = this;
1330        visitor.visit(node, function() {
1331            node.body.walk(visitor);
1332        });
1333    },
1334    _validate: function() {
1335        if (!(this.body instanceof AST_DefClass
1336            || this.body instanceof AST_Definitions
1337            || this.body instanceof AST_LambdaDefinition)) {
1338            throw new Error("body must be AST_DefClass, AST_Definitions or AST_LambdaDefinition");
1339        }
1340    },
1341}, AST_Statement);
1342
1343var AST_ExportDefault = DEFNODE("ExportDefault", "body", {
1344    $documentation: "An `export default` statement",
1345    $propdoc: {
1346        body: "[AST_Node] the default export",
1347    },
1348    _equals: function(node) {
1349        return this.body.equals(node.body);
1350    },
1351    walk: function(visitor) {
1352        var node = this;
1353        visitor.visit(node, function() {
1354            node.body.walk(visitor);
1355        });
1356    },
1357    _validate: function() {
1358        if (!(this.body instanceof AST_DefClass || this.body instanceof AST_LambdaDefinition)) {
1359            must_be_expression(this, "body");
1360        }
1361    },
1362}, AST_Statement);
1363
1364var AST_ExportForeign = DEFNODE("ExportForeign", "aliases keys path", {
1365    $documentation: "An `export ... from '...'` statement",
1366    $propdoc: {
1367        aliases: "[AST_String*] array of aliases to export",
1368        keys: "[AST_String*] array of keys to import",
1369        path: "[AST_String] the path to import module",
1370    },
1371    _equals: function(node) {
1372        return this.path.equals(node.path)
1373            && all_equals(this.aliases, node.aliases)
1374            && all_equals(this.keys, node.keys);
1375    },
1376    _validate: function() {
1377        if (this.aliases.length != this.keys.length) {
1378            throw new Error("aliases:key length mismatch: " + this.aliases.length + " != " + this.keys.length);
1379        }
1380        this.aliases.forEach(function(name) {
1381            if (!(name instanceof AST_String)) throw new Error("aliases must contain AST_String");
1382        });
1383        this.keys.forEach(function(name) {
1384            if (!(name instanceof AST_String)) throw new Error("keys must contain AST_String");
1385        });
1386        if (!(this.path instanceof AST_String)) throw new Error("path must be AST_String");
1387    },
1388}, AST_Statement);
1389
1390var AST_ExportReferences = DEFNODE("ExportReferences", "properties", {
1391    $documentation: "An `export { ... }` statement",
1392    $propdoc: {
1393        properties: "[AST_SymbolExport*] array of aliases to export",
1394    },
1395    _equals: function(node) {
1396        return all_equals(this.properties, node.properties);
1397    },
1398    walk: function(visitor) {
1399        var node = this;
1400        visitor.visit(node, function() {
1401            node.properties.forEach(function(prop) {
1402                prop.walk(visitor);
1403            });
1404        });
1405    },
1406    _validate: function() {
1407        this.properties.forEach(function(prop) {
1408            if (!(prop instanceof AST_SymbolExport)) throw new Error("properties must contain AST_SymbolExport");
1409        });
1410    },
1411}, AST_Statement);
1412
1413var AST_Import = DEFNODE("Import", "all default path properties", {
1414    $documentation: "An `import` statement",
1415    $propdoc: {
1416        all: "[AST_SymbolImport?] the imported namespace, or null if not specified",
1417        default: "[AST_SymbolImport?] the alias for default `export`, or null if not specified",
1418        path: "[AST_String] the path to import module",
1419        properties: "[(AST_SymbolImport*)?] array of aliases, or null if not specified",
1420    },
1421    _equals: function(node) {
1422        return this.path.equals(node.path)
1423            && prop_equals(this.all, node.all)
1424            && prop_equals(this.default, node.default)
1425            && !this.properties == !node.properties
1426            && (!this.properties || all_equals(this.properties, node.properties));
1427    },
1428    walk: function(visitor) {
1429        var node = this;
1430        visitor.visit(node, function() {
1431            if (node.all) node.all.walk(visitor);
1432            if (node.default) node.default.walk(visitor);
1433            if (node.properties) node.properties.forEach(function(prop) {
1434                prop.walk(visitor);
1435            });
1436        });
1437    },
1438    _validate: function() {
1439        if (this.all != null) {
1440            if (!(this.all instanceof AST_SymbolImport)) throw new Error("all must be AST_SymbolImport");
1441            if (this.properties != null) throw new Error("cannot import both * and {} in the same statement");
1442        }
1443        if (this.default != null) {
1444            if (!(this.default instanceof AST_SymbolImport)) throw new Error("default must be AST_SymbolImport");
1445            if (this.default.key.value !== "") throw new Error("invalid default key: " + this.default.key.value);
1446        }
1447        if (!(this.path instanceof AST_String)) throw new Error("path must be AST_String");
1448        if (this.properties != null) this.properties.forEach(function(node) {
1449            if (!(node instanceof AST_SymbolImport)) throw new Error("properties must contain AST_SymbolImport");
1450        });
1451    },
1452}, AST_Statement);
1453
1454var AST_DefaultValue = DEFNODE("DefaultValue", "name value", {
1455    $documentation: "A default value declaration",
1456    $propdoc: {
1457        name: "[AST_Destructured|AST_SymbolDeclaration] name of the variable",
1458        value: "[AST_Node] value to assign if variable is `undefined`",
1459    },
1460    _equals: function(node) {
1461        return this.name.equals(node.name)
1462            && this.value.equals(node.value);
1463    },
1464    walk: function(visitor) {
1465        var node = this;
1466        visitor.visit(node, function() {
1467            node.name.walk(visitor);
1468            node.value.walk(visitor);
1469        });
1470    },
1471    _validate: function() {
1472        must_be_expression(this, "value");
1473    },
1474});
1475
1476function must_be_expressions(node, prop, allow_spread, allow_hole) {
1477    node[prop].forEach(function(node) {
1478        validate_expression(node, prop, true, allow_spread, allow_hole);
1479    });
1480}
1481
1482var AST_Call = DEFNODE("Call", "args expression optional pure terminal", {
1483    $documentation: "A function call expression",
1484    $propdoc: {
1485        args: "[AST_Node*] array of arguments",
1486        expression: "[AST_Node] expression to invoke as function",
1487        optional: "[boolean] whether the expression is optional chaining",
1488        pure: "[boolean/S] marker for side-effect-free call expression",
1489        terminal: "[boolean] whether the chain has ended",
1490    },
1491    _equals: function(node) {
1492        return !this.optional == !node.optional
1493            && this.expression.equals(node.expression)
1494            && all_equals(this.args, node.args);
1495    },
1496    walk: function(visitor) {
1497        var node = this;
1498        visitor.visit(node, function() {
1499            node.expression.walk(visitor);
1500            node.args.forEach(function(arg) {
1501                arg.walk(visitor);
1502            });
1503        });
1504    },
1505    _validate: function() {
1506        must_be_expression(this, "expression");
1507        must_be_expressions(this, "args", true);
1508    },
1509});
1510
1511var AST_New = DEFNODE("New", null, {
1512    $documentation: "An object instantiation.  Derives from a function call since it has exactly the same properties",
1513    _validate: function() {
1514        if (this.optional) throw new Error("optional must be false");
1515        if (this.terminal) throw new Error("terminal must be false");
1516    },
1517}, AST_Call);
1518
1519var AST_Sequence = DEFNODE("Sequence", "expressions", {
1520    $documentation: "A sequence expression (comma-separated expressions)",
1521    $propdoc: {
1522        expressions: "[AST_Node*] array of expressions (at least two)",
1523    },
1524    _equals: function(node) {
1525        return all_equals(this.expressions, node.expressions);
1526    },
1527    walk: function(visitor) {
1528        var node = this;
1529        visitor.visit(node, function() {
1530            node.expressions.forEach(function(expr) {
1531                expr.walk(visitor);
1532            });
1533        });
1534    },
1535    _validate: function() {
1536        if (this.expressions.length < 2) throw new Error("expressions must contain multiple elements");
1537        must_be_expressions(this, "expressions");
1538    },
1539});
1540
1541function root_expr(prop) {
1542    while (prop instanceof AST_PropAccess) prop = prop.expression;
1543    return prop;
1544}
1545
1546var AST_PropAccess = DEFNODE("PropAccess", "expression optional property terminal", {
1547    $documentation: "Base class for property access expressions, i.e. `a.foo` or `a[\"foo\"]`",
1548    $propdoc: {
1549        expression: "[AST_Node] thecontainerexpression",
1550        optional: "[boolean] whether the expression is optional chaining",
1551        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",
1552        terminal: "[boolean] whether the chain has ended",
1553    },
1554    _equals: function(node) {
1555        return !this.optional == !node.optional
1556            && prop_equals(this.property, node.property)
1557            && this.expression.equals(node.expression);
1558    },
1559    get_property: function() {
1560        var p = this.property;
1561        if (p instanceof AST_Constant) return p.value;
1562        if (p instanceof AST_UnaryPrefix && p.operator == "void" && p.expression instanceof AST_Constant) return;
1563        return p;
1564    },
1565    _validate: function() {
1566        if (this.TYPE == "PropAccess") throw new Error("should not instantiate AST_PropAccess");
1567        must_be_expression(this, "expression");
1568    },
1569});
1570
1571var AST_Dot = DEFNODE("Dot", "quoted", {
1572    $documentation: "A dotted property access expression",
1573    $propdoc: {
1574        quoted: "[boolean] whether property is transformed from a quoted string",
1575    },
1576    walk: function(visitor) {
1577        var node = this;
1578        visitor.visit(node, function() {
1579            node.expression.walk(visitor);
1580        });
1581    },
1582    _validate: function() {
1583        if (typeof this.property != "string") throw new Error("property must be string");
1584    },
1585}, AST_PropAccess);
1586
1587var AST_Sub = DEFNODE("Sub", null, {
1588    $documentation: "Index-style property access, i.e. `a[\"foo\"]`",
1589    walk: function(visitor) {
1590        var node = this;
1591        visitor.visit(node, function() {
1592            node.expression.walk(visitor);
1593            node.property.walk(visitor);
1594        });
1595    },
1596    _validate: function() {
1597        must_be_expression(this, "property");
1598    },
1599}, AST_PropAccess);
1600
1601var AST_Spread = DEFNODE("Spread", "expression", {
1602    $documentation: "Spread expression in array/object literals or function calls",
1603    $propdoc: {
1604        expression: "[AST_Node] expression to be expanded",
1605    },
1606    _equals: function(node) {
1607        return this.expression.equals(node.expression);
1608    },
1609    walk: function(visitor) {
1610        var node = this;
1611        visitor.visit(node, function() {
1612            node.expression.walk(visitor);
1613        });
1614    },
1615    _validate: function() {
1616        must_be_expression(this, "expression");
1617    },
1618});
1619
1620var AST_Unary = DEFNODE("Unary", "operator expression", {
1621    $documentation: "Base class for unary expressions",
1622    $propdoc: {
1623        operator: "[string] the operator",
1624        expression: "[AST_Node] expression that this unary operator applies to",
1625    },
1626    _equals: function(node) {
1627        return this.operator == node.operator
1628            && this.expression.equals(node.expression);
1629    },
1630    walk: function(visitor) {
1631        var node = this;
1632        visitor.visit(node, function() {
1633            node.expression.walk(visitor);
1634        });
1635    },
1636    _validate: function() {
1637        if (this.TYPE == "Unary") throw new Error("should not instantiate AST_Unary");
1638        if (typeof this.operator != "string") throw new Error("operator must be string");
1639        must_be_expression(this, "expression");
1640    },
1641});
1642
1643var AST_UnaryPrefix = DEFNODE("UnaryPrefix", null, {
1644    $documentation: "Unary prefix expression, i.e. `typeof i` or `++i`"
1645}, AST_Unary);
1646
1647var AST_UnaryPostfix = DEFNODE("UnaryPostfix", null, {
1648    $documentation: "Unary postfix expression, i.e. `i++`"
1649}, AST_Unary);
1650
1651var AST_Binary = DEFNODE("Binary", "operator left right", {
1652    $documentation: "Binary expression, i.e. `a + b`",
1653    $propdoc: {
1654        left: "[AST_Node] left-hand side expression",
1655        operator: "[string] the operator",
1656        right: "[AST_Node] right-hand side expression"
1657    },
1658    _equals: function(node) {
1659        return this.operator == node.operator
1660            && this.left.equals(node.left)
1661            && this.right.equals(node.right);
1662    },
1663    walk: function(visitor) {
1664        var node = this;
1665        visitor.visit(node, function() {
1666            node.left.walk(visitor);
1667            node.right.walk(visitor);
1668        });
1669    },
1670    _validate: function() {
1671        if (!(this instanceof AST_Assign)) must_be_expression(this, "left");
1672        if (typeof this.operator != "string") throw new Error("operator must be string");
1673        must_be_expression(this, "right");
1674    },
1675});
1676
1677var AST_Conditional = DEFNODE("Conditional", "condition consequent alternative", {
1678    $documentation: "Conditional expression using the ternary operator, i.e. `a ? b : c`",
1679    $propdoc: {
1680        condition: "[AST_Node]",
1681        consequent: "[AST_Node]",
1682        alternative: "[AST_Node]"
1683    },
1684    _equals: function(node) {
1685        return this.condition.equals(node.condition)
1686            && this.consequent.equals(node.consequent)
1687            && this.alternative.equals(node.alternative);
1688    },
1689    walk: function(visitor) {
1690        var node = this;
1691        visitor.visit(node, function() {
1692            node.condition.walk(visitor);
1693            node.consequent.walk(visitor);
1694            node.alternative.walk(visitor);
1695        });
1696    },
1697    _validate: function() {
1698        must_be_expression(this, "condition");
1699        must_be_expression(this, "consequent");
1700        must_be_expression(this, "alternative");
1701    },
1702});
1703
1704var AST_Assign = DEFNODE("Assign", null, {
1705    $documentation: "An assignment expression — `a = b + 5`",
1706    _validate: function() {
1707        if (this.operator.indexOf("=") < 0) throw new Error('operator must contain "="');
1708        if (this.left instanceof AST_Destructured) {
1709            if (this.operator != "=") throw new Error("invalid destructuring operator: " + this.operator);
1710            validate_destructured(this.left, function(node) {
1711                if (!(node instanceof AST_PropAccess || node instanceof AST_SymbolRef)) {
1712                    throw new Error("left must be assignable: " + node.TYPE);
1713                }
1714            });
1715        } else if (!(this.left instanceof AST_Infinity
1716            || this.left instanceof AST_NaN
1717            || this.left instanceof AST_PropAccess && !this.left.optional
1718            || this.left instanceof AST_SymbolRef
1719            || this.left instanceof AST_Undefined)) {
1720            throw new Error("left must be assignable");
1721        }
1722    },
1723}, AST_Binary);
1724
1725var AST_Await = DEFNODE("Await", "expression", {
1726    $documentation: "An await expression",
1727    $propdoc: {
1728        expression: "[AST_Node] expression with Promise to resolve on",
1729    },
1730    _equals: function(node) {
1731        return this.expression.equals(node.expression);
1732    },
1733    walk: function(visitor) {
1734        var node = this;
1735        visitor.visit(node, function() {
1736            node.expression.walk(visitor);
1737        });
1738    },
1739    _validate: function() {
1740        must_be_expression(this, "expression");
1741    },
1742});
1743
1744var AST_Yield = DEFNODE("Yield", "expression nested", {
1745    $documentation: "A yield expression",
1746    $propdoc: {
1747        expression: "[AST_Node?] return value for iterator, or null if undefined",
1748        nested: "[boolean] whether to iterate over expression as generator",
1749    },
1750    _equals: function(node) {
1751        return !this.nested == !node.nested
1752            && prop_equals(this.expression, node.expression);
1753    },
1754    walk: function(visitor) {
1755        var node = this;
1756        visitor.visit(node, function() {
1757            if (node.expression) node.expression.walk(visitor);
1758        });
1759    },
1760    _validate: function() {
1761        if (this.expression != null) {
1762            must_be_expression(this, "expression");
1763        } else if (this.nested) {
1764            throw new Error("yield* must contain expression");
1765        }
1766    },
1767});
1768
1769/* -----[ LITERALS ]----- */
1770
1771var AST_Array = DEFNODE("Array", "elements", {
1772    $documentation: "An array literal",
1773    $propdoc: {
1774        elements: "[AST_Node*] array of elements"
1775    },
1776    _equals: function(node) {
1777        return all_equals(this.elements, node.elements);
1778    },
1779    walk: function(visitor) {
1780        var node = this;
1781        visitor.visit(node, function() {
1782            node.elements.forEach(function(element) {
1783                element.walk(visitor);
1784            });
1785        });
1786    },
1787    _validate: function() {
1788        must_be_expressions(this, "elements", true, true);
1789    },
1790});
1791
1792var AST_Destructured = DEFNODE("Destructured", "rest", {
1793    $documentation: "Base class for destructured literal",
1794    $propdoc: {
1795        rest: "[(AST_Destructured|AST_SymbolDeclaration|AST_SymbolRef)?] rest parameter, or null if absent",
1796    },
1797    _validate: function() {
1798        if (this.TYPE == "Destructured") throw new Error("should not instantiate AST_Destructured");
1799    },
1800});
1801
1802function validate_destructured(node, check, allow_default) {
1803    if (node instanceof AST_DefaultValue && allow_default) return validate_destructured(node.name, check);
1804    if (node instanceof AST_Destructured) {
1805        if (node.rest != null) validate_destructured(node.rest, check);
1806        if (node instanceof AST_DestructuredArray) return node.elements.forEach(function(node) {
1807            if (!(node instanceof AST_Hole)) validate_destructured(node, check, true);
1808        });
1809        if (node instanceof AST_DestructuredObject) return node.properties.forEach(function(prop) {
1810            validate_destructured(prop.value, check, true);
1811        });
1812    }
1813    check(node);
1814}
1815
1816var AST_DestructuredArray = DEFNODE("DestructuredArray", "elements", {
1817    $documentation: "A destructured array literal",
1818    $propdoc: {
1819        elements: "[(AST_DefaultValue|AST_Destructured|AST_SymbolDeclaration|AST_SymbolRef)*] array of elements",
1820    },
1821    _equals: function(node) {
1822        return prop_equals(this.rest, node.rest)
1823            && all_equals(this.elements, node.elements);
1824    },
1825    walk: function(visitor) {
1826        var node = this;
1827        visitor.visit(node, function() {
1828            node.elements.forEach(function(element) {
1829                element.walk(visitor);
1830            });
1831            if (node.rest) node.rest.walk(visitor);
1832        });
1833    },
1834}, AST_Destructured);
1835
1836var AST_DestructuredKeyVal = DEFNODE("DestructuredKeyVal", "key value", {
1837    $documentation: "A key: value destructured property",
1838    $propdoc: {
1839        key: "[string|AST_Node] property name.  For computed property this is an AST_Node.",
1840        value: "[AST_DefaultValue|AST_Destructured|AST_SymbolDeclaration|AST_SymbolRef] property value",
1841    },
1842    _equals: function(node) {
1843        return prop_equals(this.key, node.key)
1844            && this.value.equals(node.value);
1845    },
1846    walk: function(visitor) {
1847        var node = this;
1848        visitor.visit(node, function() {
1849            if (node.key instanceof AST_Node) node.key.walk(visitor);
1850            node.value.walk(visitor);
1851        });
1852    },
1853    _validate: function() {
1854        if (typeof this.key != "string") {
1855            if (!(this.key instanceof AST_Node)) throw new Error("key must be string or AST_Node");
1856            must_be_expression(this, "key");
1857        }
1858        if (!(this.value instanceof AST_Node)) throw new Error("value must be AST_Node");
1859    },
1860});
1861
1862var AST_DestructuredObject = DEFNODE("DestructuredObject", "properties", {
1863    $documentation: "A destructured object literal",
1864    $propdoc: {
1865        properties: "[AST_DestructuredKeyVal*] array of properties",
1866    },
1867    _equals: function(node) {
1868        return prop_equals(this.rest, node.rest)
1869            && all_equals(this.properties, node.properties);
1870    },
1871    walk: function(visitor) {
1872        var node = this;
1873        visitor.visit(node, function() {
1874            node.properties.forEach(function(prop) {
1875                prop.walk(visitor);
1876            });
1877            if (node.rest) node.rest.walk(visitor);
1878        });
1879    },
1880    _validate: function() {
1881        this.properties.forEach(function(node) {
1882            if (!(node instanceof AST_DestructuredKeyVal)) throw new Error("properties must be AST_DestructuredKeyVal[]");
1883        });
1884    },
1885}, AST_Destructured);
1886
1887var AST_Object = DEFNODE("Object", "properties", {
1888    $documentation: "An object literal",
1889    $propdoc: {
1890        properties: "[(AST_ObjectProperty|AST_Spread)*] array of properties"
1891    },
1892    _equals: function(node) {
1893        return all_equals(this.properties, node.properties);
1894    },
1895    walk: function(visitor) {
1896        var node = this;
1897        visitor.visit(node, function() {
1898            node.properties.forEach(function(prop) {
1899                prop.walk(visitor);
1900            });
1901        });
1902    },
1903    _validate: function() {
1904        this.properties.forEach(function(node) {
1905            if (!(node instanceof AST_ObjectProperty || node instanceof AST_Spread)) {
1906                throw new Error("properties must contain AST_ObjectProperty and/or AST_Spread only");
1907            }
1908        });
1909    },
1910});
1911
1912var AST_ObjectProperty = DEFNODE("ObjectProperty", "key value", {
1913    $documentation: "Base class for literal object properties",
1914    $propdoc: {
1915        key: "[string|AST_Node] property name.  For computed property this is an AST_Node.",
1916        value: "[AST_Node] property value.  For getters and setters this is an AST_Accessor.",
1917    },
1918    _equals: function(node) {
1919        return prop_equals(this.key, node.key)
1920            && this.value.equals(node.value);
1921    },
1922    walk: function(visitor) {
1923        var node = this;
1924        visitor.visit(node, function() {
1925            if (node.key instanceof AST_Node) node.key.walk(visitor);
1926            node.value.walk(visitor);
1927        });
1928    },
1929    _validate: function() {
1930        if (this.TYPE == "ObjectProperty") throw new Error("should not instantiate AST_ObjectProperty");
1931        if (typeof this.key != "string") {
1932            if (!(this.key instanceof AST_Node)) throw new Error("key must be string or AST_Node");
1933            must_be_expression(this, "key");
1934        }
1935        if (!(this.value instanceof AST_Node)) throw new Error("value must be AST_Node");
1936    },
1937});
1938
1939var AST_ObjectKeyVal = DEFNODE("ObjectKeyVal", null, {
1940    $documentation: "A key: value object property",
1941    _validate: function() {
1942        must_be_expression(this, "value");
1943    },
1944}, AST_ObjectProperty);
1945
1946var AST_ObjectMethod = DEFNODE("ObjectMethod", null, {
1947    $documentation: "A key(){} object property",
1948    _validate: function() {
1949        if (!(this.value instanceof AST_LambdaExpression)) throw new Error("value must be AST_LambdaExpression");
1950        if (is_arrow(this.value)) throw new Error("value cannot be AST_Arrow or AST_AsyncArrow");
1951        if (this.value.name != null) throw new Error("name of object method's lambda must be null");
1952    },
1953}, AST_ObjectKeyVal);
1954
1955var AST_ObjectSetter = DEFNODE("ObjectSetter", null, {
1956    $documentation: "An object setter property",
1957    _validate: function() {
1958        if (!(this.value instanceof AST_Accessor)) throw new Error("value must be AST_Accessor");
1959    },
1960}, AST_ObjectProperty);
1961
1962var AST_ObjectGetter = DEFNODE("ObjectGetter", null, {
1963    $documentation: "An object getter property",
1964    _validate: function() {
1965        if (!(this.value instanceof AST_Accessor)) throw new Error("value must be AST_Accessor");
1966    },
1967}, AST_ObjectProperty);
1968
1969var AST_Symbol = DEFNODE("Symbol", "scope name thedef", {
1970    $documentation: "Base class for all symbols",
1971    $propdoc: {
1972        name: "[string] name of this symbol",
1973        scope: "[AST_Scope/S] the current scope (not necessarily the definition scope)",
1974        thedef: "[SymbolDef/S] the definition of this symbol"
1975    },
1976    _equals: function(node) {
1977        return this.thedef ? this.thedef === node.thedef : this.name == node.name;
1978    },
1979    _validate: function() {
1980        if (this.TYPE == "Symbol") throw new Error("should not instantiate AST_Symbol");
1981        if (typeof this.name != "string") throw new Error("name must be string");
1982    },
1983});
1984
1985var AST_SymbolDeclaration = DEFNODE("SymbolDeclaration", "init", {
1986    $documentation: "A declaration symbol (symbol in var, function name or argument, symbol in catch)",
1987}, AST_Symbol);
1988
1989var AST_SymbolConst = DEFNODE("SymbolConst", null, {
1990    $documentation: "Symbol defining a constant",
1991}, AST_SymbolDeclaration);
1992
1993var AST_SymbolImport = DEFNODE("SymbolImport", "key", {
1994    $documentation: "Symbol defined by an `import` statement",
1995    $propdoc: {
1996        key: "[AST_String] the original `export` name",
1997    },
1998    _equals: function(node) {
1999        return this.name == node.name
2000            && this.key.equals(node.key);
2001    },
2002    _validate: function() {
2003        if (!(this.key instanceof AST_String)) throw new Error("key must be AST_String");
2004    },
2005}, AST_SymbolConst);
2006
2007var AST_SymbolLet = DEFNODE("SymbolLet", null, {
2008    $documentation: "Symbol defining a lexical-scoped variable",
2009}, AST_SymbolDeclaration);
2010
2011var AST_SymbolVar = DEFNODE("SymbolVar", null, {
2012    $documentation: "Symbol defining a variable",
2013}, AST_SymbolDeclaration);
2014
2015var AST_SymbolFunarg = DEFNODE("SymbolFunarg", "unused", {
2016    $documentation: "Symbol naming a function argument",
2017}, AST_SymbolVar);
2018
2019var AST_SymbolDefun = DEFNODE("SymbolDefun", null, {
2020    $documentation: "Symbol defining a function",
2021}, AST_SymbolDeclaration);
2022
2023var AST_SymbolLambda = DEFNODE("SymbolLambda", null, {
2024    $documentation: "Symbol naming a function expression",
2025}, AST_SymbolDeclaration);
2026
2027var AST_SymbolDefClass = DEFNODE("SymbolDefClass", null, {
2028    $documentation: "Symbol defining a class",
2029}, AST_SymbolConst);
2030
2031var AST_SymbolClass = DEFNODE("SymbolClass", null, {
2032    $documentation: "Symbol naming a class expression",
2033}, AST_SymbolConst);
2034
2035var AST_SymbolCatch = DEFNODE("SymbolCatch", null, {
2036    $documentation: "Symbol naming the exception in catch",
2037}, AST_SymbolDeclaration);
2038
2039var AST_Label = DEFNODE("Label", "references", {
2040    $documentation: "Symbol naming a label (declaration)",
2041    $propdoc: {
2042        references: "[AST_LoopControl*] a list of nodes referring to this label"
2043    },
2044    initialize: function() {
2045        this.references = [];
2046        this.thedef = this;
2047    },
2048}, AST_Symbol);
2049
2050var AST_SymbolRef = DEFNODE("SymbolRef", "fixed in_arg redef", {
2051    $documentation: "Reference to some symbol (not definition/declaration)",
2052}, AST_Symbol);
2053
2054var AST_SymbolExport = DEFNODE("SymbolExport", "alias", {
2055    $documentation: "Reference in an `export` statement",
2056    $propdoc: {
2057        alias: "[AST_String] the `export` alias",
2058    },
2059    _equals: function(node) {
2060        return this.name == node.name
2061            && this.alias.equals(node.alias);
2062    },
2063    _validate: function() {
2064        if (!(this.alias instanceof AST_String)) throw new Error("alias must be AST_String");
2065    },
2066}, AST_SymbolRef);
2067
2068var AST_LabelRef = DEFNODE("LabelRef", null, {
2069    $documentation: "Reference to a label symbol",
2070}, AST_Symbol);
2071
2072var AST_ObjectIdentity = DEFNODE("ObjectIdentity", null, {
2073    $documentation: "Base class for `super` & `this`",
2074    _equals: return_true,
2075    _validate: function() {
2076        if (this.TYPE == "ObjectIdentity") throw new Error("should not instantiate AST_ObjectIdentity");
2077    },
2078}, AST_Symbol);
2079
2080var AST_Super = DEFNODE("Super", null, {
2081    $documentation: "The `super` symbol",
2082    _validate: function() {
2083        if (this.name !== "super") throw new Error('name must be "super"');
2084    },
2085}, AST_ObjectIdentity);
2086
2087var AST_This = DEFNODE("This", null, {
2088    $documentation: "The `this` symbol",
2089    _validate: function() {
2090        if (this.TYPE == "This" && this.name !== "this") throw new Error('name must be "this"');
2091    },
2092}, AST_ObjectIdentity);
2093
2094var AST_NewTarget = DEFNODE("NewTarget", null, {
2095    $documentation: "The `new.target` symbol",
2096    initialize: function() {
2097        this.name = "new.target";
2098    },
2099    _validate: function() {
2100        if (this.name !== "new.target") throw new Error('name must be "new.target": ' + this.name);
2101    },
2102}, AST_This);
2103
2104var AST_Template = DEFNODE("Template", "expressions strings tag", {
2105    $documentation: "A template literal, i.e. tag`str1${expr1}...strN${exprN}strN+1`",
2106    $propdoc: {
2107        expressions: "[AST_Node*] the placeholder expressions",
2108        strings: "[string*] the raw text segments",
2109        tag: "[AST_Node?] tag function, or null if absent",
2110    },
2111    _equals: function(node) {
2112        return prop_equals(this.tag, node.tag)
2113            && list_equals(this.strings, node.strings)
2114            && all_equals(this.expressions, node.expressions);
2115    },
2116    walk: function(visitor) {
2117        var node = this;
2118        visitor.visit(node, function() {
2119            if (node.tag) node.tag.walk(visitor);
2120            node.expressions.forEach(function(expr) {
2121                expr.walk(visitor);
2122            });
2123        });
2124    },
2125    _validate: function() {
2126        if (this.expressions.length + 1 != this.strings.length) {
2127            throw new Error("malformed template with " + this.expressions.length + " placeholder(s) but " + this.strings.length + " text segment(s)");
2128        }
2129        must_be_expressions(this, "expressions");
2130        this.strings.forEach(function(string) {
2131            if (typeof string != "string") throw new Error("strings must contain string");
2132        });
2133        if (this.tag != null) must_be_expression(this, "tag");
2134    },
2135});
2136
2137var AST_Constant = DEFNODE("Constant", null, {
2138    $documentation: "Base class for all constants",
2139    _equals: function(node) {
2140        return this.value === node.value;
2141    },
2142    _validate: function() {
2143        if (this.TYPE == "Constant") throw new Error("should not instantiate AST_Constant");
2144    },
2145});
2146
2147var AST_String = DEFNODE("String", "quote value", {
2148    $documentation: "A string literal",
2149    $propdoc: {
2150        quote: "[string?] the original quote character",
2151        value: "[string] the contents of this string",
2152    },
2153    _validate: function() {
2154        if (this.quote != null) {
2155            if (typeof this.quote != "string") throw new Error("quote must be string");
2156            if (!/^["']$/.test(this.quote)) throw new Error("invalid quote: " + this.quote);
2157        }
2158        if (typeof this.value != "string") throw new Error("value must be string");
2159    },
2160}, AST_Constant);
2161
2162var AST_Number = DEFNODE("Number", "value", {
2163    $documentation: "A number literal",
2164    $propdoc: {
2165        value: "[number] the numeric value",
2166    },
2167    _validate: function() {
2168        if (typeof this.value != "number") throw new Error("value must be number");
2169        if (!isFinite(this.value)) throw new Error("value must be finite");
2170        if (this.value < 0) throw new Error("value cannot be negative");
2171    },
2172}, AST_Constant);
2173
2174var AST_BigInt = DEFNODE("BigInt", "value", {
2175    $documentation: "A BigInt literal",
2176    $propdoc: {
2177        value: "[string] the numeric representation",
2178    },
2179    _validate: function() {
2180        if (typeof this.value != "string") throw new Error("value must be string");
2181        if (this.value[0] == "-") throw new Error("value cannot be negative");
2182    },
2183}, AST_Constant);
2184
2185var AST_RegExp = DEFNODE("RegExp", "value", {
2186    $documentation: "A regexp literal",
2187    $propdoc: {
2188        value: "[RegExp] the actual regexp"
2189    },
2190    _equals: function(node) {
2191        return "" + this.value == "" + node.value;
2192    },
2193    _validate: function() {
2194        if (!(this.value instanceof RegExp)) throw new Error("value must be RegExp");
2195    },
2196}, AST_Constant);
2197
2198var AST_Atom = DEFNODE("Atom", null, {
2199    $documentation: "Base class for atoms",
2200    _equals: return_true,
2201    _validate: function() {
2202        if (this.TYPE == "Atom") throw new Error("should not instantiate AST_Atom");
2203    },
2204}, AST_Constant);
2205
2206var AST_Null = DEFNODE("Null", null, {
2207    $documentation: "The `null` atom",
2208    value: null,
2209}, AST_Atom);
2210
2211var AST_NaN = DEFNODE("NaN", null, {
2212    $documentation: "The impossible value",
2213    value: 0/0,
2214}, AST_Atom);
2215
2216var AST_Undefined = DEFNODE("Undefined", null, {
2217    $documentation: "The `undefined` value",
2218    value: function(){}(),
2219}, AST_Atom);
2220
2221var AST_Hole = DEFNODE("Hole", null, {
2222    $documentation: "A hole in an array",
2223    value: function(){}(),
2224}, AST_Atom);
2225
2226var AST_Infinity = DEFNODE("Infinity", null, {
2227    $documentation: "The `Infinity` value",
2228    value: 1/0,
2229}, AST_Atom);
2230
2231var AST_Boolean = DEFNODE("Boolean", null, {
2232    $documentation: "Base class for booleans",
2233    _validate: function() {
2234        if (this.TYPE == "Boolean") throw new Error("should not instantiate AST_Boolean");
2235    },
2236}, AST_Atom);
2237
2238var AST_False = DEFNODE("False", null, {
2239    $documentation: "The `false` atom",
2240    value: false,
2241}, AST_Boolean);
2242
2243var AST_True = DEFNODE("True", null, {
2244    $documentation: "The `true` atom",
2245    value: true,
2246}, AST_Boolean);
2247
2248/* -----[ TreeWalker ]----- */
2249
2250function TreeWalker(callback) {
2251    this.callback = callback;
2252    this.directives = Object.create(null);
2253    this.stack = [];
2254}
2255TreeWalker.prototype = {
2256    visit: function(node, descend) {
2257        this.push(node);
2258        var done = this.callback(node, descend || noop);
2259        if (!done && descend) descend();
2260        this.pop();
2261    },
2262    parent: function(n) {
2263        return this.stack[this.stack.length - 2 - (n || 0)];
2264    },
2265    push: function(node) {
2266        var value;
2267        if (node instanceof AST_Class) {
2268            this.directives = Object.create(this.directives);
2269            value = "use strict";
2270        } else if (node instanceof AST_Directive) {
2271            value = node.value;
2272        } else if (node instanceof AST_Lambda) {
2273            this.directives = Object.create(this.directives);
2274        }
2275        if (value && !this.directives[value]) this.directives[value] = node;
2276        this.stack.push(node);
2277    },
2278    pop: function() {
2279        var node = this.stack.pop();
2280        if (node instanceof AST_Class || node instanceof AST_Lambda) {
2281            this.directives = Object.getPrototypeOf(this.directives);
2282        }
2283    },
2284    self: function() {
2285        return this.stack[this.stack.length - 1];
2286    },
2287    find_parent: function(type) {
2288        var stack = this.stack;
2289        for (var i = stack.length - 1; --i >= 0;) {
2290            var x = stack[i];
2291            if (x instanceof type) return x;
2292        }
2293    },
2294    has_directive: function(type) {
2295        var dir = this.directives[type];
2296        if (dir) return dir;
2297        var node = this.stack[this.stack.length - 1];
2298        if (node instanceof AST_Scope) {
2299            for (var i = 0; i < node.body.length; ++i) {
2300                var st = node.body[i];
2301                if (!(st instanceof AST_Directive)) break;
2302                if (st.value == type) return st;
2303            }
2304        }
2305    },
2306    loopcontrol_target: function(node) {
2307        var stack = this.stack;
2308        if (node.label) for (var i = stack.length; --i >= 0;) {
2309            var x = stack[i];
2310            if (x instanceof AST_LabeledStatement && x.label.name == node.label.name)
2311                return x.body;
2312        } else for (var i = stack.length; --i >= 0;) {
2313            var x = stack[i];
2314            if (x instanceof AST_IterationStatement
2315                || node instanceof AST_Break && x instanceof AST_Switch)
2316                return x;
2317        }
2318    },
2319    in_boolean_context: function() {
2320        for (var drop = true, level = 0, parent, self = this.self(); parent = this.parent(level++); self = parent) {
2321            if (parent instanceof AST_Binary) switch (parent.operator) {
2322              case "&&":
2323              case "||":
2324                if (parent.left === self) drop = false;
2325                continue;
2326              default:
2327                return false;
2328            }
2329            if (parent instanceof AST_Conditional) {
2330                if (parent.condition === self) return true;
2331                continue;
2332            }
2333            if (parent instanceof AST_DWLoop) return parent.condition === self;
2334            if (parent instanceof AST_For) return parent.condition === self;
2335            if (parent instanceof AST_If) return parent.condition === self;
2336            if (parent instanceof AST_Return) {
2337                if (parent.in_bool) return true;
2338                while (parent = this.parent(level++)) {
2339                    if (parent instanceof AST_Lambda) {
2340                        if (parent.name) return false;
2341                        parent = this.parent(level++);
2342                        if (parent.TYPE != "Call") return false;
2343                        break;
2344                    }
2345                }
2346            }
2347            if (parent instanceof AST_Sequence) {
2348                if (parent.tail_node() === self) continue;
2349                return drop ? "d" : true;
2350            }
2351            if (parent instanceof AST_SimpleStatement) return drop ? "d" : true;
2352            if (parent instanceof AST_UnaryPrefix) return parent.operator == "!";
2353            return false;
2354        }
2355    }
2356};
2357