docs: function parse($TEXT, options) {
  
        options = defaults(options, {
            bare_returns   : false,
            ecma           : 8,
            expression     : false,
            filename       : null,
            html5_comments : true,
            module         : false,
            shebang        : true,
            strict         : false,
            toplevel       : null,
        }, true);
  
        var S = {
            input         : (typeof $TEXT == "string"
                             ? tokenizer($TEXT, options.filename,
                                         options.html5_comments, options.shebang)
                             : $TEXT),
            token         : null,
            prev          : null,
            peeked        : null,
            in_function   : 0,
            in_async      : -1,
            in_generator  : -1,
            in_directives : true,
            in_loop       : 0,
            labels        : []
        };
  
        S.token = next();
  
        function is(type, value) {
            return is_token(S.token, type, value);
        }
  
        function peek() { return S.peeked || (S.peeked = S.input()); }
  
        function next() {
            S.prev = S.token;
  
            if (!S.peeked) peek();
            S.token = S.peeked;
            S.peeked = null;
            S.in_directives = S.in_directives && (
                S.token.type == "string" || is("punc", ";")
            );
            return S.token;
        }
  
        function prev() {
            return S.prev;
        }
  
        function croak(msg, line, col, pos) {
            var ctx = S.input.context();
            js_error(msg,
                     ctx.filename,
                     line != null ? line : ctx.tokline,
                     col != null ? col : ctx.tokcol,
                     pos != null ? pos : ctx.tokpos);
        }
  
        function token_error(token, msg) {
            croak(msg, token.line, token.col);
        }
  
        function unexpected(token) {
            if (token == null)
                token = S.token;
            token_error(token, "Unexpected token: " + token.type + " (" + token.value + ")");
        }
  
        function expect_token(type, val) {
            if (is(type, val)) {
                return next();
            }
            token_error(S.token, "Unexpected token " + S.token.type + " «" + S.token.value + "»" + ", expected " + type + " «" + val + "»");
        }
  
        function expect(punc) { return expect_token("punc", punc); }
  
        function has_newline_before(token) {
            return token.nlb || !all(token.comments_before, function(comment) {
                return !comment.nlb;
            });
        }
  
        function can_insert_semicolon() {
            return !options.strict
                && (is("eof") || is("punc", "}") || has_newline_before(S.token));
        }
  
        function is_in_generator() {
            return S.in_generator === S.in_function;
        }
  
        function is_in_async() {
            return S.in_async === S.in_function;
        }
  
        function semicolon(optional) {
            if (is("punc", ";")) next();
            else if (!optional && !can_insert_semicolon()) unexpected();
        }
  
        function parenthesised() {
            expect("(");
            var exp = expression(true);
            expect(")");
            return exp;
        }
  
        function embed_tokens(parser) {
            return function() {
                var start = S.token;
                var expr = parser.apply(null, arguments);
                var end = prev();
                expr.start = start;
                expr.end = end;
                return expr;
            };
        }
  
        function handle_regexp() {
            if (is("operator", "/") || is("operator", "/=")) {
                S.peeked = null;
                S.token = S.input(S.token.value.substr(1)); // force regexp
            }
        }
  
        var statement = embed_tokens(function(is_export_default, is_for_body, is_if_body) {
            handle_regexp();
            switch (S.token.type) {
              case "string":
                if (S.in_directives) {
                    var token = peek();
                    if (S.token.raw.indexOf("\\") == -1
                        && (is_token(token, "punc", ";")
                            || is_token(token, "punc", "}")
                            || has_newline_before(token)
                            || is_token(token, "eof"))) {
                        S.input.add_directive(S.token.value);
                    } else {
                        S.in_directives = false;
                    }
                }
                var dir = S.in_directives, stat = simple_statement();
                return dir && stat.body instanceof AST_String ? new AST_Directive(stat.body) : stat;
              case "template_head":
              case "num":
              case "regexp":
              case "operator":
              case "atom":
                return simple_statement();
  
              case "name":
                if (S.token.value == "async" && is_token(peek(), "keyword", "function")) {
                    next();
                    next();
                    if (is_for_body) {
                        croak("functions are not allowed as the body of a loop");
                    }
                    return function_(AST_Defun, false, true, is_export_default);
                }
                if (S.token.value == "import" && !is_token(peek(), "punc", "(")) {
                    next();
                    var node = import_();
                    semicolon();
                    return node;
                }
                return is_token(peek(), "punc", ":")
                    ? labeled_statement()
                    : simple_statement();
  
              case "punc":
                switch (S.token.value) {
                  case "{":
                    return new AST_BlockStatement({
                        start : S.token,
                        body  : block_(),
                        end   : prev()
                    });
                  case "[":
                  case "(":
                    return simple_statement();
                  case ";":
                    S.in_directives = false;
                    next();
                    return new AST_EmptyStatement();
                  default:
                    unexpected();
                }
  
              case "keyword":
                switch (S.token.value) {
                  case "break":
                    next();
                    return break_cont(AST_Break);
  
                  case "continue":
                    next();
                    return break_cont(AST_Continue);
  
                  case "debugger":
                    next();
                    semicolon();
                    return new AST_Debugger();
  
                  case "do":
                    next();
                    var body = in_loop(statement);
                    expect_token("keyword", "while");
                    var condition = parenthesised();
                    semicolon(true);
                    return new AST_Do({
                        body      : body,
                        condition : condition
                    });
  
                  case "while":
                    next();
                    return new AST_While({
                        condition : parenthesised(),
                        body      : in_loop(function() { return statement(false, true); })
                    });
  
                  case "for":
                    next();
                    return for_();
  
                  case "class":
                    next();
                    if (is_for_body) {
                        croak("classes are not allowed as the body of a loop");
                    }
                    if (is_if_body) {
                        croak("classes are not allowed as the body of an if");
                    }
                    return class_(AST_DefClass);
  
                  case "function":
                    next();
                    if (is_for_body) {
                        croak("functions are not allowed as the body of a loop");
                    }
                    return function_(AST_Defun, false, false, is_export_default);
  
                  case "if":
                    next();
                    return if_();
  
                  case "return":
                    if (S.in_function == 0 && !options.bare_returns)
                        croak("'return' outside of function");
                    next();
                    var value = null;
                    if (is("punc", ";")) {
                        next();
                    } else if (!can_insert_semicolon()) {
                        value = expression(true);
                        semicolon();
                    }
                    return new AST_Return({
                        value: value
                    });
  
                  case "switch":
                    next();
                    return new AST_Switch({
                        expression : parenthesised(),
                        body       : in_loop(switch_body_)
                    });
  
                  case "throw":
                    next();
                    if (has_newline_before(S.token))
                        croak("Illegal newline after 'throw'");
                    var value = expression(true);
                    semicolon();
                    return new AST_Throw({
                        value: value
                    });
  
                  case "try":
                    next();
                    return try_();
  
                  case "var":
                    next();
                    var node = var_();
                    semicolon();
                    return node;
  
                  case "let":
                    next();
                    var node = let_();
                    semicolon();
                    return node;
  
                  case "const":
                    next();
                    var node = const_();
                    semicolon();
                    return node;
  
                  case "with":
                    if (S.input.has_directive("use strict")) {
                        croak("Strict mode may not include a with statement");
                    }
                    next();
                    return new AST_With({
                        expression : parenthesised(),
                        body       : statement()
                    });
  
                  case "export":
                    if (!is_token(peek(), "punc", "(")) {
                        next();
                        var node = export_();
                        if (is("punc", ";")) semicolon();
                        return node;
                    }
                }
            }
            unexpected();
        });
  
        function labeled_statement() {
            var label = as_symbol(AST_Label);
            if (label.name === "await" && is_in_async()) {
                token_error(S.prev, "await cannot be used as label inside async function");
            }
            if (find_if(function(l) { return l.name == label.name; }, S.labels)) {
                // ECMA-262, 12.12: An ECMAScript program is considered
                // syntactically incorrect if it contains a
                // LabelledStatement that is enclosed by a
                // LabelledStatement with the same Identifier as label.
                croak("Label " + label.name + " defined twice");
            }
            expect(":");
            S.labels.push(label);
            var stat = statement();
            S.labels.pop();
            if (!(stat instanceof AST_IterationStatement)) {
                // check for `continue` that refers to this label.
                // those should be reported as syntax errors.
                // https://github.com/mishoo/UglifyJS2/issues/287
                label.references.forEach(function(ref) {
                    if (ref instanceof AST_Continue) {
                        ref = ref.label.start;
                        croak("Continue label `" + label.name + "` refers to non-IterationStatement.",
                              ref.line, ref.col, ref.pos);
                    }
                });
            }
            return new AST_LabeledStatement({ body: stat, label: label });
        }
  
        function simple_statement(tmp) {
            return new AST_SimpleStatement({ body: (tmp = expression(true), semicolon(), tmp) });
        }
  
        function break_cont(type) {
            var label = null, ldef;
            if (!can_insert_semicolon()) {
                label = as_symbol(AST_LabelRef, true);
            }
            if (label != null) {
                ldef = find_if(function(l) { return l.name == label.name; }, S.labels);
                if (!ldef)
                    croak("Undefined label " + label.name);
                label.thedef = ldef;
            } else if (S.in_loop == 0)
                croak(type.TYPE + " not inside a loop or switch");
            semicolon();
            var stat = new type({ label: label });
            if (ldef) ldef.references.push(stat);
            return stat;
        }
  
        function for_() {
            var for_await_error = "`for await` invalid in this context";
            var await_tok = S.token;
            if (await_tok.type == "name" && await_tok.value == "await") {
                if (!is_in_async()) {
                    token_error(await_tok, for_await_error);
                }
                next();
            } else {
                await_tok = false;
            }
            expect("(");
            var init = null;
            if (!is("punc", ";")) {
                init =
                    is("keyword", "var") ? (next(), var_(true)) :
                    is("keyword", "let") ? (next(), let_(true)) :
                    is("keyword", "const") ? (next(), const_(true)) :
                                           expression(true, true);
                var is_in = is("operator", "in");
                var is_of = is("name", "of");
                if (await_tok && !is_of) {
                    token_error(await_tok, for_await_error);
                }
                if (is_in || is_of) {
                    if (init instanceof AST_Definitions) {
                        if (init.definitions.length > 1)
                            token_error(init.start, "Only one variable declaration allowed in for..in loop");
                    } else if (!(is_assignable(init) || (init = to_destructuring(init)) instanceof AST_Destructuring)) {
                        token_error(init.start, "Invalid left-hand side in for..in loop");
                    }
                    next();
                    if (is_in) {
                        return for_in(init);
                    } else {
                        return for_of(init, !!await_tok);
                    }
                }
            } else if (await_tok) {
                token_error(await_tok, for_await_error);
            }
            return regular_for(init);
        }
  
        function regular_for(init) {
            expect(";");
            var test = is("punc", ";") ? null : expression(true);
            expect(";");
            var step = is("punc", ")") ? null : expression(true);
            expect(")");
            return new AST_For({
                init      : init,
                condition : test,
                step      : step,
                body      : in_loop(function() { return statement(false, true); })
            });
        }
  
        function for_of(init, is_await) {
            var lhs = init instanceof AST_Definitions ? init.definitions[0].name : null;
            var obj = expression(true);
            expect(")");
            return new AST_ForOf({
                await  : is_await,
                init   : init,
                name   : lhs,
                object : obj,
                body   : in_loop(function() { return statement(false, true); })
            });
        }
  
        function for_in(init) {
            var obj = expression(true);
            expect(")");
            return new AST_ForIn({
                init   : init,
                object : obj,
                body   : in_loop(function() { return statement(false, true); })
            });
        }
  
        var arrow_function = function(start, argnames, is_async) {
            if (has_newline_before(S.token)) {
                croak("Unexpected newline before arrow (=>)");
            }
  
            expect_token("arrow", "=>");
  
            var body = _function_body(is("punc", "{"), false, is_async);
  
            var end =
                body instanceof Array && body.length ? body[body.length - 1].end :
                body instanceof Array ? start :
                    body.end;
  
            return new AST_Arrow({
                start    : start,
                end      : end,
                async    : is_async,
                argnames : argnames,
                body     : body
            });
        };
  
        var function_ = function(ctor, is_generator_property, is_async, is_export_default) {
  
            var in_statement = ctor === AST_Defun;
            var is_generator = is("operator", "*");
            if (is_generator) {
                next();
            }
  
            var name = is("name") ? as_symbol(in_statement ? AST_SymbolDefun : AST_SymbolLambda) : null;
            if (in_statement && !name) {
                if (is_export_default) {
                    ctor = AST_Function;
                } else {
                    unexpected();
                }
            }
  
            if (name && ctor !== AST_Accessor && !(name instanceof AST_SymbolDeclaration))
                unexpected(prev());
  
            var args = [];
            var body = _function_body(true, is_generator || is_generator_property, is_async, name, args);
            return new ctor({
                start : args.start,
                end   : body.end,
                is_generator: is_generator,
                async : is_async,
                name  : name,
                argnames: args,
                body  : body
            });
        };
  
        function track_used_binding_identifiers(is_parameter, strict) {
            var parameters = {};
            var duplicate = false;
            var default_assignment = false;
            var spread = false;
            var strict_mode = !!strict;
            var tracker = {
                add_parameter: function(token) {
                    if (parameters["$" + token.value] !== undefined) {
                        if (duplicate === false) {
                            duplicate = token;
                        }
                        tracker.check_strict();
                    } else {
                        parameters["$" + token.value] = true;
                        if (is_parameter) {
                            switch (token.value) {
                              case "arguments":
                              case "eval":
                              case "yield":
                                if (strict_mode) {
                                    token_error(token, "Unexpected " + token.value + " identifier as parameter inside strict mode");
                                }
                                break;
                              default:
                                if (RESERVED_WORDS(token.value)) {
                                    unexpected();
                                }
                            }
                        }
                    }
                },
                mark_default_assignment: function(token) {
                    if (default_assignment === false) {
                        default_assignment = token;
                    }
                },
                mark_spread: function(token) {
                    if (spread === false) {
                        spread = token;
                    }
                },
                mark_strict_mode: function() {
                    strict_mode = true;
                },
                is_strict: function() {
                    return default_assignment !== false || spread !== false || strict_mode;
                },
                check_strict: function() {
                    if (tracker.is_strict() && duplicate !== false) {
                        token_error(duplicate, "Parameter " + duplicate.value + " was used already");
                    }
                }
            };
  
            return tracker;
        }
  
        function parameters(params) {
            var used_parameters = track_used_binding_identifiers(true, S.input.has_directive("use strict"));
  
            expect("(");
  
            while (!is("punc", ")")) {
                var param = parameter(used_parameters);
                params.push(param);
  
                if (!is("punc", ")")) {
                    expect(",");
                    if (is("punc", ")") && options.ecma < 8) unexpected();
                }
  
                if (param instanceof AST_Expansion) {
                    break;
                }
            }
  
            next();
        }
  
        function parameter(used_parameters, symbol_type) {
            var param;
            var expand = false;
            if (used_parameters === undefined) {
                used_parameters = track_used_binding_identifiers(true, S.input.has_directive("use strict"));
            }
            if (is("expand", "...")) {
                expand = S.token;
                used_parameters.mark_spread(S.token);
                next();
            }
            param = binding_element(used_parameters, symbol_type);
  
            if (is("operator", "=") && expand === false) {
                used_parameters.mark_default_assignment(S.token);
                next();
                param = new AST_DefaultAssign({
                    start: param.start,
                    left: param,
                    operator: "=",
                    right: expression(false),
                    end: S.token
                });
            }
  
            if (expand !== false) {
                if (!is("punc", ")")) {
                    unexpected();
                }
                param = new AST_Expansion({
                    start: expand,
                    expression: param,
                    end: expand
                });
            }
            used_parameters.check_strict();
  
            return param;
        }
  
        function binding_element(used_parameters, symbol_type) {
            var elements = [];
            var first = true;
            var is_expand = false;
            var expand_token;
            var first_token = S.token;
            if (used_parameters === undefined) {
                used_parameters = track_used_binding_identifiers(false, S.input.has_directive("use strict"));
            }
            symbol_type = symbol_type === undefined ? AST_SymbolFunarg : symbol_type;
            if (is("punc", "[")) {
                next();
                while (!is("punc", "]")) {
                    if (first) {
                        first = false;
                    } else {
                        expect(",");
                    }
  
                    if (is("expand", "...")) {
                        is_expand = true;
                        expand_token = S.token;
                        used_parameters.mark_spread(S.token);
                        next();
                    }
                    if (is("punc")) {
                        switch (S.token.value) {
                          case ",":
                            elements.push(new AST_Hole({
                                start: S.token,
                                end: S.token
                            }));
                            continue;
                          case "]": // Trailing comma after last element
                            break;
                          case "[":
                          case "{":
                            elements.push(binding_element(used_parameters, symbol_type));
                            break;
                          default:
                            unexpected();
                        }
                    } else if (is("name")) {
                        used_parameters.add_parameter(S.token);
                        elements.push(as_symbol(symbol_type));
                    } else {
                        croak("Invalid function parameter");
                    }
                    if (is("operator", "=") && is_expand === false) {
                        used_parameters.mark_default_assignment(S.token);
                        next();
                        elements[elements.length - 1] = new AST_DefaultAssign({
                            start: elements[elements.length - 1].start,
                            left: elements[elements.length - 1],
                            operator: "=",
                            right: expression(false),
                            end: S.token
                        });
                    }
                    if (is_expand) {
                        if (!is("punc", "]")) {
                            croak("Rest element must be last element");
                        }
                        elements[elements.length - 1] = new AST_Expansion({
                            start: expand_token,
                            expression: elements[elements.length - 1],
                            end: expand_token
                        });
                    }
                }
                expect("]");
                used_parameters.check_strict();
                return new AST_Destructuring({
                    start: first_token,
                    names: elements,
                    is_array: true,
                    end: prev()
                });
            } else if (is("punc", "{")) {
                next();
                while (!is("punc", "}")) {
                    if (first) {
                        first = false;
                    } else {
                        expect(",");
                    }
                    if (is("expand", "...")) {
                        is_expand = true;
                        expand_token = S.token;
                        used_parameters.mark_spread(S.token);
                        next();
                    }
                    if (is("name") && (is_token(peek(), "punc") || is_token(peek(), "operator")) && [",", "}", "="].indexOf(peek().value) !== -1) {
                        used_parameters.add_parameter(S.token);
                        var start = prev();
                        var value = as_symbol(symbol_type);
                        if (is_expand) {
                            elements.push(new AST_Expansion({
                                start: expand_token,
                                expression: value,
                                end: value.end,
                            }));
                        } else {
                            elements.push(new AST_ObjectKeyVal({
                                start: start,
                                key: value.name,
                                value: value,
                                end: value.end,
                            }));
                        }
                    } else if (is("punc", "}")) {
                        continue; // Allow trailing hole
                    } else {
                        var property_token = S.token;
                        var property = as_property_name();
                        if (property === null) {
                            unexpected(prev());
                        } else if (prev().type === "name" && !is("punc", ":")) {
                            elements.push(new AST_ObjectKeyVal({
                                start: prev(),
                                key: property,
                                value: new symbol_type({
                                    start: prev(),
                                    name: property,
                                    end: prev()
                                }),
                                end: prev()
                            }));
                        } else {
                            expect(":");
                            elements.push(new AST_ObjectKeyVal({
                                start: property_token,
                                quote: property_token.quote,
                                key: property,
                                value: binding_element(used_parameters, symbol_type),
                                end: prev()
                            }));
                        }
                    }
                    if (is_expand) {
                        if (!is("punc", "}")) {
                            croak("Rest element must be last element");
                        }
                    } else if (is("operator", "=")) {
                        used_parameters.mark_default_assignment(S.token);
                        next();
                        elements[elements.length - 1].value = new AST_DefaultAssign({
                            start: elements[elements.length - 1].value.start,
                            left: elements[elements.length - 1].value,
                            operator: "=",
                            right: expression(false),
                            end: S.token
                        });
                    }
                }
                expect("}");
                used_parameters.check_strict();
                return new AST_Destructuring({
                    start: first_token,
                    names: elements,
                    is_array: false,
                    end: prev()
                });
            } else if (is("name")) {
                used_parameters.add_parameter(S.token);
                return as_symbol(symbol_type);
            } else {
                croak("Invalid function parameter");
            }
        }
  
        function params_or_seq_(allow_arrows, maybe_sequence) {
            var spread_token;
            var invalid_sequence;
            var trailing_comma;
            var a = [];
            expect("(");
            while (!is("punc", ")")) {
                if (spread_token) unexpected(spread_token);
                if (is("expand", "...")) {
                    spread_token = S.token;
                    if (maybe_sequence) invalid_sequence = S.token;
                    next();
                    a.push(new AST_Expansion({
                        start: prev(),
                        expression: expression(),
                        end: S.token,
                    }));
                } else {
                    a.push(expression());
                }
                if (!is("punc", ")")) {
                    expect(",");
                    if (is("punc", ")")) {
                        if (options.ecma < 8) unexpected();
                        trailing_comma = prev();
                        if (maybe_sequence) invalid_sequence = trailing_comma;
                    }
                }
            }
            expect(")");
            if (allow_arrows && is("arrow", "=>")) {
                if (spread_token && trailing_comma) unexpected(trailing_comma);
            } else if (invalid_sequence) {
                unexpected(invalid_sequence);
            }
            return a;
        }
  
        function _function_body(block, generator, is_async, name, args) {
            var loop = S.in_loop;
            var labels = S.labels;
            var current_generator = S.in_generator;
            var current_async = S.in_async;
            ++S.in_function;
            if (generator)
                S.in_generator = S.in_function;
            if (is_async)
                S.in_async = S.in_function;
            if (args) parameters(args);
            if (block)
                S.in_directives = true;
            S.in_loop = 0;
            S.labels = [];
            if (block) {
                S.input.push_directives_stack();
                var a = block_();
                if (name) _verify_symbol(name);
                if (args) args.forEach(_verify_symbol);
                S.input.pop_directives_stack();
            } else {
                var a = expression(false);
            }
            --S.in_function;
            S.in_loop = loop;
            S.labels = labels;
            S.in_generator = current_generator;
            S.in_async = current_async;
            return a;
        }
  
        function _await_expression() {
            // Previous token must be "await" and not be interpreted as an identifier
            if (!is_in_async()) {
                croak("Unexpected await expression outside async function",
                    S.prev.line, S.prev.col, S.prev.pos);
            }
            // the await expression is parsed as a unary expression in Babel
            return new AST_Await({
                start: prev(),
                end: S.token,
                expression : maybe_unary(true),
            });
        }
  
        function _yield_expression() {
            // Previous token must be keyword yield and not be interpret as an identifier
            if (!is_in_generator()) {
                croak("Unexpected yield expression outside generator function",
                    S.prev.line, S.prev.col, S.prev.pos);
            }
            var start = S.token;
            var star = false;
            var has_expression = true;
  
            // Attempt to get expression or star (and then the mandatory expression)
            // behind yield on the same line.
            //
            // If nothing follows on the same line of the yieldExpression,
            // it should default to the value `undefined` for yield to return.
            // In that case, the `undefined` stored as `null` in ast.
            //
            // Note 1: It isn't allowed for yield* to close without an expression
            // Note 2: If there is a nlb between yield and star, it is interpret as
            //         yield <explicit undefined> <inserted automatic semicolon> *
            if (can_insert_semicolon() ||
                (is("punc") && PUNC_AFTER_EXPRESSION(S.token.value))) {
                has_expression = false;
  
            } else if (is("operator", "*")) {
                star = true;
                next();
            }
  
            return new AST_Yield({
                start      : start,
                is_star    : star,
                expression : has_expression ? expression() : null,
                end        : prev()
            });
        }
  
        function if_() {
            var cond = parenthesised(), body = statement(false, false, true), belse = null;
            if (is("keyword", "else")) {
                next();
                belse = statement(false, false, true);
            }
            return new AST_If({
                condition   : cond,
                body        : body,
                alternative : belse
            });
        }
  
        function block_() {
            expect("{");
            var a = [];
            while (!is("punc", "}")) {
                if (is("eof")) unexpected();
                a.push(statement());
            }
            next();
            return a;
        }
  
        function switch_body_() {
            expect("{");
            var a = [], cur = null, branch = null, tmp;
            while (!is("punc", "}")) {
                if (is("eof")) unexpected();
                if (is("keyword", "case")) {
                    if (branch) branch.end = prev();
                    cur = [];
                    branch = new AST_Case({
                        start      : (tmp = S.token, next(), tmp),
                        expression : expression(true),
                        body       : cur
                    });
                    a.push(branch);
                    expect(":");
                } else if (is("keyword", "default")) {
                    if (branch) branch.end = prev();
                    cur = [];
                    branch = new AST_Default({
                        start : (tmp = S.token, next(), expect(":"), tmp),
                        body  : cur
                    });
                    a.push(branch);
                } else {
                    if (!cur) unexpected();
                    cur.push(statement());
                }
            }
            if (branch) branch.end = prev();
            next();
            return a;
        }
  
        function try_() {
            var body = block_(), bcatch = null, bfinally = null;
            if (is("keyword", "catch")) {
                var start = S.token;
                next();
                if (is("punc", "{")) {
                    var name = null;
                } else {
                    expect("(");
                    var name = parameter(undefined, AST_SymbolCatch);
                    expect(")");
                }
                bcatch = new AST_Catch({
                    start   : start,
                    argname : name,
                    body    : block_(),
                    end     : prev()
                });
            }
            if (is("keyword", "finally")) {
                var start = S.token;
                next();
                bfinally = new AST_Finally({
                    start : start,
                    body  : block_(),
                    end   : prev()
                });
            }
            if (!bcatch && !bfinally)
                croak("Missing catch/finally blocks");
            return new AST_Try({
                body     : body,
                bcatch   : bcatch,
                bfinally : bfinally
            });
        }
  
        function vardefs(no_in, kind) {
            var a = [];
            var def;
            for (;;) {
                var sym_type =
                    kind === "var" ? AST_SymbolVar :
                    kind === "const" ? AST_SymbolConst :
                    kind === "let" ? AST_SymbolLet : null;
                if (is("punc", "{") || is("punc", "[")) {
                    def = new AST_VarDef({
                        start: S.token,
                        name: binding_element(undefined ,sym_type),
                        value: is("operator", "=") ? (expect_token("operator", "="), expression(false, no_in)) : null,
                        end: prev()
                    });
                } else {
                    def = new AST_VarDef({
                        start : S.token,
                        name  : as_symbol(sym_type),
                        value : is("operator", "=")
                            ? (next(), expression(false, no_in))
                            : !no_in && kind === "const"
                                ? croak("Missing initializer in const declaration") : null,
                        end   : prev()
                    });
                    if (def.name.name == "import") croak("Unexpected token: import");
                }
                a.push(def);
                if (!is("punc", ","))
                    break;
                next();
            }
            return a;
        }
  
        var var_ = function(no_in) {
            return new AST_Var({
                start       : prev(),
                definitions : vardefs(no_in, "var"),
                end         : prev()
            });
        };
  
        var let_ = function(no_in) {
            return new AST_Let({
                start       : prev(),
                definitions : vardefs(no_in, "let"),
                end         : prev()
            });
        };
  
        var const_ = function(no_in) {
            return new AST_Const({
                start       : prev(),
                definitions : vardefs(no_in, "const"),
                end         : prev()
            });
        };
  
        var new_ = function(allow_calls) {
            var start = S.token;
            expect_token("operator", "new");
            if (is("punc", ".")) {
                next();
                expect_token("name", "target");
                return subscripts(new AST_NewTarget({
                    start : start,
                    end   : prev()
                }), allow_calls);
            }
            var newexp = expr_atom(false), args;
            if (is("punc", "(")) {
                next();
                args = expr_list(")", options.ecma >= 8);
            } else {
                args = [];
            }
            var call = new AST_New({
                start      : start,
                expression : newexp,
                args       : args,
                end        : prev()
            });
            mark_pure(call);
            return subscripts(call, allow_calls);
        };
  
        function as_atom_node() {
            var tok = S.token, ret;
            switch (tok.type) {
              case "name":
                ret = _make_symbol(AST_SymbolRef);
                break;
              case "num":
                ret = new AST_Number({ start: tok, end: tok, value: tok.value });
                break;
              case "string":
                ret = new AST_String({
                    start : tok,
                    end   : tok,
                    value : tok.value,
                    quote : tok.quote
                });
                break;
              case "regexp":
                ret = new AST_RegExp({ start: tok, end: tok, value: tok.value });
                break;
              case "atom":
                switch (tok.value) {
                  case "false":
                    ret = new AST_False({ start: tok, end: tok });
                    break;
                  case "true":
                    ret = new AST_True({ start: tok, end: tok });
                    break;
                  case "null":
                    ret = new AST_Null({ start: tok, end: tok });
                    break;
                }
                break;
            }
            next();
            return ret;
        }
  
        function to_fun_args(ex, _, __, default_seen_above) {
            var insert_default = function(ex, default_value) {
                if (default_value) {
                    return new AST_DefaultAssign({
                        start: ex.start,
                        left: ex,
                        operator: "=",
                        right: default_value,
                        end: default_value.end
                    });
                }
                return ex;
            };
            if (ex instanceof AST_Object) {
                return insert_default(new AST_Destructuring({
                    start: ex.start,
                    end: ex.end,
                    is_array: false,
                    names: ex.properties.map(to_fun_args)
                }), default_seen_above);
            } else if (ex instanceof AST_ObjectKeyVal) {
                ex.value = to_fun_args(ex.value, 0, [ex.key]);
                return insert_default(ex, default_seen_above);
            } else if (ex instanceof AST_Hole) {
                return ex;
            } else if (ex instanceof AST_Destructuring) {
                ex.names = ex.names.map(to_fun_args);
                return insert_default(ex, default_seen_above);
            } else if (ex instanceof AST_SymbolRef) {
                return insert_default(new AST_SymbolFunarg({
                    name: ex.name,
                    start: ex.start,
                    end: ex.end
                }), default_seen_above);
            } else if (ex instanceof AST_Expansion) {
                ex.expression = to_fun_args(ex.expression);
                return insert_default(ex, default_seen_above);
            } else if (ex instanceof AST_Array) {
                return insert_default(new AST_Destructuring({
                    start: ex.start,
                    end: ex.end,
                    is_array: true,
                    names: ex.elements.map(to_fun_args)
                }), default_seen_above);
            } else if (ex instanceof AST_Assign) {
                return insert_default(to_fun_args(ex.left, undefined, undefined, ex.right), default_seen_above);
            } else if (ex instanceof AST_DefaultAssign) {
                ex.left = to_fun_args(ex.left, 0, [ex.left]);
                return ex;
            } else {
                croak("Invalid function parameter", ex.start.line, ex.start.col);
            }
        }
  
        var expr_atom = function(allow_calls, allow_arrows) {
            if (is("operator", "new")) {
                return new_(allow_calls);
            }
            var start = S.token;
            var peeked;
            var async = is("name", "async")
                && (peeked = peek()).value != "["
                && peeked.type != "arrow"
                && as_atom_node();
            if (is("punc")) {
                switch (S.token.value) {
                  case "(":
                    if (async && !allow_calls) break;
                    var exprs = params_or_seq_(allow_arrows, !async);
                    if (allow_arrows && is("arrow", "=>")) {
                        return arrow_function(start, exprs.map(to_fun_args), !!async);
                    }
                    var ex = async ? new AST_Call({
                        expression: async,
                        args: exprs
                    }) : exprs.length == 1 ? exprs[0] : new AST_Sequence({
                        expressions: exprs
                    });
                    if (ex.start) {
                        var len = start.comments_before.length;
                        [].unshift.apply(ex.start.comments_before, start.comments_before);
                        start.comments_before = ex.start.comments_before;
                        start.comments_before_length = len;
                        if (len == 0 && start.comments_before.length > 0) {
                            var comment = start.comments_before[0];
                            if (!comment.nlb) {
                                comment.nlb = start.nlb;
                                start.nlb = false;
                            }
                        }
                        start.comments_after = ex.start.comments_after;
                    }
                    ex.start = start;
                    var end = prev();
                    if (ex.end) {
                        end.comments_before = ex.end.comments_before;
                        [].push.apply(ex.end.comments_after, end.comments_after);
                        end.comments_after = ex.end.comments_after;
                    }
                    ex.end = end;
                    if (ex instanceof AST_Call) mark_pure(ex);
                    return subscripts(ex, allow_calls);
                  case "[":
                    return subscripts(array_(), allow_calls);
                  case "{":
                    return subscripts(object_or_destructuring_(), allow_calls);
                }
                if (!async) unexpected();
            }
            if (allow_arrows && is("name") && is_token(peek(), "arrow")) {
                var param = new AST_SymbolFunarg({
                    name: S.token.value,
                    start: start,
                    end: start,
                });
                next();
                return arrow_function(start, [param], !!async);
            }
            if (is("keyword", "function")) {
                next();
                var func = function_(AST_Function, false, !!async);
                func.start = start;
                func.end = prev();
                return subscripts(func, allow_calls);
            }
            if (async) return subscripts(async, allow_calls);
            if (is("keyword", "class")) {
                next();
                var cls = class_(AST_ClassExpression);
                cls.start = start;
                cls.end = prev();
                return subscripts(cls, allow_calls);
            }
            if (is("template_head")) {
                return subscripts(template_string(false), allow_calls);
            }
            if (ATOMIC_START_TOKEN(S.token.type)) {
                return subscripts(as_atom_node(), allow_calls);
            }
            unexpected();
        };
  
        function template_string(tagged) {
            var segments = [], start = S.token;
  
            segments.push(new AST_TemplateSegment({
                start: S.token,
                raw: S.token.raw,
                value: S.token.value,
                end: S.token
            }));
            while (S.token.end === false) {
                next();
                handle_regexp();
                segments.push(expression(true));
  
                if (!is_token("template_substitution")) {
                    unexpected();
                }
  
                segments.push(new AST_TemplateSegment({
                    start: S.token,
                    raw: S.token.raw,
                    value: S.token.value,
                    end: S.token
                }));
            }
            next();
  
            return new AST_TemplateString({
                start: start,
                segments: segments,
                end: S.token
            });
        }
  
        function expr_list(closing, allow_trailing_comma, allow_empty) {
            var first = true, a = [];
            while (!is("punc", closing)) {
                if (first) first = false; else expect(",");
                if (allow_trailing_comma && is("punc", closing)) break;
                if (is("punc", ",") && allow_empty) {
                    a.push(new AST_Hole({ start: S.token, end: S.token }));
                } else if (is("expand", "...")) {
                    next();
                    a.push(new AST_Expansion({start: prev(), expression: expression(),end: S.token}));
                } else {
                    a.push(expression(false));
                }
            }
            next();
            return a;
        }
  
        var array_ = embed_tokens(function() {
            expect("[");
            return new AST_Array({
                elements: expr_list("]", !options.strict, true)
            });
        });
  
        var create_accessor = embed_tokens(function(is_generator, is_async) {
            return function_(AST_Accessor, is_generator, is_async);
        });
  
        var object_or_destructuring_ = embed_tokens(function object_or_destructuring_() {
            var start = S.token, first = true, a = [];
            expect("{");
            while (!is("punc", "}")) {
                if (first) first = false; else expect(",");
                if (!options.strict && is("punc", "}"))
                    // allow trailing comma
                    break;
  
                start = S.token;
                if (start.type == "expand") {
                    next();
                    a.push(new AST_Expansion({
                        start: start,
                        expression: expression(false),
                        end: prev(),
                    }));
                    continue;
                }
  
                var name = as_property_name();
                var value;
  
                // Check property and fetch value
                if (!is("punc", ":")) {
                    var concise = concise_method_or_getset(name, start);
                    if (concise) {
                        a.push(concise);
                        continue;
                    }
  
                    value = new AST_SymbolRef({
                        start: prev(),
                        name: name,
                        end: prev()
                    });
                } else if (name === null) {
                    unexpected(prev());
                } else {
                    next(); // `:` - see first condition
                    value = expression(false);
                }
  
                // Check for default value and alter value accordingly if necessary
                if (is("operator", "=")) {
                    next();
                    value = new AST_Assign({
                        start: start,
                        left: value,
                        operator: "=",
                        right: expression(false),
                        end: prev()
                    });
                }
  
                // Create property
                a.push(new AST_ObjectKeyVal({
                    start: start,
                    quote: start.quote,
                    key: name instanceof AST_Node ? name : "" + name,
                    value: value,
                    end: prev()
                }));
            }
            next();
            return new AST_Object({ properties: a });
        });
  
        function class_(KindOfClass) {
            var start, method, class_name, extends_, a = [];
  
            S.input.push_directives_stack(); // Push directive stack, but not scope stack
            S.input.add_directive("use strict");
  
            if (S.token.type == "name" && S.token.value != "extends") {
                class_name = as_symbol(KindOfClass === AST_DefClass ? AST_SymbolDefClass : AST_SymbolClass);
            }
  
            if (KindOfClass === AST_DefClass && !class_name) {
                unexpected();
            }
  
            if (S.token.value == "extends") {
                next();
                extends_ = expression(true);
            }
  
            expect("{");
  
            if (is("punc", ";")) { next(); }  // Leading semicolons are okay in class bodies.
            while (!is("punc", "}")) {
                start = S.token;
                method = concise_method_or_getset(as_property_name(), start, true);
                if (!method) { unexpected(); }
                a.push(method);
                if (is("punc", ";")) { next(); }
            }
  
            S.input.pop_directives_stack();
  
            next();
  
            return new KindOfClass({
                start: start,
                name: class_name,
                extends: extends_,
                properties: a,
                end: prev(),
            });
        }
  
        function concise_method_or_getset(name, start, is_class) {
            var get_ast = function(name, token) {
                if (typeof name === "string" || typeof name === "number") {
                    return new AST_SymbolMethod({
                        start: token,
                        name: "" + name,
                        end: prev()
                    });
                } else if (name === null) {
                    unexpected();
                }
                return name;
            };
            var is_async = false;
            var is_static = false;
            var is_generator = false;
            var property_token = start;
            if (is_class && name === "static" && !is("punc", "(")) {
                is_static = true;
                property_token = S.token;
                name = as_property_name();
            }
            if (name === "async" && !is("punc", "(") && !is("punc", ",") && !is("punc", "}")) {
                is_async = true;
                property_token = S.token;
                name = as_property_name();
            }
            if (name === null) {
                is_generator = true;
                property_token = S.token;
                name = as_property_name();
                if (name === null) {
                    unexpected();
                }
            }
            if (is("punc", "(")) {
                name = get_ast(name, start);
                var node = new AST_ConciseMethod({
                    start       : start,
                    static      : is_static,
                    is_generator: is_generator,
                    async       : is_async,
                    key         : name,
                    quote       : name instanceof AST_SymbolMethod ?
                                  property_token.quote : undefined,
                    value       : create_accessor(is_generator, is_async),
                    end         : prev()
                });
                return node;
            }
            property_token = S.token;
            if (name == "get") {
                if (!is("punc") || is("punc", "[")) {
                    name = get_ast(as_property_name(), start);
                    return new AST_ObjectGetter({
                        start : start,
                        static: is_static,
                        key   : name,
                        quote : name instanceof AST_SymbolMethod ?
                                property_token.quote : undefined,
                        value : create_accessor(),
                        end   : prev()
                    });
                }
            } else if (name == "set") {
                if (!is("punc") || is("punc", "[")) {
                    name = get_ast(as_property_name(), start);
                    return new AST_ObjectSetter({
                        start : start,
                        static: is_static,
                        key   : name,
                        quote : name instanceof AST_SymbolMethod ?
                                property_token.quote : undefined,
                        value : create_accessor(),
                        end   : prev()
                    });
                }
            }
        }
  
        function import_() {
            var start = prev();
            var imported_name;
            var imported_names;
            if (is("name")) {
                imported_name = as_symbol(AST_SymbolImport);
            }
  
            if (is("punc", ",")) {
                next();
            }
  
            imported_names = map_names(true);
  
            if (imported_names || imported_name) {
                expect_token("name", "from");
            }
            var mod_str = S.token;
            if (mod_str.type !== "string") {
                unexpected();
            }
            next();
            return new AST_Import({
                start: start,
                imported_name: imported_name,
                imported_names: imported_names,
                module_name: new AST_String({
                    start: mod_str,
                    value: mod_str.value,
                    quote: mod_str.quote,
                    end: mod_str,
                }),
                end: S.token,
            });
        }
  
        function map_name(is_import) {
            function make_symbol(type) {
                return new type({
                    name: as_property_name(),
                    start: prev(),
                    end: prev()
                });
            }
  
            var foreign_type = is_import ? AST_SymbolImportForeign : AST_SymbolExportForeign;
            var type = is_import ? AST_SymbolImport : AST_SymbolExport;
            var start = S.token;
            var foreign_name;
            var name;
  
            if (is_import) {
                foreign_name = make_symbol(foreign_type);
            } else {
                name = make_symbol(type);
            }
            if (is("name", "as")) {
                next();  // The "as" word
                if (is_import) {
                    name = make_symbol(type);
                } else {
                    foreign_name = make_symbol(foreign_type);
                }
            } else if (is_import) {
                name = new type(foreign_name);
            } else {
                foreign_name = new foreign_type(name);
            }
  
            return new AST_NameMapping({
                start: start,
                foreign_name: foreign_name,
                name: name,
                end: prev(),
            });
        }
  
        function map_nameAsterisk(is_import, name) {
            var foreign_type = is_import ? AST_SymbolImportForeign : AST_SymbolExportForeign;
            var type = is_import ? AST_SymbolImport : AST_SymbolExport;
            var start = S.token;
            var foreign_name;
            var end = prev();
  
            name = name || new type({
                name: "*",
                start: start,
                end: end,
            });
  
            foreign_name = new foreign_type({
                name: "*",
                start: start,
                end: end,
            });
  
            return new AST_NameMapping({
                start: start,
                foreign_name: foreign_name,
                name: name,
                end: end,
            });
        }
  
        function map_names(is_import) {
            var names;
            if (is("punc", "{")) {
                next();
                names = [];
                while (!is("punc", "}")) {
                    names.push(map_name(is_import));
                    if (is("punc", ",")) {
                        next();
                    }
                }
                next();
            } else if (is("operator", "*")) {
                var name;
                next();
                if (is_import && is("name", "as")) {
                    next();  // The "as" word
                    name = as_symbol(is_import ? AST_SymbolImport : AST_SymbolExportForeign);
                }
                names = [map_nameAsterisk(is_import, name)];
            }
            return names;
        }
  
        function export_() {
            var start = S.token;
            var is_default;
            var exported_names;
  
            if (is("keyword", "default")) {
                is_default = true;
                next();
            } else if (exported_names = map_names(false)) {
                if (is("name", "from")) {
                    next();
  
                    var mod_str = S.token;
                    if (mod_str.type !== "string") {
                        unexpected();
                    }
                    next();
  
                    return new AST_Export({
                        start: start,
                        is_default: is_default,
                        exported_names: exported_names,
                        module_name: new AST_String({
                            start: mod_str,
                            value: mod_str.value,
                            quote: mod_str.quote,
                            end: mod_str,
                        }),
                        end: prev(),
                    });
                } else {
                    return new AST_Export({
                        start: start,
                        is_default: is_default,
                        exported_names: exported_names,
                        end: prev(),
                    });
                }
            }
  
            var node;
            var exported_value;
            var exported_definition;
            if (is("punc", "{")
                || is_default
                    && (is("keyword", "class") || is("keyword", "function"))
                    && is_token(peek(), "punc")) {
                exported_value = expression(false);
                semicolon();
            } else if ((node = statement(is_default)) instanceof AST_Definitions && is_default) {
                unexpected(node.start);
            } else if (node instanceof AST_Definitions || node instanceof AST_Lambda || node instanceof AST_DefClass) {
                exported_definition = node;
            } else if (node instanceof AST_SimpleStatement) {
                exported_value = node.body;
            } else {
                unexpected(node.start);
            }
  
            return new AST_Export({
                start: start,
                is_default: is_default,
                exported_value: exported_value,
                exported_definition: exported_definition,
                end: prev(),
            });
        }
  
        function as_property_name() {
            var tmp = S.token;
            switch (tmp.type) {
              case "punc":
                if (tmp.value === "[") {
                    next();
                    var ex = expression(false);
                    expect("]");
                    return ex;
                } else unexpected(tmp);
              case "operator":
                if (tmp.value === "*") {
                    next();
                    return null;
                }
                if (["delete", "in", "instanceof", "new", "typeof", "void"].indexOf(tmp.value) === -1) {
                    unexpected(tmp);
                }
              case "name":
                if (tmp.value == "yield") {
                    if (is_in_generator()) {
                        token_error(tmp, "Yield cannot be used as identifier inside generators");
                    } else if (!is_token(peek(), "punc", ":")
                        && !is_token(peek(), "punc", "(")
                        && S.input.has_directive("use strict")) {
                        token_error(tmp, "Unexpected yield identifier inside strict mode");
                    }
                }
              case "string":
              case "num":
              case "keyword":
              case "atom":
                next();
                return tmp.value;
              default:
                unexpected(tmp);
            }
        }
  
        function as_name() {
            var tmp = S.token;
            if (tmp.type != "name") unexpected();
            next();
            return tmp.value;
        }
  
        function _make_symbol(type) {
            var name = S.token.value;
            return new (name == "this" ? AST_This :
                        name == "super" ? AST_Super :
                        type)({
                name  : String(name),
                start : S.token,
                end   : S.token
            });
        }
  
        function _verify_symbol(sym) {
            var name = sym.name;
            if (is_in_generator() && name == "yield") {
                token_error(sym.start, "Yield cannot be used as identifier inside generators");
            }
            if (S.input.has_directive("use strict")) {
                if (name == "yield") {
                    token_error(sym.start, "Unexpected yield identifier inside strict mode");
                }
                if (sym instanceof AST_SymbolDeclaration && (name == "arguments" || name == "eval")) {
                    token_error(sym.start, "Unexpected " + name + " in strict mode");
                }
            }
        }
  
        function as_symbol(type, noerror) {
            if (!is("name")) {
                if (!noerror) croak("Name expected");
                return null;
            }
            var sym = _make_symbol(type);
            _verify_symbol(sym);
            next();
            return sym;
        }
  
        function mark_pure(call) {
            var start = call.start;
            var comments = start.comments_before;
            var i = HOP(start, "comments_before_length") ? start.comments_before_length : comments.length;
            while (--i >= 0) {
                var comment = comments[i];
                if (/[@#]__PURE__/.test(comment.value)) {
                    call.pure = comment;
                    break;
                }
            }
        }
  
        var subscripts = function(expr, allow_calls) {
            var start = expr.start;
            if (is("punc", ".")) {
                next();
                return subscripts(new AST_Dot({
                    start      : start,
                    expression : expr,
                    property   : as_name(),
                    end        : prev()
                }), allow_calls);
            }
            if (is("punc", "[")) {
                next();
                var prop = expression(true);
                expect("]");
                return subscripts(new AST_Sub({
                    start      : start,
                    expression : expr,
                    property   : prop,
                    end        : prev()
                }), allow_calls);
            }
            if (allow_calls && is("punc", "(")) {
                next();
                var call = new AST_Call({
                    start      : start,
                    expression : expr,
                    args       : call_args(),
                    end        : prev()
                });
                mark_pure(call);
                return subscripts(call, true);
            }
            if (is("template_head")) {
                return subscripts(new AST_PrefixedTemplateString({
                    start: start,
                    prefix: expr,
                    template_string: template_string(true),
                    end: prev()
                }), allow_calls);
            }
            return expr;
        };
  
        var call_args = embed_tokens(function _call_args() {
            var args = [];
            while (!is("punc", ")")) {
                if (is("expand", "...")) {
                    next();
                    args.push(new AST_Expansion({
                        start: prev(),
                        expression: expression(false),
                        end: prev()
                    }));
                } else {
                    args.push(expression(false));
                }
                if (!is("punc", ")")) {
                    expect(",");
                    if (is("punc", ")") && options.ecma < 8) unexpected();
                }
            }
            next();
            return args;
        });
  
        var maybe_unary = function(allow_calls, allow_arrows) {
            var start = S.token;
            if (start.type == "name" && start.value == "await") {
                if (is_in_async()) {
                    next();
                    return _await_expression();
                } else if (S.input.has_directive("use strict")) {
                    token_error(S.token, "Unexpected await identifier inside strict mode");
                }
            }
            if (is("operator") && UNARY_PREFIX(start.value)) {
                next();
                handle_regexp();
                var ex = make_unary(AST_UnaryPrefix, start, maybe_unary(allow_calls));
                ex.start = start;
                ex.end = prev();
                return ex;
            }
            var val = expr_atom(allow_calls, allow_arrows);
            while (is("operator") && UNARY_POSTFIX(S.token.value) && !has_newline_before(S.token)) {
                if (val instanceof AST_Arrow) unexpected();
                val = make_unary(AST_UnaryPostfix, S.token, val);
                val.start = start;
                val.end = S.token;
                next();
            }
            return val;
        };
  
        function make_unary(ctor, token, expr) {
            var op = token.value;
            switch (op) {
              case "++":
              case "--":
                if (!is_assignable(expr))
                    croak("Invalid use of " + op + " operator", token.line, token.col, token.pos);
                break;
              case "delete":
                if (expr instanceof AST_SymbolRef && S.input.has_directive("use strict"))
                    croak("Calling delete on expression not allowed in strict mode", expr.start.line, expr.start.col, expr.start.pos);
                break;
            }
            return new ctor({ operator: op, expression: expr });
        }
  
        var expr_op = function(left, min_prec, no_in) {
            var op = is("operator") ? S.token.value : null;
            if (op == "in" && no_in) op = null;
            if (op == "**" && left instanceof AST_UnaryPrefix
                /* unary token in front not allowed - parenthesis required */
                && !is_token(left.start, "punc", "(")
                && left.operator !== "--" && left.operator !== "++")
                    unexpected(left.start);
            var prec = op != null ? PRECEDENCE[op] : null;
            if (prec != null && (prec > min_prec || (op === "**" && min_prec === prec))) {
                next();
                var right = expr_op(maybe_unary(true), prec, no_in);
                return expr_op(new AST_Binary({
                    start    : left.start,
                    left     : left,
                    operator : op,
                    right    : right,
                    end      : right.end
                }), min_prec, no_in);
            }
            return left;
        };
  
        function expr_ops(no_in) {
            return expr_op(maybe_unary(true, true), 0, no_in);
        }
  
        var maybe_conditional = function(no_in) {
            var start = S.token;
            var expr = expr_ops(no_in);
            if (is("operator", "?")) {
                next();
                var yes = expression(false);
                expect(":");
                return new AST_Conditional({
                    start       : start,
                    condition   : expr,
                    consequent  : yes,
                    alternative : expression(false, no_in),
                    end         : prev()
                });
            }
            return expr;
        };
  
        function is_assignable(expr) {
            return expr instanceof AST_PropAccess || expr instanceof AST_SymbolRef;
        }
  
        function to_destructuring(node) {
            if (node instanceof AST_Object) {
                node = new AST_Destructuring({
                    start: node.start,
                    names: node.properties.map(to_destructuring),
                    is_array: false,
                    end: node.end
                });
            } else if (node instanceof AST_Array) {
                var names = [];
  
                for (var i = 0; i < node.elements.length; i++) {
                    // Only allow expansion as last element
                    if (node.elements[i] instanceof AST_Expansion) {
                        if (i + 1 !== node.elements.length) {
                            token_error(node.elements[i].start, "Spread must the be last element in destructuring array");
                        }
                        node.elements[i].expression = to_destructuring(node.elements[i].expression);
                    }
  
                    names.push(to_destructuring(node.elements[i]));
                }
  
                node = new AST_Destructuring({
                    start: node.start,
                    names: names,
                    is_array: true,
                    end: node.end
                });
            } else if (node instanceof AST_ObjectProperty) {
                node.value = to_destructuring(node.value);
            } else if (node instanceof AST_Assign) {
                node = new AST_DefaultAssign({
                    start: node.start,
                    left: node.left,
                    operator: "=",
                    right: node.right,
                    end: node.end
                });
            }
            return node;
        }
  
        // In ES6, AssignmentExpression can also be an ArrowFunction
        var maybe_assign = function(no_in) {
            handle_regexp();
            var start = S.token;
  
            if (start.type == "name" && start.value == "yield") {
                if (is_in_generator()) {
                    next();
                    return _yield_expression();
                } else if (S.input.has_directive("use strict")) {
                    token_error(S.token, "Unexpected yield identifier inside strict mode");
                }
            }
  
            var left = maybe_conditional(no_in);
            var val = S.token.value;
  
            if (is("operator") && ASSIGNMENT(val)) {
                if (is_assignable(left) || (left = to_destructuring(left)) instanceof AST_Destructuring) {
                    next();
                    return new AST_Assign({
                        start    : start,
                        left     : left,
                        operator : val,
                        right    : maybe_assign(no_in),
                        end      : prev()
                    });
                }
                croak("Invalid assignment");
            }
            return left;
        };
  
        var expression = function(commas, no_in) {
            var start = S.token;
            var exprs = [];
            while (true) {
                exprs.push(maybe_assign(no_in));
                if (!commas || !is("punc", ",")) break;
                next();
                commas = true;
            }
            return exprs.length == 1 ? exprs[0] : new AST_Sequence({
                start       : start,
                expressions : exprs,
                end         : peek()
            });
        };
  
        function in_loop(cont) {
            ++S.in_loop;
            var ret = cont();
            --S.in_loop;
            return ret;
        }
  
        if (options.expression) {
            return expression(true);
        }
  
        return (function() {
            var start = S.token;
            var body = [];
            S.input.push_directives_stack();
            if (options.module) S.input.add_directive("use strict");
            while (!is("eof"))
                body.push(statement());
            S.input.pop_directives_stack();
            var end = prev();
            var toplevel = options.toplevel;
            if (toplevel) {
                toplevel.body = toplevel.body.concat(body);
                toplevel.end = end;
            } else {
                toplevel = new AST_Toplevel({ start: start, body: body, end: end });
            }
            return toplevel;
        })();
  
    }