# -*- coding: utf-8 -*-
# Copyright JS Foundation and other contributors, https://js.foundation/
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#   * Redistributions of source code must retain the above copyright
#     notice, this list of conditions and the following disclaimer.
#   * Redistributions in binary form must reproduce the above copyright
#     notice, this list of conditions and the following disclaimer in the
#     documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

from __future__ import absolute_import, unicode_literals

from .objects import Object
from .compat import basestring, unicode
from .utils import format
from .error_handler import ErrorHandler
from .messages import Messages
from .scanner import RawToken, Scanner, SourceLocation, Position, RegExp
from .token import Token, TokenName
from .syntax import Syntax
from . import nodes as Node


class Value(object):
    def __init__(self, value):
        self.value = value


class Params(object):
    def __init__(self, simple=None, message=None, stricted=None, firstRestricted=None, inFor=None, paramSet=None, params=None, get=None):
        self.simple = simple
        self.message = message
        self.stricted = stricted
        self.firstRestricted = firstRestricted
        self.inFor = inFor
        self.paramSet = paramSet
        self.params = params
        self.get = get


class Config(Object):
    def __init__(self, range=False, loc=False, source=None, tokens=False, comment=False, tolerant=False, **options):
        self.range = range
        self.loc = loc
        self.source = source
        self.tokens = tokens
        self.comment = comment
        self.tolerant = tolerant
        for k, v in options.items():
            setattr(self, k, v)


class Context(object):
    def __init__(self, isModule=False, allowAwait=False, allowIn=True, allowStrictDirective=True, allowYield=True, firstCoverInitializedNameError=None, isAssignmentTarget=False, isBindingElement=False, inFunctionBody=False, inIteration=False, inSwitch=False, labelSet=None, strict=False):
        self.isModule = isModule
        self.allowAwait = allowAwait
        self.allowIn = allowIn
        self.allowStrictDirective = allowStrictDirective
        self.allowYield = allowYield
        self.firstCoverInitializedNameError = firstCoverInitializedNameError
        self.isAssignmentTarget = isAssignmentTarget
        self.isBindingElement = isBindingElement
        self.inFunctionBody = inFunctionBody
        self.inIteration = inIteration
        self.inSwitch = inSwitch
        self.labelSet = {} if labelSet is None else labelSet
        self.strict = strict


class Marker(object):
    def __init__(self, index=None, line=None, column=None):
        self.index = index
        self.line = line
        self.column = column


class TokenEntry(Object):
    def __init__(self, type=None, value=None, regex=None, range=None, loc=None):
        self.type = type
        self.value = value
        self.regex = regex
        self.range = range
        self.loc = loc


class Parser(object):
    def __init__(self, code, options={}, delegate=None):
        self.config = Config(**options)

        self.delegate = delegate

        self.errorHandler = ErrorHandler()
        self.errorHandler.tolerant = self.config.tolerant
        self.scanner = Scanner(code, self.errorHandler)
        self.scanner.trackComment = self.config.comment

        self.operatorPrecedence = {
            '||': 1,
            '&&': 2,
            '|': 3,
            '^': 4,
            '&': 5,
            '==': 6,
            '!=': 6,
            '===': 6,
            '!==': 6,
            '<': 7,
            '>': 7,
            '<=': 7,
            '>=': 7,
            'instanceof': 7,
            'in': 7,
            '<<': 8,
            '>>': 8,
            '>>>': 8,
            '+': 9,
            '-': 9,
            '*': 11,
            '/': 11,
            '%': 11,
        }

        self.lookahead = RawToken(
            type=Token.EOF,
            value='',
            lineNumber=self.scanner.lineNumber,
            lineStart=0,
            start=0,
            end=0
        )
        self.hasLineTerminator = False

        self.context = Context(
            isModule=False,
            allowAwait=False,
            allowIn=True,
            allowStrictDirective=True,
            allowYield=True,
            firstCoverInitializedNameError=None,
            isAssignmentTarget=False,
            isBindingElement=False,
            inFunctionBody=False,
            inIteration=False,
            inSwitch=False,
            labelSet={},
            strict=False
        )
        self.tokens = []

        self.startMarker = Marker(
            index=0,
            line=self.scanner.lineNumber,
            column=0
        )
        self.lastMarker = Marker(
            index=0,
            line=self.scanner.lineNumber,
            column=0
        )
        self.nextToken()
        self.lastMarker = Marker(
            index=self.scanner.index,
            line=self.scanner.lineNumber,
            column=self.scanner.index - self.scanner.lineStart
        )

    def throwError(self, messageFormat, *args):
        msg = format(messageFormat, *args)
        index = self.lastMarker.index
        line = self.lastMarker.line
        column = self.lastMarker.column + 1
        raise self.errorHandler.createError(index, line, column, msg)

    def tolerateError(self, messageFormat, *args):
        msg = format(messageFormat, *args)
        index = self.lastMarker.index
        line = self.scanner.lineNumber
        column = self.lastMarker.column + 1
        self.errorHandler.tolerateError(index, line, column, msg)

    # Throw an exception because of the token.

    def unexpectedTokenError(self, token=None, message=None):
        msg = message or Messages.UnexpectedToken
        if token:
            if not message:
                typ = token.type
                if typ is Token.EOF:
                    msg = Messages.UnexpectedEOS
                elif typ is Token.Identifier:
                    msg = Messages.UnexpectedIdentifier
                elif typ is Token.NumericLiteral:
                    msg = Messages.UnexpectedNumber
                elif typ is Token.StringLiteral:
                    msg = Messages.UnexpectedString
                elif typ is Token.Template:
                    msg = Messages.UnexpectedTemplate
                elif typ is Token.Keyword:
                    if self.scanner.isFutureReservedWord(token.value):
                        msg = Messages.UnexpectedReserved
                    elif self.context.strict and self.scanner.isStrictModeReservedWord(token.value):
                        msg = Messages.StrictReservedWord
                else:
                    msg = Messages.UnexpectedToken
            value = token.value
        else:
            value = 'ILLEGAL'

        msg = msg.replace('%0', unicode(value), 1)

        if token and isinstance(token.lineNumber, int):
            index = token.start
            line = token.lineNumber
            lastMarkerLineStart = self.lastMarker.index - self.lastMarker.column
            column = token.start - lastMarkerLineStart + 1
            return self.errorHandler.createError(index, line, column, msg)
        else:
            index = self.lastMarker.index
            line = self.lastMarker.line
            column = self.lastMarker.column + 1
            return self.errorHandler.createError(index, line, column, msg)

    def throwUnexpectedToken(self, token=None, message=None):
        raise self.unexpectedTokenError(token, message)

    def tolerateUnexpectedToken(self, token=None, message=None):
        self.errorHandler.tolerate(self.unexpectedTokenError(token, message))

    def collectComments(self):
        if not self.config.comment:
            self.scanner.scanComments()
        else:
            comments = self.scanner.scanComments()
            if comments:
                for e in comments:
                    if e.multiLine:
                        node = Node.BlockComment(self.scanner.source[e.slice[0]:e.slice[1]])
                    else:
                        node = Node.LineComment(self.scanner.source[e.slice[0]:e.slice[1]])
                    if self.config.range:
                        node.range = e.range
                    if self.config.loc:
                        node.loc = e.loc
                    if self.delegate:
                        metadata = SourceLocation(
                            start=Position(
                                line=e.loc.start.line,
                                column=e.loc.start.column,
                                offset=e.range[0],
                            ),
                            end=Position(
                                line=e.loc.end.line,
                                column=e.loc.end.column,
                                offset=e.range[1],
                            )
                        )
                        new_node = self.delegate(node, metadata)
                        if new_node is not None:
                            node = new_node

    # From internal representation to an external structure

    def getTokenRaw(self, token):
        return self.scanner.source[token.start:token.end]

    def convertToken(self, token):
        t = TokenEntry(
            type=TokenName[token.type],
            value=self.getTokenRaw(token),
        )
        if self.config.range:
            t.range = [token.start, token.end]
        if self.config.loc:
            t.loc = SourceLocation(
                start=Position(
                    line=self.startMarker.line,
                    column=self.startMarker.column,
                ),
                end=Position(
                    line=self.scanner.lineNumber,
                    column=self.scanner.index - self.scanner.lineStart,
                ),
            )
        if token.type is Token.RegularExpression:
            t.regex = RegExp(
                pattern=token.pattern,
                flags=token.flags,
            )

        return t

    def nextToken(self):
        token = self.lookahead

        self.lastMarker.index = self.scanner.index
        self.lastMarker.line = self.scanner.lineNumber
        self.lastMarker.column = self.scanner.index - self.scanner.lineStart

        self.collectComments()

        if self.scanner.index != self.startMarker.index:
            self.startMarker.index = self.scanner.index
            self.startMarker.line = self.scanner.lineNumber
            self.startMarker.column = self.scanner.index - self.scanner.lineStart

        next = self.scanner.lex()
        self.hasLineTerminator = token.lineNumber != next.lineNumber

        if next and self.context.strict and next.type is Token.Identifier:
            if self.scanner.isStrictModeReservedWord(next.value):
                next.type = Token.Keyword
        self.lookahead = next

        if self.config.tokens and next.type is not Token.EOF:
            self.tokens.append(self.convertToken(next))

        return token

    def nextRegexToken(self):
        self.collectComments()

        token = self.scanner.scanRegExp()
        if self.config.tokens:
            # Pop the previous token, '/' or '/='
            # self is added from the lookahead token.
            self.tokens.pop()

            self.tokens.append(self.convertToken(token))

        # Prime the next lookahead.
        self.lookahead = token
        self.nextToken()

        return token

    def createNode(self):
        return Marker(
            index=self.startMarker.index,
            line=self.startMarker.line,
            column=self.startMarker.column,
        )

    def startNode(self, token, lastLineStart=0):
        column = token.start - token.lineStart
        line = token.lineNumber
        if column < 0:
            column += lastLineStart
            line -= 1

        return Marker(
            index=token.start,
            line=line,
            column=column,
        )

    def finalize(self, marker, node):
        if self.config.range:
            node.range = [marker.index, self.lastMarker.index]

        if self.config.loc:
            node.loc = SourceLocation(
                start=Position(
                    line=marker.line,
                    column=marker.column,
                ),
                end=Position(
                    line=self.lastMarker.line,
                    column=self.lastMarker.column,
                ),
            )
            if self.config.source:
                node.loc.source = self.config.source

        if self.delegate:
            metadata = SourceLocation(
                start=Position(
                    line=marker.line,
                    column=marker.column,
                    offset=marker.index,
                ),
                end=Position(
                    line=self.lastMarker.line,
                    column=self.lastMarker.column,
                    offset=self.lastMarker.index,
                )
            )
            new_node = self.delegate(node, metadata)
            if new_node is not None:
                node = new_node

        return node

    # Expect the next token to match the specified punctuator.
    # If not, an exception will be thrown.

    def expect(self, value):
        token = self.nextToken()
        if token.type is not Token.Punctuator or token.value != value:
            self.throwUnexpectedToken(token)

    # Quietly expect a comma when in tolerant mode, otherwise delegates to expect().

    def expectCommaSeparator(self):
        if self.config.tolerant:
            token = self.lookahead
            if token.type is Token.Punctuator and token.value == ',':
                self.nextToken()
            elif token.type is Token.Punctuator and token.value == ';':
                self.nextToken()
                self.tolerateUnexpectedToken(token)
            else:
                self.tolerateUnexpectedToken(token, Messages.UnexpectedToken)
        else:
            self.expect(',')

    # Expect the next token to match the specified keyword.
    # If not, an exception will be thrown.

    def expectKeyword(self, keyword):
        token = self.nextToken()
        if token.type is not Token.Keyword or token.value != keyword:
            self.throwUnexpectedToken(token)

    # Return true if the next token matches the specified punctuator.

    def match(self, *value):
        return self.lookahead.type is Token.Punctuator and self.lookahead.value in value

    # Return true if the next token matches the specified keyword

    def matchKeyword(self, *keyword):
        return self.lookahead.type is Token.Keyword and self.lookahead.value in keyword

    # Return true if the next token matches the specified contextual keyword
    # (where an identifier is sometimes a keyword depending on the context)

    def matchContextualKeyword(self, *keyword):
        return self.lookahead.type is Token.Identifier and self.lookahead.value in keyword

    # Return true if the next token is an assignment operator

    def matchAssign(self):
        if self.lookahead.type is not Token.Punctuator:
            return False

        op = self.lookahead.value
        return op in ('=', '*=', '**=', '/=', '%=', '+=', '-=', '<<=', '>>=', '>>>=', '&=', '^=', '|=')

    # Cover grammar support.
    #
    # When an assignment expression position starts with a left parenthesis, the determination of the type
    # of the syntax is to be deferred arbitrarily long until the end of the parentheses pair (plus a lookahead)
    # or the first comma. This situation also defers the determination of all the expressions nested in the pair.
    #
    # There are three productions that can be parsed in a parentheses pair that needs to be determined
    # after the outermost pair is closed. They are:
    #
    #   1. AssignmentExpression
    #   2. BindingElements
    #   3. AssignmentTargets
    #
    # In order to avoid exponential backtracking, we use two flags to denote if the production can be
    # binding element or assignment target.
    #
    # The three productions have the relationship:
    #
    #   BindingElements ⊆ AssignmentTargets ⊆ AssignmentExpression
    #
    # with a single exception that CoverInitializedName when used directly in an Expression, generates
    # an early error. Therefore, we need the third state, firstCoverInitializedNameError, to track the
    # first usage of CoverInitializedName and report it when we reached the end of the parentheses pair.
    #
    # isolateCoverGrammar function runs the given parser function with a new cover grammar context, and it does not
    # effect the current flags. This means the production the parser parses is only used as an expression. Therefore
    # the CoverInitializedName check is conducted.
    #
    # inheritCoverGrammar function runs the given parse function with a new cover grammar context, and it propagates
    # the flags outside of the parser. This means the production the parser parses is used as a part of a potential
    # pattern. The CoverInitializedName check is deferred.

    def isolateCoverGrammar(self, parseFunction):
        previousIsBindingElement = self.context.isBindingElement
        previousIsAssignmentTarget = self.context.isAssignmentTarget
        previousFirstCoverInitializedNameError = self.context.firstCoverInitializedNameError

        self.context.isBindingElement = True
        self.context.isAssignmentTarget = True
        self.context.firstCoverInitializedNameError = None

        result = parseFunction()
        if self.context.firstCoverInitializedNameError is not None:
            self.throwUnexpectedToken(self.context.firstCoverInitializedNameError)

        self.context.isBindingElement = previousIsBindingElement
        self.context.isAssignmentTarget = previousIsAssignmentTarget
        self.context.firstCoverInitializedNameError = previousFirstCoverInitializedNameError

        return result

    def inheritCoverGrammar(self, parseFunction):
        previousIsBindingElement = self.context.isBindingElement
        previousIsAssignmentTarget = self.context.isAssignmentTarget
        previousFirstCoverInitializedNameError = self.context.firstCoverInitializedNameError

        self.context.isBindingElement = True
        self.context.isAssignmentTarget = True
        self.context.firstCoverInitializedNameError = None

        result = parseFunction()

        self.context.isBindingElement = self.context.isBindingElement and previousIsBindingElement
        self.context.isAssignmentTarget = self.context.isAssignmentTarget and previousIsAssignmentTarget
        self.context.firstCoverInitializedNameError = previousFirstCoverInitializedNameError or self.context.firstCoverInitializedNameError

        return result

    def consumeSemicolon(self):
        if self.match(';'):
            self.nextToken()
        elif not self.hasLineTerminator:
            if self.lookahead.type is not Token.EOF and not self.match('}'):
                self.throwUnexpectedToken(self.lookahead)
            self.lastMarker.index = self.startMarker.index
            self.lastMarker.line = self.startMarker.line
            self.lastMarker.column = self.startMarker.column

    # https://tc39.github.io/ecma262/#sec-primary-expression

    def parsePrimaryExpression(self):
        node = self.createNode()

        typ = self.lookahead.type
        if typ is Token.Identifier:
            if (self.context.isModule or self.context.allowAwait) and self.lookahead.value == 'await':
                self.tolerateUnexpectedToken(self.lookahead)
            expr = self.parseFunctionExpression() if self.matchAsyncFunction() else self.finalize(node, Node.Identifier(self.nextToken().value))

        elif typ in (
            Token.NumericLiteral,
            Token.StringLiteral,
        ):
            if self.context.strict and self.lookahead.octal:
                self.tolerateUnexpectedToken(self.lookahead, Messages.StrictOctalLiteral)
            self.context.isAssignmentTarget = False
            self.context.isBindingElement = False
            token = self.nextToken()
            raw = self.getTokenRaw(token)
            expr = self.finalize(node, Node.Literal(token.value, raw))

        elif typ is Token.BooleanLiteral:
            self.context.isAssignmentTarget = False
            self.context.isBindingElement = False
            token = self.nextToken()
            raw = self.getTokenRaw(token)
            expr = self.finalize(node, Node.Literal(token.value == 'true', raw))

        elif typ is Token.NullLiteral:
            self.context.isAssignmentTarget = False
            self.context.isBindingElement = False
            token = self.nextToken()
            raw = self.getTokenRaw(token)
            expr = self.finalize(node, Node.Literal(None, raw))

        elif typ is Token.Template:
            expr = self.parseTemplateLiteral()

        elif typ is Token.Punctuator:
            value = self.lookahead.value
            if value == '(':
                self.context.isBindingElement = False
                expr = self.inheritCoverGrammar(self.parseGroupExpression)
            elif value == '[':
                expr = self.inheritCoverGrammar(self.parseArrayInitializer)
            elif value == '{':
                expr = self.inheritCoverGrammar(self.parseObjectInitializer)
            elif value in ('/', '/='):
                self.context.isAssignmentTarget = False
                self.context.isBindingElement = False
                self.scanner.index = self.startMarker.index
                token = self.nextRegexToken()
                raw = self.getTokenRaw(token)
                expr = self.finalize(node, Node.RegexLiteral(token.regex, raw, token.pattern, token.flags))
            else:
                expr = self.throwUnexpectedToken(self.nextToken())

        elif typ is Token.Keyword:
            if not self.context.strict and self.context.allowYield and self.matchKeyword('yield'):
                expr = self.parseIdentifierName()
            elif not self.context.strict and self.matchKeyword('let'):
                expr = self.finalize(node, Node.Identifier(self.nextToken().value))
            else:
                self.context.isAssignmentTarget = False
                self.context.isBindingElement = False
                if self.matchKeyword('function'):
                    expr = self.parseFunctionExpression()
                elif self.matchKeyword('this'):
                    self.nextToken()
                    expr = self.finalize(node, Node.ThisExpression())
                elif self.matchKeyword('class'):
                    expr = self.parseClassExpression()
                elif self.matchImportCall():
                    expr = self.parseImportCall()
                else:
                    expr = self.throwUnexpectedToken(self.nextToken())

        else:
            expr = self.throwUnexpectedToken(self.nextToken())

        return expr

    # https://tc39.github.io/ecma262/#sec-array-initializer

    def parseSpreadElement(self):
        node = self.createNode()
        self.expect('...')
        arg = self.inheritCoverGrammar(self.parseAssignmentExpression)
        return self.finalize(node, Node.SpreadElement(arg))

    def parseArrayInitializer(self):
        node = self.createNode()
        elements = []

        self.expect('[')
        while not self.match(']'):
            if self.match(','):
                self.nextToken()
                elements.append(None)
            elif self.match('...'):
                element = self.parseSpreadElement()
                if not self.match(']'):
                    self.context.isAssignmentTarget = False
                    self.context.isBindingElement = False
                    self.expect(',')
                elements.append(element)
            else:
                elements.append(self.inheritCoverGrammar(self.parseAssignmentExpression))
                if not self.match(']'):
                    self.expect(',')
        self.expect(']')

        return self.finalize(node, Node.ArrayExpression(elements))

    # https://tc39.github.io/ecma262/#sec-object-initializer

    def parsePropertyMethod(self, params):
        self.context.isAssignmentTarget = False
        self.context.isBindingElement = False

        previousStrict = self.context.strict
        previousAllowStrictDirective = self.context.allowStrictDirective
        self.context.allowStrictDirective = params.simple
        body = self.isolateCoverGrammar(self.parseFunctionSourceElements)
        if self.context.strict and params.firstRestricted:
            self.tolerateUnexpectedToken(params.firstRestricted, params.message)
        if self.context.strict and params.stricted:
            self.tolerateUnexpectedToken(params.stricted, params.message)
        self.context.strict = previousStrict
        self.context.allowStrictDirective = previousAllowStrictDirective

        return body

    def parsePropertyMethodFunction(self):
        isGenerator = False
        node = self.createNode()

        previousAllowYield = self.context.allowYield
        self.context.allowYield = True
        params = self.parseFormalParameters()
        method = self.parsePropertyMethod(params)
        self.context.allowYield = previousAllowYield

        return self.finalize(node, Node.FunctionExpression(None, params.params, method, isGenerator))

    def parsePropertyMethodAsyncFunction(self):
        node = self.createNode()

        previousAllowYield = self.context.allowYield
        previousAwait = self.context.allowAwait
        self.context.allowYield = False
        self.context.allowAwait = True
        params = self.parseFormalParameters()
        method = self.parsePropertyMethod(params)
        self.context.allowYield = previousAllowYield
        self.context.allowAwait = previousAwait

        return self.finalize(node, Node.AsyncFunctionExpression(None, params.params, method))

    def parseObjectPropertyKey(self):
        node = self.createNode()
        token = self.nextToken()

        typ = token.type
        if typ in (
            Token.StringLiteral,
            Token.NumericLiteral,
        ):
            if self.context.strict and token.octal:
                self.tolerateUnexpectedToken(token, Messages.StrictOctalLiteral)
            raw = self.getTokenRaw(token)
            key = self.finalize(node, Node.Literal(token.value, raw))

        elif typ in (
            Token.Identifier,
            Token.BooleanLiteral,
            Token.NullLiteral,
            Token.Keyword,
        ):
            key = self.finalize(node, Node.Identifier(token.value))

        elif typ is Token.Punctuator:
            if token.value == '[':
                key = self.isolateCoverGrammar(self.parseAssignmentExpression)
                self.expect(']')
            else:
                key = self.throwUnexpectedToken(token)

        else:
            key = self.throwUnexpectedToken(token)

        return key

    def isPropertyKey(self, key, value):
        return (
            (key.type is Syntax.Identifier and key.name == value) or
            (key.type is Syntax.Literal and key.value == value)
        )

    def parseObjectProperty(self, hasProto):
        node = self.createNode()
        token = self.lookahead

        key = None
        value = None

        computed = False
        method = False
        shorthand = False
        isAsync = False

        if token.type is Token.Identifier:
            id = token.value
            self.nextToken()
            computed = self.match('[')
            isAsync = not self.hasLineTerminator and (id == 'async') and not (self.match(':', '(', '*', ','))
            key = self.parseObjectPropertyKey() if isAsync else self.finalize(node, Node.Identifier(id))
        elif self.match('*'):
            self.nextToken()
        else:
            computed = self.match('[')
            key = self.parseObjectPropertyKey()

        lookaheadPropertyKey = self.qualifiedPropertyName(self.lookahead)
        if token.type is Token.Identifier and not isAsync and token.value == 'get' and lookaheadPropertyKey:
            kind = 'get'
            computed = self.match('[')
            key = self.parseObjectPropertyKey()
            self.context.allowYield = False
            value = self.parseGetterMethod()

        elif token.type is Token.Identifier and not isAsync and token.value == 'set' and lookaheadPropertyKey:
            kind = 'set'
            computed = self.match('[')
            key = self.parseObjectPropertyKey()
            value = self.parseSetterMethod()

        elif token.type is Token.Punctuator and token.value == '*' and lookaheadPropertyKey:
            kind = 'init'
            computed = self.match('[')
            key = self.parseObjectPropertyKey()
            value = self.parseGeneratorMethod()
            method = True

        else:
            if not key:
                self.throwUnexpectedToken(self.lookahead)

            kind = 'init'
            if self.match(':') and not isAsync:
                if not computed and self.isPropertyKey(key, '__proto__'):
                    if hasProto.value:
                        self.tolerateError(Messages.DuplicateProtoProperty)
                    hasProto.value = True
                self.nextToken()
                value = self.inheritCoverGrammar(self.parseAssignmentExpression)

            elif self.match('('):
                value = self.parsePropertyMethodAsyncFunction() if isAsync else self.parsePropertyMethodFunction()
                method = True

            elif token.type is Token.Identifier:
                id = self.finalize(node, Node.Identifier(token.value))
                if self.match('='):
                    self.context.firstCoverInitializedNameError = self.lookahead
                    self.nextToken()
                    shorthand = True
                    init = self.isolateCoverGrammar(self.parseAssignmentExpression)
                    value = self.finalize(node, Node.AssignmentPattern(id, init))
                else:
                    shorthand = True
                    value = id
            else:
                self.throwUnexpectedToken(self.nextToken())

        return self.finalize(node, Node.Property(kind, key, computed, value, method, shorthand))

    def parseObjectInitializer(self):
        node = self.createNode()

        self.expect('{')
        properties = []
        hasProto = Value(False)
        while not self.match('}'):
            properties.append(self.parseSpreadElement() if self.match('...') else self.parseObjectProperty(hasProto))
            if not self.match('}'):
                self.expectCommaSeparator()
        self.expect('}')

        return self.finalize(node, Node.ObjectExpression(properties))

    # https://tc39.github.io/ecma262/#sec-template-literals

    def parseTemplateHead(self):
        assert self.lookahead.head, 'Template literal must start with a template head'

        node = self.createNode()
        token = self.nextToken()
        raw = token.value
        cooked = token.cooked

        return self.finalize(node, Node.TemplateElement(raw, cooked, token.tail))

    def parseTemplateElement(self):
        if self.lookahead.type is not Token.Template:
            self.throwUnexpectedToken()

        node = self.createNode()
        token = self.nextToken()
        raw = token.value
        cooked = token.cooked

        return self.finalize(node, Node.TemplateElement(raw, cooked, token.tail))

    def parseTemplateLiteral(self):
        node = self.createNode()

        expressions = []
        quasis = []

        quasi = self.parseTemplateHead()
        quasis.append(quasi)
        while not quasi.tail:
            expressions.append(self.parseExpression())
            quasi = self.parseTemplateElement()
            quasis.append(quasi)

        return self.finalize(node, Node.TemplateLiteral(quasis, expressions))

    # https://tc39.github.io/ecma262/#sec-grouping-operator

    def reinterpretExpressionAsPattern(self, expr):
        typ = expr.type
        if typ in (
            Syntax.Identifier,
            Syntax.MemberExpression,
            Syntax.RestElement,
            Syntax.AssignmentPattern,
        ):
            pass
        elif typ is Syntax.SpreadElement:
            expr.type = Syntax.RestElement
            self.reinterpretExpressionAsPattern(expr.argument)
        elif typ is Syntax.ArrayExpression:
            expr.type = Syntax.ArrayPattern
            for elem in expr.elements:
                if elem is not None:
                    self.reinterpretExpressionAsPattern(elem)
        elif typ is Syntax.ObjectExpression:
            expr.type = Syntax.ObjectPattern
            for prop in expr.properties:
                self.reinterpretExpressionAsPattern(prop if prop.type is Syntax.SpreadElement else prop.value)
        elif typ is Syntax.AssignmentExpression:
            expr.type = Syntax.AssignmentPattern
            del expr.operator
            self.reinterpretExpressionAsPattern(expr.left)
        else:
            # Allow other node type for tolerant parsing.
            pass

    def parseGroupExpression(self):
        self.expect('(')
        if self.match(')'):
            self.nextToken()
            if not self.match('=>'):
                self.expect('=>')
            expr = Node.ArrowParameterPlaceHolder([])
        else:
            startToken = self.lookahead
            params = []
            if self.match('...'):
                expr = self.parseRestElement(params)
                self.expect(')')
                if not self.match('=>'):
                    self.expect('=>')
                expr = Node.ArrowParameterPlaceHolder([expr])
            else:
                arrow = False
                self.context.isBindingElement = True
                expr = self.inheritCoverGrammar(self.parseAssignmentExpression)

                if self.match(','):
                    expressions = []

                    self.context.isAssignmentTarget = False
                    expressions.append(expr)
                    while self.lookahead.type is not Token.EOF:
                        if not self.match(','):
                            break
                        self.nextToken()
                        if self.match(')'):
                            self.nextToken()
                            for expression in expressions:
                                self.reinterpretExpressionAsPattern(expression)
                            arrow = True
                            expr = Node.ArrowParameterPlaceHolder(expressions)
                        elif self.match('...'):
                            if not self.context.isBindingElement:
                                self.throwUnexpectedToken(self.lookahead)
                            expressions.append(self.parseRestElement(params))
                            self.expect(')')
                            if not self.match('=>'):
                                self.expect('=>')
                            self.context.isBindingElement = False
                            for expression in expressions:
                                self.reinterpretExpressionAsPattern(expression)
                            arrow = True
                            expr = Node.ArrowParameterPlaceHolder(expressions)
                        else:
                            expressions.append(self.inheritCoverGrammar(self.parseAssignmentExpression))
                        if arrow:
                            break
                    if not arrow:
                        expr = self.finalize(self.startNode(startToken), Node.SequenceExpression(expressions))

                if not arrow:
                    self.expect(')')
                    if self.match('=>'):
                        if expr.type is Syntax.Identifier and expr.name == 'yield':
                            arrow = True
                            expr = Node.ArrowParameterPlaceHolder([expr])
                        if not arrow:
                            if not self.context.isBindingElement:
                                self.throwUnexpectedToken(self.lookahead)

                            if expr.type is Syntax.SequenceExpression:
                                for expression in expr.expressions:
                                    self.reinterpretExpressionAsPattern(expression)
                            else:
                                self.reinterpretExpressionAsPattern(expr)

                            if expr.type is Syntax.SequenceExpression:
                                parameters = expr.expressions
                            else:
                                parameters = [expr]
                            expr = Node.ArrowParameterPlaceHolder(parameters)
                    self.context.isBindingElement = False

        return expr

    # https://tc39.github.io/ecma262/#sec-left-hand-side-expressions

    def parseArguments(self):
        self.expect('(')
        args = []
        if not self.match(')'):
            while True:
                if self.match('...'):
                    expr = self.parseSpreadElement()
                else:
                    expr = self.isolateCoverGrammar(self.parseAssignmentExpression)
                args.append(expr)
                if self.match(')'):
                    break
                self.expectCommaSeparator()
                if self.match(')'):
                    break
        self.expect(')')

        return args

    def isIdentifierName(self, token):
        return (
            token.type is Token.Identifier or
            token.type is Token.Keyword or
            token.type is Token.BooleanLiteral or
            token.type is Token.NullLiteral
        )

    def parseIdentifierName(self):
        node = self.createNode()
        token = self.nextToken()
        if not self.isIdentifierName(token):
            self.throwUnexpectedToken(token)
        return self.finalize(node, Node.Identifier(token.value))

    def parseNewExpression(self):
        node = self.createNode()

        id = self.parseIdentifierName()
        assert id.name == 'new', 'New expression must start with `new`'

        if self.match('.'):
            self.nextToken()
            if self.lookahead.type is Token.Identifier and self.context.inFunctionBody and self.lookahead.value == 'target':
                property = self.parseIdentifierName()
                expr = Node.MetaProperty(id, property)
            else:
                self.throwUnexpectedToken(self.lookahead)
        elif self.matchKeyword('import'):
            self.throwUnexpectedToken(self.lookahead)
        else:
            callee = self.isolateCoverGrammar(self.parseLeftHandSideExpression)
            args = self.parseArguments() if self.match('(') else []
            expr = Node.NewExpression(callee, args)
            self.context.isAssignmentTarget = False
            self.context.isBindingElement = False

        return self.finalize(node, expr)

    def parseAsyncArgument(self):
        arg = self.parseAssignmentExpression()
        self.context.firstCoverInitializedNameError = None
        return arg

    def parseAsyncArguments(self):
        self.expect('(')
        args = []
        if not self.match(')'):
            while True:
                if self.match('...'):
                    expr = self.parseSpreadElement()
                else:
                    expr = self.isolateCoverGrammar(self.parseAsyncArgument)
                args.append(expr)
                if self.match(')'):
                    break
                self.expectCommaSeparator()
                if self.match(')'):
                    break
        self.expect(')')

        return args

    def matchImportCall(self):
        match = self.matchKeyword('import')
        if match:
            state = self.scanner.saveState()
            self.scanner.scanComments()
            next = self.scanner.lex()
            self.scanner.restoreState(state)
            match = (next.type is Token.Punctuator) and (next.value == '(')

        return match

    def parseImportCall(self):
        node = self.createNode()
        self.expectKeyword('import')
        return self.finalize(node, Node.Import())

    def parseLeftHandSideExpressionAllowCall(self):
        startToken = self.lookahead
        maybeAsync = self.matchContextualKeyword('async')

        previousAllowIn = self.context.allowIn
        self.context.allowIn = True

        if self.matchKeyword('super') and self.context.inFunctionBody:
            expr = self.createNode()
            self.nextToken()
            expr = self.finalize(expr, Node.Super())
            if not self.match('(') and not self.match('.') and not self.match('['):
                self.throwUnexpectedToken(self.lookahead)
        else:
            expr = self.inheritCoverGrammar(self.parseNewExpression if self.matchKeyword('new') else self.parsePrimaryExpression)

        while True:
            if self.match('.'):
                self.context.isBindingElement = False
                self.context.isAssignmentTarget = True
                self.expect('.')
                property = self.parseIdentifierName()
                expr = self.finalize(self.startNode(startToken), Node.StaticMemberExpression(expr, property))

            elif self.match('('):
                asyncArrow = maybeAsync and (startToken.lineNumber == self.lookahead.lineNumber)
                self.context.isBindingElement = False
                self.context.isAssignmentTarget = False
                if asyncArrow:
                    args = self.parseAsyncArguments()
                else:
                    args = self.parseArguments()
                if expr.type is Syntax.Import and len(args) != 1:
                    self.tolerateError(Messages.BadImportCallArity)
                expr = self.finalize(self.startNode(startToken), Node.CallExpression(expr, args))
                if asyncArrow and self.match('=>'):
                    for arg in args:
                        self.reinterpretExpressionAsPattern(arg)
                    expr = Node.AsyncArrowParameterPlaceHolder(args)
            elif self.match('['):
                self.context.isBindingElement = False
                self.context.isAssignmentTarget = True
                self.expect('[')
                property = self.isolateCoverGrammar(self.parseExpression)
                self.expect(']')
                expr = self.finalize(self.startNode(startToken), Node.ComputedMemberExpression(expr, property))

            elif self.lookahead.type is Token.Template and self.lookahead.head:
                quasi = self.parseTemplateLiteral()
                expr = self.finalize(self.startNode(startToken), Node.TaggedTemplateExpression(expr, quasi))

            else:
                break

        self.context.allowIn = previousAllowIn

        return expr

    def parseSuper(self):
        node = self.createNode()

        self.expectKeyword('super')
        if not self.match('[') and not self.match('.'):
            self.throwUnexpectedToken(self.lookahead)

        return self.finalize(node, Node.Super())

    def parseLeftHandSideExpression(self):
        assert self.context.allowIn, 'callee of new expression always allow in keyword.'

        node = self.startNode(self.lookahead)
        if self.matchKeyword('super') and self.context.inFunctionBody:
            expr = self.parseSuper()
        else:
            expr = self.inheritCoverGrammar(self.parseNewExpression if self.matchKeyword('new') else self.parsePrimaryExpression)

        while True:
            if self.match('['):
                self.context.isBindingElement = False
                self.context.isAssignmentTarget = True
                self.expect('[')
                property = self.isolateCoverGrammar(self.parseExpression)
                self.expect(']')
                expr = self.finalize(node, Node.ComputedMemberExpression(expr, property))

            elif self.match('.'):
                self.context.isBindingElement = False
                self.context.isAssignmentTarget = True
                self.expect('.')
                property = self.parseIdentifierName()
                expr = self.finalize(node, Node.StaticMemberExpression(expr, property))

            elif self.lookahead.type is Token.Template and self.lookahead.head:
                quasi = self.parseTemplateLiteral()
                expr = self.finalize(node, Node.TaggedTemplateExpression(expr, quasi))

            else:
                break

        return expr

    # https://tc39.github.io/ecma262/#sec-update-expressions

    def parseUpdateExpression(self):
        startToken = self.lookahead

        if self.match('++', '--'):
            node = self.startNode(startToken)
            token = self.nextToken()
            expr = self.inheritCoverGrammar(self.parseUnaryExpression)
            if self.context.strict and expr.type is Syntax.Identifier and self.scanner.isRestrictedWord(expr.name):
                self.tolerateError(Messages.StrictLHSPrefix)
            if not self.context.isAssignmentTarget:
                self.tolerateError(Messages.InvalidLHSInAssignment)
            prefix = True
            expr = self.finalize(node, Node.UpdateExpression(token.value, expr, prefix))
            self.context.isAssignmentTarget = False
            self.context.isBindingElement = False
        else:
            expr = self.inheritCoverGrammar(self.parseLeftHandSideExpressionAllowCall)
            if not self.hasLineTerminator and self.lookahead.type is Token.Punctuator:
                if self.match('++', '--'):
                    if self.context.strict and expr.type is Syntax.Identifier and self.scanner.isRestrictedWord(expr.name):
                        self.tolerateError(Messages.StrictLHSPostfix)
                    if not self.context.isAssignmentTarget:
                        self.tolerateError(Messages.InvalidLHSInAssignment)
                    self.context.isAssignmentTarget = False
                    self.context.isBindingElement = False
                    operator = self.nextToken().value
                    prefix = False
                    expr = self.finalize(self.startNode(startToken), Node.UpdateExpression(operator, expr, prefix))

        return expr

    # https://tc39.github.io/ecma262/#sec-unary-operators

    def parseAwaitExpression(self):
        node = self.createNode()
        self.nextToken()
        argument = self.parseUnaryExpression()
        return self.finalize(node, Node.AwaitExpression(argument))

    def parseUnaryExpression(self):
        if (
            self.match('+', '-', '~', '!') or
            self.matchKeyword('delete', 'void', 'typeof')
        ):
            node = self.startNode(self.lookahead)
            token = self.nextToken()
            expr = self.inheritCoverGrammar(self.parseUnaryExpression)
            expr = self.finalize(node, Node.UnaryExpression(token.value, expr))
            if self.context.strict and expr.operator == 'delete' and expr.argument.type is Syntax.Identifier:
                self.tolerateError(Messages.StrictDelete)
            self.context.isAssignmentTarget = False
            self.context.isBindingElement = False
        elif self.context.allowAwait and self.matchContextualKeyword('await'):
            expr = self.parseAwaitExpression()
        else:
            expr = self.parseUpdateExpression()

        return expr

    def parseExponentiationExpression(self):
        startToken = self.lookahead

        expr = self.inheritCoverGrammar(self.parseUnaryExpression)
        if expr.type is not Syntax.UnaryExpression and self.match('**'):
            self.nextToken()
            self.context.isAssignmentTarget = False
            self.context.isBindingElement = False
            left = expr
            right = self.isolateCoverGrammar(self.parseExponentiationExpression)
            expr = self.finalize(self.startNode(startToken), Node.BinaryExpression('**', left, right))

        return expr

    # https://tc39.github.io/ecma262/#sec-exp-operator
    # https://tc39.github.io/ecma262/#sec-multiplicative-operators
    # https://tc39.github.io/ecma262/#sec-additive-operators
    # https://tc39.github.io/ecma262/#sec-bitwise-shift-operators
    # https://tc39.github.io/ecma262/#sec-relational-operators
    # https://tc39.github.io/ecma262/#sec-equality-operators
    # https://tc39.github.io/ecma262/#sec-binary-bitwise-operators
    # https://tc39.github.io/ecma262/#sec-binary-logical-operators

    def binaryPrecedence(self, token):
        op = token.value
        if token.type is Token.Punctuator:
            precedence = self.operatorPrecedence.get(op, 0)
        elif token.type is Token.Keyword:
            precedence = 7 if (op == 'instanceof' or (self.context.allowIn and op == 'in')) else 0
        else:
            precedence = 0
        return precedence

    def parseBinaryExpression(self):
        startToken = self.lookahead

        expr = self.inheritCoverGrammar(self.parseExponentiationExpression)

        token = self.lookahead
        prec = self.binaryPrecedence(token)
        if prec > 0:
            self.nextToken()

            self.context.isAssignmentTarget = False
            self.context.isBindingElement = False

            markers = [startToken, self.lookahead]
            left = expr
            right = self.isolateCoverGrammar(self.parseExponentiationExpression)

            stack = [left, token.value, right]
            precedences = [prec]
            while True:
                prec = self.binaryPrecedence(self.lookahead)
                if prec <= 0:
                    break

                # Reduce: make a binary expression from the three topmost entries.
                while len(stack) > 2 and prec <= precedences[-1]:
                    right = stack.pop()
                    operator = stack.pop()
                    precedences.pop()
                    left = stack.pop()
                    markers.pop()
                    node = self.startNode(markers[-1])
                    stack.append(self.finalize(node, Node.BinaryExpression(operator, left, right)))

                # Shift.
                stack.append(self.nextToken().value)
                precedences.append(prec)
                markers.append(self.lookahead)
                stack.append(self.isolateCoverGrammar(self.parseExponentiationExpression))

            # Final reduce to clean-up the stack.
            i = len(stack) - 1
            expr = stack[i]

            lastMarker = markers.pop()
            while i > 1:
                marker = markers.pop()
                lastLineStart = lastMarker.lineStart if lastMarker else 0
                node = self.startNode(marker, lastLineStart)
                operator = stack[i - 1]
                expr = self.finalize(node, Node.BinaryExpression(operator, stack[i - 2], expr))
                i -= 2
                lastMarker = marker

        return expr

    # https://tc39.github.io/ecma262/#sec-conditional-operator

    def parseConditionalExpression(self):
        startToken = self.lookahead

        expr = self.inheritCoverGrammar(self.parseBinaryExpression)
        if self.match('?'):
            self.nextToken()

            previousAllowIn = self.context.allowIn
            self.context.allowIn = True
            consequent = self.isolateCoverGrammar(self.parseAssignmentExpression)
            self.context.allowIn = previousAllowIn

            self.expect(':')
            alternate = self.isolateCoverGrammar(self.parseAssignmentExpression)

            expr = self.finalize(self.startNode(startToken), Node.ConditionalExpression(expr, consequent, alternate))
            self.context.isAssignmentTarget = False
            self.context.isBindingElement = False

        return expr

    # https://tc39.github.io/ecma262/#sec-assignment-operators

    def checkPatternParam(self, options, param):
        typ = param.type
        if typ is Syntax.Identifier:
            self.validateParam(options, param, param.name)
        elif typ is Syntax.RestElement:
            self.checkPatternParam(options, param.argument)
        elif typ is Syntax.AssignmentPattern:
            self.checkPatternParam(options, param.left)
        elif typ is Syntax.ArrayPattern:
            for element in param.elements:
                if element is not None:
                    self.checkPatternParam(options, element)
        elif typ is Syntax.ObjectPattern:
            for prop in param.properties:
                self.checkPatternParam(options, prop if prop.type is Syntax.RestElement else prop.value)

        options.simple = options.simple and isinstance(param, Node.Identifier)

    def reinterpretAsCoverFormalsList(self, expr):
        params = [expr]

        asyncArrow = False
        typ = expr.type
        if typ is Syntax.Identifier:
            pass
        elif typ is Syntax.ArrowParameterPlaceHolder:
            params = expr.params
            asyncArrow = expr.isAsync
        else:
            return None

        options = Params(
            simple=True,
            paramSet={},
        )

        for param in params:
            if param.type is Syntax.AssignmentPattern:
                if param.right.type is Syntax.YieldExpression:
                    if param.right.argument:
                        self.throwUnexpectedToken(self.lookahead)
                    param.right.type = Syntax.Identifier
                    param.right.name = 'yield'
                    del param.right.argument
                    del param.right.delegate
            elif asyncArrow and param.type is Syntax.Identifier and param.name == 'await':
                self.throwUnexpectedToken(self.lookahead)
            self.checkPatternParam(options, param)

        if self.context.strict or not self.context.allowYield:
            for param in params:
                if param.type is Syntax.YieldExpression:
                    self.throwUnexpectedToken(self.lookahead)

        if options.message is Messages.StrictParamDupe:
            token = options.stricted if self.context.strict else options.firstRestricted
            self.throwUnexpectedToken(token, options.message)

        return Params(
            simple=options.simple,
            params=params,
            stricted=options.stricted,
            firstRestricted=options.firstRestricted,
            message=options.message
        )

    def parseAssignmentExpression(self):
        if not self.context.allowYield and self.matchKeyword('yield'):
            expr = self.parseYieldExpression()
        else:
            startToken = self.lookahead
            token = startToken
            expr = self.parseConditionalExpression()

            if token.type is Token.Identifier and (token.lineNumber == self.lookahead.lineNumber) and token.value == 'async':
                if self.lookahead.type is Token.Identifier or self.matchKeyword('yield'):
                    arg = self.parsePrimaryExpression()
                    self.reinterpretExpressionAsPattern(arg)
                    expr = Node.AsyncArrowParameterPlaceHolder([arg])

            if expr.type is Syntax.ArrowParameterPlaceHolder or self.match('=>'):

                # https://tc39.github.io/ecma262/#sec-arrow-function-definitions
                self.context.isAssignmentTarget = False
                self.context.isBindingElement = False
                isAsync = expr.isAsync
                list = self.reinterpretAsCoverFormalsList(expr)

                if list:
                    if self.hasLineTerminator:
                        self.tolerateUnexpectedToken(self.lookahead)
                    self.context.firstCoverInitializedNameError = None

                    previousStrict = self.context.strict
                    previousAllowStrictDirective = self.context.allowStrictDirective
                    self.context.allowStrictDirective = list.simple

                    previousAllowYield = self.context.allowYield
                    previousAwait = self.context.allowAwait
                    self.context.allowYield = True
                    self.context.allowAwait = isAsync

                    node = self.startNode(startToken)
                    self.expect('=>')
                    if self.match('{'):
                        previousAllowIn = self.context.allowIn
                        self.context.allowIn = True
                        body = self.parseFunctionSourceElements()
                        self.context.allowIn = previousAllowIn
                    else:
                        body = self.isolateCoverGrammar(self.parseAssignmentExpression)
                    expression = body.type is not Syntax.BlockStatement

                    if self.context.strict and list.firstRestricted:
                        self.throwUnexpectedToken(list.firstRestricted, list.message)
                    if self.context.strict and list.stricted:
                        self.tolerateUnexpectedToken(list.stricted, list.message)
                    if isAsync:
                        expr = self.finalize(node, Node.AsyncArrowFunctionExpression(list.params, body, expression))
                    else:
                        expr = self.finalize(node, Node.ArrowFunctionExpression(list.params, body, expression))

                    self.context.strict = previousStrict
                    self.context.allowStrictDirective = previousAllowStrictDirective
                    self.context.allowYield = previousAllowYield
                    self.context.allowAwait = previousAwait
            else:
                if self.matchAssign():
                    if not self.context.isAssignmentTarget:
                        self.tolerateError(Messages.InvalidLHSInAssignment)

                    if self.context.strict and expr.type is Syntax.Identifier:
                        id = expr
                        if self.scanner.isRestrictedWord(id.name):
                            self.tolerateUnexpectedToken(token, Messages.StrictLHSAssignment)
                        if self.scanner.isStrictModeReservedWord(id.name):
                            self.tolerateUnexpectedToken(token, Messages.StrictReservedWord)

                    if not self.match('='):
                        self.context.isAssignmentTarget = False
                        self.context.isBindingElement = False
                    else:
                        self.reinterpretExpressionAsPattern(expr)

                    token = self.nextToken()
                    operator = token.value
                    right = self.isolateCoverGrammar(self.parseAssignmentExpression)
                    expr = self.finalize(self.startNode(startToken), Node.AssignmentExpression(operator, expr, right))
                    self.context.firstCoverInitializedNameError = None

        return expr

    # https://tc39.github.io/ecma262/#sec-comma-operator

    def parseExpression(self):
        startToken = self.lookahead
        expr = self.isolateCoverGrammar(self.parseAssignmentExpression)

        if self.match(','):
            expressions = []
            expressions.append(expr)
            while self.lookahead.type is not Token.EOF:
                if not self.match(','):
                    break
                self.nextToken()
                expressions.append(self.isolateCoverGrammar(self.parseAssignmentExpression))

            expr = self.finalize(self.startNode(startToken), Node.SequenceExpression(expressions))

        return expr

    # https://tc39.github.io/ecma262/#sec-block

    def parseStatementListItem(self):
        self.context.isAssignmentTarget = True
        self.context.isBindingElement = True
        if self.lookahead.type is Token.Keyword:
            value = self.lookahead.value
            if value == 'export':
                if not self.context.isModule:
                    self.tolerateUnexpectedToken(self.lookahead, Messages.IllegalExportDeclaration)
                statement = self.parseExportDeclaration()
            elif value == 'import':
                if self.matchImportCall():
                    statement = self.parseExpressionStatement()
                else:
                    if not self.context.isModule:
                        self.tolerateUnexpectedToken(self.lookahead, Messages.IllegalImportDeclaration)
                    statement = self.parseImportDeclaration()
            elif value == 'const':
                statement = self.parseLexicalDeclaration(Params(inFor=False))
            elif value == 'function':
                statement = self.parseFunctionDeclaration()
            elif value == 'class':
                statement = self.parseClassDeclaration()
            elif value == 'let':
                statement = self.parseLexicalDeclaration(Params(inFor=False)) if self.isLexicalDeclaration() else self.parseStatement()
            else:
                statement = self.parseStatement()
        else:
            statement = self.parseStatement()

        return statement

    def parseBlock(self):
        node = self.createNode()

        self.expect('{')
        block = []
        while True:
            if self.match('}'):
                break
            block.append(self.parseStatementListItem())
        self.expect('}')

        return self.finalize(node, Node.BlockStatement(block))

    # https://tc39.github.io/ecma262/#sec-let-and-const-declarations

    def parseLexicalBinding(self, kind, options):
        node = self.createNode()
        params = []
        id = self.parsePattern(params, kind)

        if self.context.strict and id.type is Syntax.Identifier:
            if self.scanner.isRestrictedWord(id.name):
                self.tolerateError(Messages.StrictVarName)

        init = None
        if kind == 'const':
            if not self.matchKeyword('in') and not self.matchContextualKeyword('of'):
                if self.match('='):
                    self.nextToken()
                    init = self.isolateCoverGrammar(self.parseAssignmentExpression)
                else:
                    self.throwError(Messages.DeclarationMissingInitializer, 'const')
        elif (not options.inFor and id.type is not Syntax.Identifier) or self.match('='):
            self.expect('=')
            init = self.isolateCoverGrammar(self.parseAssignmentExpression)

        return self.finalize(node, Node.VariableDeclarator(id, init))

    def parseBindingList(self, kind, options):
        lst = [self.parseLexicalBinding(kind, options)]

        while self.match(','):
            self.nextToken()
            lst.append(self.parseLexicalBinding(kind, options))

        return lst

    def isLexicalDeclaration(self):
        state = self.scanner.saveState()
        self.scanner.scanComments()
        next = self.scanner.lex()
        self.scanner.restoreState(state)

        return (
            (next.type is Token.Identifier) or
            (next.type is Token.Punctuator and next.value == '[') or
            (next.type is Token.Punctuator and next.value == '{') or
            (next.type is Token.Keyword and next.value == 'let') or
            (next.type is Token.Keyword and next.value == 'yield')
        )

    def parseLexicalDeclaration(self, options):
        node = self.createNode()
        kind = self.nextToken().value
        assert kind == 'let' or kind == 'const', 'Lexical declaration must be either or const'

        declarations = self.parseBindingList(kind, options)
        self.consumeSemicolon()

        return self.finalize(node, Node.VariableDeclaration(declarations, kind))

    # https://tc39.github.io/ecma262/#sec-destructuring-binding-patterns

    def parseBindingRestElement(self, params, kind=None):
        node = self.createNode()

        self.expect('...')
        arg = self.parsePattern(params, kind)

        return self.finalize(node, Node.RestElement(arg))

    def parseArrayPattern(self, params, kind=None):
        node = self.createNode()

        self.expect('[')
        elements = []
        while not self.match(']'):
            if self.match(','):
                self.nextToken()
                elements.append(None)
            else:
                if self.match('...'):
                    elements.append(self.parseBindingRestElement(params, kind))
                    break
                else:
                    elements.append(self.parsePatternWithDefault(params, kind))
                if not self.match(']'):
                    self.expect(',')
        self.expect(']')

        return self.finalize(node, Node.ArrayPattern(elements))

    def parsePropertyPattern(self, params, kind=None):
        node = self.createNode()

        computed = False
        shorthand = False
        method = False

        key = None

        if self.lookahead.type is Token.Identifier:
            keyToken = self.lookahead
            key = self.parseVariableIdentifier()
            init = self.finalize(node, Node.Identifier(keyToken.value))
            if self.match('='):
                params.append(keyToken)
                shorthand = True
                self.nextToken()
                expr = self.parseAssignmentExpression()
                value = self.finalize(self.startNode(keyToken), Node.AssignmentPattern(init, expr))
            elif not self.match(':'):
                params.append(keyToken)
                shorthand = True
                value = init
            else:
                self.expect(':')
                value = self.parsePatternWithDefault(params, kind)
        else:
            computed = self.match('[')
            key = self.parseObjectPropertyKey()
            self.expect(':')
            value = self.parsePatternWithDefault(params, kind)

        return self.finalize(node, Node.Property('init', key, computed, value, method, shorthand))

    def parseRestProperty(self, params, kind):
        node = self.createNode()
        self.expect('...')
        arg = self.parsePattern(params)
        if self.match('='):
            self.throwError(Messages.DefaultRestProperty)
        if not self.match('}'):
            self.throwError(Messages.PropertyAfterRestProperty)
        return self.finalize(node, Node.RestElement(arg))

    def parseObjectPattern(self, params, kind=None):
        node = self.createNode()
        properties = []

        self.expect('{')
        while not self.match('}'):
            properties.append(self.parseRestProperty(params, kind) if self.match('...') else self.parsePropertyPattern(params, kind))
            if not self.match('}'):
                self.expect(',')
        self.expect('}')

        return self.finalize(node, Node.ObjectPattern(properties))

    def parsePattern(self, params, kind=None):
        if self.match('['):
            pattern = self.parseArrayPattern(params, kind)
        elif self.match('{'):
            pattern = self.parseObjectPattern(params, kind)
        else:
            if self.matchKeyword('let') and (kind in ('const', 'let')):
                self.tolerateUnexpectedToken(self.lookahead, Messages.LetInLexicalBinding)
            params.append(self.lookahead)
            pattern = self.parseVariableIdentifier(kind)

        return pattern

    def parsePatternWithDefault(self, params, kind=None):
        startToken = self.lookahead

        pattern = self.parsePattern(params, kind)
        if self.match('='):
            self.nextToken()
            previousAllowYield = self.context.allowYield
            self.context.allowYield = True
            right = self.isolateCoverGrammar(self.parseAssignmentExpression)
            self.context.allowYield = previousAllowYield
            pattern = self.finalize(self.startNode(startToken), Node.AssignmentPattern(pattern, right))

        return pattern

    # https://tc39.github.io/ecma262/#sec-variable-statement

    def parseVariableIdentifier(self, kind=None):
        node = self.createNode()

        token = self.nextToken()
        if token.type is Token.Keyword and token.value == 'yield':
            if self.context.strict:
                self.tolerateUnexpectedToken(token, Messages.StrictReservedWord)
            elif not self.context.allowYield:
                self.throwUnexpectedToken(token)
        elif token.type is not Token.Identifier:
            if self.context.strict and token.type is Token.Keyword and self.scanner.isStrictModeReservedWord(token.value):
                self.tolerateUnexpectedToken(token, Messages.StrictReservedWord)
            else:
                if self.context.strict or token.value != 'let' or kind != 'var':
                    self.throwUnexpectedToken(token)
        elif (self.context.isModule or self.context.allowAwait) and token.type is Token.Identifier and token.value == 'await':
            self.tolerateUnexpectedToken(token)

        return self.finalize(node, Node.Identifier(token.value))

    def parseVariableDeclaration(self, options):
        node = self.createNode()

        params = []
        id = self.parsePattern(params, 'var')

        if self.context.strict and id.type is Syntax.Identifier:
            if self.scanner.isRestrictedWord(id.name):
                self.tolerateError(Messages.StrictVarName)

        init = None
        if self.match('='):
            self.nextToken()
            init = self.isolateCoverGrammar(self.parseAssignmentExpression)
        elif id.type is not Syntax.Identifier and not options.inFor:
            self.expect('=')

        return self.finalize(node, Node.VariableDeclarator(id, init))

    def parseVariableDeclarationList(self, options):
        opt = Params(inFor=options.inFor)

        lst = []
        lst.append(self.parseVariableDeclaration(opt))
        while self.match(','):
            self.nextToken()
            lst.append(self.parseVariableDeclaration(opt))

        return lst

    def parseVariableStatement(self):
        node = self.createNode()
        self.expectKeyword('var')
        declarations = self.parseVariableDeclarationList(Params(inFor=False))
        self.consumeSemicolon()

        return self.finalize(node, Node.VariableDeclaration(declarations, 'var'))

    # https://tc39.github.io/ecma262/#sec-empty-statement

    def parseEmptyStatement(self):
        node = self.createNode()
        self.expect(';')
        return self.finalize(node, Node.EmptyStatement())

    # https://tc39.github.io/ecma262/#sec-expression-statement

    def parseExpressionStatement(self):
        node = self.createNode()
        expr = self.parseExpression()
        self.consumeSemicolon()
        return self.finalize(node, Node.ExpressionStatement(expr))

    # https://tc39.github.io/ecma262/#sec-if-statement

    def parseIfClause(self):
        if self.context.strict and self.matchKeyword('function'):
            self.tolerateError(Messages.StrictFunction)
        return self.parseStatement()

    def parseIfStatement(self):
        node = self.createNode()
        alternate = None

        self.expectKeyword('if')
        self.expect('(')
        test = self.parseExpression()

        if not self.match(')') and self.config.tolerant:
            self.tolerateUnexpectedToken(self.nextToken())
            consequent = self.finalize(self.createNode(), Node.EmptyStatement())
        else:
            self.expect(')')
            consequent = self.parseIfClause()
            if self.matchKeyword('else'):
                self.nextToken()
                alternate = self.parseIfClause()

        return self.finalize(node, Node.IfStatement(test, consequent, alternate))

    # https://tc39.github.io/ecma262/#sec-do-while-statement

    def parseDoWhileStatement(self):
        node = self.createNode()
        self.expectKeyword('do')

        previousInIteration = self.context.inIteration
        self.context.inIteration = True
        body = self.parseStatement()
        self.context.inIteration = previousInIteration

        self.expectKeyword('while')
        self.expect('(')
        test = self.parseExpression()

        if not self.match(')') and self.config.tolerant:
            self.tolerateUnexpectedToken(self.nextToken())
        else:
            self.expect(')')
            if self.match(';'):
                self.nextToken()

        return self.finalize(node, Node.DoWhileStatement(body, test))

    # https://tc39.github.io/ecma262/#sec-while-statement

    def parseWhileStatement(self):
        node = self.createNode()

        self.expectKeyword('while')
        self.expect('(')
        test = self.parseExpression()

        if not self.match(')') and self.config.tolerant:
            self.tolerateUnexpectedToken(self.nextToken())
            body = self.finalize(self.createNode(), Node.EmptyStatement())
        else:
            self.expect(')')

            previousInIteration = self.context.inIteration
            self.context.inIteration = True
            body = self.parseStatement()
            self.context.inIteration = previousInIteration

        return self.finalize(node, Node.WhileStatement(test, body))

    # https://tc39.github.io/ecma262/#sec-for-statement
    # https://tc39.github.io/ecma262/#sec-for-in-and-for-of-statements

    def parseForStatement(self):
        init = None
        test = None
        update = None
        forIn = True
        left = None
        right = None

        node = self.createNode()
        self.expectKeyword('for')
        self.expect('(')

        if self.match(';'):
            self.nextToken()
        else:
            if self.matchKeyword('var'):
                init = self.createNode()
                self.nextToken()

                previousAllowIn = self.context.allowIn
                self.context.allowIn = False
                declarations = self.parseVariableDeclarationList(Params(inFor=True))
                self.context.allowIn = previousAllowIn

                if len(declarations) == 1 and self.matchKeyword('in'):
                    decl = declarations[0]
                    if decl.init and (decl.id.type is Syntax.ArrayPattern or decl.id.type is Syntax.ObjectPattern or self.context.strict):
                        self.tolerateError(Messages.ForInOfLoopInitializer, 'for-in')
                    init = self.finalize(init, Node.VariableDeclaration(declarations, 'var'))
                    self.nextToken()
                    left = init
                    right = self.parseExpression()
                    init = None
                elif len(declarations) == 1 and declarations[0].init is None and self.matchContextualKeyword('of'):
                    init = self.finalize(init, Node.VariableDeclaration(declarations, 'var'))
                    self.nextToken()
                    left = init
                    right = self.parseAssignmentExpression()
                    init = None
                    forIn = False
                else:
                    init = self.finalize(init, Node.VariableDeclaration(declarations, 'var'))
                    self.expect(';')
            elif self.matchKeyword('const', 'let'):
                init = self.createNode()
                kind = self.nextToken().value

                if not self.context.strict and self.lookahead.value == 'in':
                    init = self.finalize(init, Node.Identifier(kind))
                    self.nextToken()
                    left = init
                    right = self.parseExpression()
                    init = None
                else:
                    previousAllowIn = self.context.allowIn
                    self.context.allowIn = False
                    declarations = self.parseBindingList(kind, Params(inFor=True))
                    self.context.allowIn = previousAllowIn

                    if len(declarations) == 1 and declarations[0].init is None and self.matchKeyword('in'):
                        init = self.finalize(init, Node.VariableDeclaration(declarations, kind))
                        self.nextToken()
                        left = init
                        right = self.parseExpression()
                        init = None
                    elif len(declarations) == 1 and declarations[0].init is None and self.matchContextualKeyword('of'):
                        init = self.finalize(init, Node.VariableDeclaration(declarations, kind))
                        self.nextToken()
                        left = init
                        right = self.parseAssignmentExpression()
                        init = None
                        forIn = False
                    else:
                        self.consumeSemicolon()
                        init = self.finalize(init, Node.VariableDeclaration(declarations, kind))
            else:
                initStartToken = self.lookahead
                previousAllowIn = self.context.allowIn
                self.context.allowIn = False
                init = self.inheritCoverGrammar(self.parseAssignmentExpression)
                self.context.allowIn = previousAllowIn

                if self.matchKeyword('in'):
                    if not self.context.isAssignmentTarget or init.type is Syntax.AssignmentExpression:
                        self.tolerateError(Messages.InvalidLHSInForIn)

                    self.nextToken()
                    self.reinterpretExpressionAsPattern(init)
                    left = init
                    right = self.parseExpression()
                    init = None
                elif self.matchContextualKeyword('of'):
                    if not self.context.isAssignmentTarget or init.type is Syntax.AssignmentExpression:
                        self.tolerateError(Messages.InvalidLHSInForLoop)

                    self.nextToken()
                    self.reinterpretExpressionAsPattern(init)
                    left = init
                    right = self.parseAssignmentExpression()
                    init = None
                    forIn = False
                else:
                    if self.match(','):
                        initSeq = [init]
                        while self.match(','):
                            self.nextToken()
                            initSeq.append(self.isolateCoverGrammar(self.parseAssignmentExpression))
                        init = self.finalize(self.startNode(initStartToken), Node.SequenceExpression(initSeq))
                    self.expect(';')

        if left is None:
            if not self.match(';'):
                test = self.parseExpression()
            self.expect(';')
            if not self.match(')'):
                update = self.parseExpression()

        if not self.match(')') and self.config.tolerant:
            self.tolerateUnexpectedToken(self.nextToken())
            body = self.finalize(self.createNode(), Node.EmptyStatement())
        else:
            self.expect(')')

            previousInIteration = self.context.inIteration
            self.context.inIteration = True
            body = self.isolateCoverGrammar(self.parseStatement)
            self.context.inIteration = previousInIteration

        if left is None:
            return self.finalize(node, Node.ForStatement(init, test, update, body))

        if forIn:
            return self.finalize(node, Node.ForInStatement(left, right, body))

        return self.finalize(node, Node.ForOfStatement(left, right, body))

    # https://tc39.github.io/ecma262/#sec-continue-statement

    def parseContinueStatement(self):
        node = self.createNode()
        self.expectKeyword('continue')

        label = None
        if self.lookahead.type is Token.Identifier and not self.hasLineTerminator:
            id = self.parseVariableIdentifier()
            label = id

            key = '$' + id.name
            if key not in self.context.labelSet:
                self.throwError(Messages.UnknownLabel, id.name)

        self.consumeSemicolon()
        if label is None and not self.context.inIteration:
            self.throwError(Messages.IllegalContinue)

        return self.finalize(node, Node.ContinueStatement(label))

    # https://tc39.github.io/ecma262/#sec-break-statement

    def parseBreakStatement(self):
        node = self.createNode()
        self.expectKeyword('break')

        label = None
        if self.lookahead.type is Token.Identifier and not self.hasLineTerminator:
            id = self.parseVariableIdentifier()

            key = '$' + id.name
            if key not in self.context.labelSet:
                self.throwError(Messages.UnknownLabel, id.name)
            label = id

        self.consumeSemicolon()
        if label is None and not self.context.inIteration and not self.context.inSwitch:
            self.throwError(Messages.IllegalBreak)

        return self.finalize(node, Node.BreakStatement(label))

    # https://tc39.github.io/ecma262/#sec-return-statement

    def parseReturnStatement(self):
        if not self.context.inFunctionBody:
            self.tolerateError(Messages.IllegalReturn)

        node = self.createNode()
        self.expectKeyword('return')

        hasArgument = (
            (
                not self.match(';') and not self.match('}') and
                not self.hasLineTerminator and self.lookahead.type is not Token.EOF
            ) or
            self.lookahead.type is Token.StringLiteral or
            self.lookahead.type is Token.Template
        )
        argument = self.parseExpression() if hasArgument else None
        self.consumeSemicolon()

        return self.finalize(node, Node.ReturnStatement(argument))

    # https://tc39.github.io/ecma262/#sec-with-statement

    def parseWithStatement(self):
        if self.context.strict:
            self.tolerateError(Messages.StrictModeWith)

        node = self.createNode()

        self.expectKeyword('with')
        self.expect('(')
        object = self.parseExpression()

        if not self.match(')') and self.config.tolerant:
            self.tolerateUnexpectedToken(self.nextToken())
            body = self.finalize(self.createNode(), Node.EmptyStatement())
        else:
            self.expect(')')
            body = self.parseStatement()

        return self.finalize(node, Node.WithStatement(object, body))

    # https://tc39.github.io/ecma262/#sec-switch-statement

    def parseSwitchCase(self):
        node = self.createNode()

        if self.matchKeyword('default'):
            self.nextToken()
            test = None
        else:
            self.expectKeyword('case')
            test = self.parseExpression()
        self.expect(':')

        consequent = []
        while True:
            if self.match('}') or self.matchKeyword('default', 'case'):
                break
            consequent.append(self.parseStatementListItem())

        return self.finalize(node, Node.SwitchCase(test, consequent))

    def parseSwitchStatement(self):
        node = self.createNode()
        self.expectKeyword('switch')

        self.expect('(')
        discriminant = self.parseExpression()
        self.expect(')')

        previousInSwitch = self.context.inSwitch
        self.context.inSwitch = True

        cases = []
        defaultFound = False
        self.expect('{')
        while True:
            if self.match('}'):
                break
            clause = self.parseSwitchCase()
            if clause.test is None:
                if defaultFound:
                    self.throwError(Messages.MultipleDefaultsInSwitch)
                defaultFound = True
            cases.append(clause)
        self.expect('}')

        self.context.inSwitch = previousInSwitch

        return self.finalize(node, Node.SwitchStatement(discriminant, cases))

    # https://tc39.github.io/ecma262/#sec-labelled-statements

    def parseLabelledStatement(self):
        node = self.createNode()
        expr = self.parseExpression()

        if expr.type is Syntax.Identifier and self.match(':'):
            self.nextToken()

            id = expr
            key = '$' + id.name
            if key in self.context.labelSet:
                self.throwError(Messages.Redeclaration, 'Label', id.name)

            self.context.labelSet[key] = True
            if self.matchKeyword('class'):
                self.tolerateUnexpectedToken(self.lookahead)
                body = self.parseClassDeclaration()
            elif self.matchKeyword('function'):
                token = self.lookahead
                declaration = self.parseFunctionDeclaration()
                if self.context.strict:
                    self.tolerateUnexpectedToken(token, Messages.StrictFunction)
                elif declaration.generator:
                    self.tolerateUnexpectedToken(token, Messages.GeneratorInLegacyContext)
                body = declaration
            else:
                body = self.parseStatement()
            del self.context.labelSet[key]

            statement = Node.LabeledStatement(id, body)
        else:
            self.consumeSemicolon()
            statement = Node.ExpressionStatement(expr)

        return self.finalize(node, statement)

    # https://tc39.github.io/ecma262/#sec-throw-statement

    def parseThrowStatement(self):
        node = self.createNode()
        self.expectKeyword('throw')

        if self.hasLineTerminator:
            self.throwError(Messages.NewlineAfterThrow)

        argument = self.parseExpression()
        self.consumeSemicolon()

        return self.finalize(node, Node.ThrowStatement(argument))

    # https://tc39.github.io/ecma262/#sec-try-statement

    def parseCatchClause(self):
        node = self.createNode()

        self.expectKeyword('catch')

        self.expect('(')
        if self.match(')'):
            self.throwUnexpectedToken(self.lookahead)

        params = []
        param = self.parsePattern(params)
        paramMap = {}
        for p in params:
            key = '$' + p.value
            if key in paramMap:
                self.tolerateError(Messages.DuplicateBinding, p.value)
            paramMap[key] = True

        if self.context.strict and param.type is Syntax.Identifier:
            if self.scanner.isRestrictedWord(param.name):
                self.tolerateError(Messages.StrictCatchVariable)

        self.expect(')')
        body = self.parseBlock()

        return self.finalize(node, Node.CatchClause(param, body))

    def parseFinallyClause(self):
        self.expectKeyword('finally')
        return self.parseBlock()

    def parseTryStatement(self):
        node = self.createNode()
        self.expectKeyword('try')

        block = self.parseBlock()
        handler = self.parseCatchClause() if self.matchKeyword('catch') else None
        finalizer = self.parseFinallyClause() if self.matchKeyword('finally') else None

        if not handler and not finalizer:
            self.throwError(Messages.NoCatchOrFinally)

        return self.finalize(node, Node.TryStatement(block, handler, finalizer))

    # https://tc39.github.io/ecma262/#sec-debugger-statement

    def parseDebuggerStatement(self):
        node = self.createNode()
        self.expectKeyword('debugger')
        self.consumeSemicolon()
        return self.finalize(node, Node.DebuggerStatement())

    # https://tc39.github.io/ecma262/#sec-ecmascript-language-statements-and-declarations

    def parseStatement(self):
        typ = self.lookahead.type
        if typ in (
            Token.BooleanLiteral,
            Token.NullLiteral,
            Token.NumericLiteral,
            Token.StringLiteral,
            Token.Template,
            Token.RegularExpression,
        ):
            statement = self.parseExpressionStatement()

        elif typ is Token.Punctuator:
            value = self.lookahead.value
            if value == '{':
                statement = self.parseBlock()
            elif value == '(':
                statement = self.parseExpressionStatement()
            elif value == ';':
                statement = self.parseEmptyStatement()
            else:
                statement = self.parseExpressionStatement()

        elif typ is Token.Identifier:
            statement = self.parseFunctionDeclaration() if self.matchAsyncFunction() else self.parseLabelledStatement()

        elif typ is Token.Keyword:
            value = self.lookahead.value
            if value == 'break':
                statement = self.parseBreakStatement()
            elif value == 'continue':
                statement = self.parseContinueStatement()
            elif value == 'debugger':
                statement = self.parseDebuggerStatement()
            elif value == 'do':
                statement = self.parseDoWhileStatement()
            elif value == 'for':
                statement = self.parseForStatement()
            elif value == 'function':
                statement = self.parseFunctionDeclaration()
            elif value == 'if':
                statement = self.parseIfStatement()
            elif value == 'return':
                statement = self.parseReturnStatement()
            elif value == 'switch':
                statement = self.parseSwitchStatement()
            elif value == 'throw':
                statement = self.parseThrowStatement()
            elif value == 'try':
                statement = self.parseTryStatement()
            elif value == 'var':
                statement = self.parseVariableStatement()
            elif value == 'while':
                statement = self.parseWhileStatement()
            elif value == 'with':
                statement = self.parseWithStatement()
            else:
                statement = self.parseExpressionStatement()

        else:
            statement = self.throwUnexpectedToken(self.lookahead)

        return statement

    # https://tc39.github.io/ecma262/#sec-function-definitions

    def parseFunctionSourceElements(self):
        node = self.createNode()

        self.expect('{')
        body = self.parseDirectivePrologues()

        previousLabelSet = self.context.labelSet
        previousInIteration = self.context.inIteration
        previousInSwitch = self.context.inSwitch
        previousInFunctionBody = self.context.inFunctionBody

        self.context.labelSet = {}
        self.context.inIteration = False
        self.context.inSwitch = False
        self.context.inFunctionBody = True

        while self.lookahead.type is not Token.EOF:
            if self.match('}'):
                break
            body.append(self.parseStatementListItem())

        self.expect('}')

        self.context.labelSet = previousLabelSet
        self.context.inIteration = previousInIteration
        self.context.inSwitch = previousInSwitch
        self.context.inFunctionBody = previousInFunctionBody

        return self.finalize(node, Node.BlockStatement(body))

    def validateParam(self, options, param, name):
        key = '$' + name
        if self.context.strict:
            if self.scanner.isRestrictedWord(name):
                options.stricted = param
                options.message = Messages.StrictParamName
            if key in options.paramSet:
                options.stricted = param
                options.message = Messages.StrictParamDupe
        elif not options.firstRestricted:
            if self.scanner.isRestrictedWord(name):
                options.firstRestricted = param
                options.message = Messages.StrictParamName
            elif self.scanner.isStrictModeReservedWord(name):
                options.firstRestricted = param
                options.message = Messages.StrictReservedWord
            elif key in options.paramSet:
                options.stricted = param
                options.message = Messages.StrictParamDupe

        options.paramSet[key] = True

    def parseRestElement(self, params):
        node = self.createNode()

        self.expect('...')
        arg = self.parsePattern(params)
        if self.match('='):
            self.throwError(Messages.DefaultRestParameter)
        if not self.match(')'):
            self.throwError(Messages.ParameterAfterRestParameter)

        return self.finalize(node, Node.RestElement(arg))

    def parseFormalParameter(self, options):
        params = []
        param = self.parseRestElement(params) if self.match('...') else self.parsePatternWithDefault(params)
        for p in params:
            self.validateParam(options, p, p.value)
        options.simple = options.simple and isinstance(param, Node.Identifier)
        options.params.append(param)

    def parseFormalParameters(self, firstRestricted=None):
        options = Params(
            simple=True,
            params=[],
            firstRestricted=firstRestricted
        )

        self.expect('(')
        if not self.match(')'):
            options.paramSet = {}
            while self.lookahead.type is not Token.EOF:
                self.parseFormalParameter(options)
                if self.match(')'):
                    break
                self.expect(',')
                if self.match(')'):
                    break
        self.expect(')')

        return Params(
            simple=options.simple,
            params=options.params,
            stricted=options.stricted,
            firstRestricted=options.firstRestricted,
            message=options.message
        )

    def matchAsyncFunction(self):
        match = self.matchContextualKeyword('async')
        if match:
            state = self.scanner.saveState()
            self.scanner.scanComments()
            next = self.scanner.lex()
            self.scanner.restoreState(state)

            match = (state.lineNumber == next.lineNumber) and (next.type is Token.Keyword) and (next.value == 'function')

        return match

    def parseFunctionDeclaration(self, identifierIsOptional=False):
        node = self.createNode()

        isAsync = self.matchContextualKeyword('async')
        if isAsync:
            self.nextToken()

        self.expectKeyword('function')

        isGenerator = False if isAsync else self.match('*')
        if isGenerator:
            self.nextToken()

        id = None
        firstRestricted = None

        if not identifierIsOptional or not self.match('('):
            token = self.lookahead
            id = self.parseVariableIdentifier()
            if self.context.strict:
                if self.scanner.isRestrictedWord(token.value):
                    self.tolerateUnexpectedToken(token, Messages.StrictFunctionName)
            else:
                if self.scanner.isRestrictedWord(token.value):
                    firstRestricted = token
                    message = Messages.StrictFunctionName
                elif self.scanner.isStrictModeReservedWord(token.value):
                    firstRestricted = token
                    message = Messages.StrictReservedWord

        previousAllowAwait = self.context.allowAwait
        previousAllowYield = self.context.allowYield
        self.context.allowAwait = isAsync
        self.context.allowYield = not isGenerator

        formalParameters = self.parseFormalParameters(firstRestricted)
        params = formalParameters.params
        stricted = formalParameters.stricted
        firstRestricted = formalParameters.firstRestricted
        if formalParameters.message:
            message = formalParameters.message

        previousStrict = self.context.strict
        previousAllowStrictDirective = self.context.allowStrictDirective
        self.context.allowStrictDirective = formalParameters.simple
        body = self.parseFunctionSourceElements()
        if self.context.strict and firstRestricted:
            self.throwUnexpectedToken(firstRestricted, message)
        if self.context.strict and stricted:
            self.tolerateUnexpectedToken(stricted, message)

        self.context.strict = previousStrict
        self.context.allowStrictDirective = previousAllowStrictDirective
        self.context.allowAwait = previousAllowAwait
        self.context.allowYield = previousAllowYield

        if isAsync:
            return self.finalize(node, Node.AsyncFunctionDeclaration(id, params, body))

        return self.finalize(node, Node.FunctionDeclaration(id, params, body, isGenerator))

    def parseFunctionExpression(self):
        node = self.createNode()

        isAsync = self.matchContextualKeyword('async')
        if isAsync:
            self.nextToken()

        self.expectKeyword('function')

        isGenerator = False if isAsync else self.match('*')
        if isGenerator:
            self.nextToken()

        id = None
        firstRestricted = None

        previousAllowAwait = self.context.allowAwait
        previousAllowYield = self.context.allowYield
        self.context.allowAwait = isAsync
        self.context.allowYield = not isGenerator

        if not self.match('('):
            token = self.lookahead
            id = self.parseIdentifierName() if not self.context.strict and not isGenerator and self.matchKeyword('yield') else self.parseVariableIdentifier()
            if self.context.strict:
                if self.scanner.isRestrictedWord(token.value):
                    self.tolerateUnexpectedToken(token, Messages.StrictFunctionName)
            else:
                if self.scanner.isRestrictedWord(token.value):
                    firstRestricted = token
                    message = Messages.StrictFunctionName
                elif self.scanner.isStrictModeReservedWord(token.value):
                    firstRestricted = token
                    message = Messages.StrictReservedWord

        formalParameters = self.parseFormalParameters(firstRestricted)
        params = formalParameters.params
        stricted = formalParameters.stricted
        firstRestricted = formalParameters.firstRestricted
        if formalParameters.message:
            message = formalParameters.message

        previousStrict = self.context.strict
        previousAllowStrictDirective = self.context.allowStrictDirective
        self.context.allowStrictDirective = formalParameters.simple
        body = self.parseFunctionSourceElements()
        if self.context.strict and firstRestricted:
            self.throwUnexpectedToken(firstRestricted, message)
        if self.context.strict and stricted:
            self.tolerateUnexpectedToken(stricted, message)
        self.context.strict = previousStrict
        self.context.allowStrictDirective = previousAllowStrictDirective
        self.context.allowAwait = previousAllowAwait
        self.context.allowYield = previousAllowYield

        if isAsync:
            return self.finalize(node, Node.AsyncFunctionExpression(id, params, body))

        return self.finalize(node, Node.FunctionExpression(id, params, body, isGenerator))

    # https://tc39.github.io/ecma262/#sec-directive-prologues-and-the-use-strict-directive

    def parseDirective(self):
        token = self.lookahead

        node = self.createNode()
        expr = self.parseExpression()
        directive = self.getTokenRaw(token)[1:-1] if expr.type is Syntax.Literal else None
        self.consumeSemicolon()

        return self.finalize(node, Node.Directive(expr, directive) if directive else Node.ExpressionStatement(expr))

    def parseDirectivePrologues(self):
        firstRestricted = None

        body = []
        while True:
            token = self.lookahead
            if token.type is not Token.StringLiteral:
                break

            statement = self.parseDirective()
            body.append(statement)
            directive = statement.directive
            if not isinstance(directive, basestring):
                break

            if directive == 'use strict':
                self.context.strict = True
                if firstRestricted:
                    self.tolerateUnexpectedToken(firstRestricted, Messages.StrictOctalLiteral)
                if not self.context.allowStrictDirective:
                    self.tolerateUnexpectedToken(token, Messages.IllegalLanguageModeDirective)
            else:
                if not firstRestricted and token.octal:
                    firstRestricted = token

        return body

    # https://tc39.github.io/ecma262/#sec-method-definitions

    def qualifiedPropertyName(self, token):
        typ = token.type
        if typ in (
            Token.Identifier,
            Token.StringLiteral,
            Token.BooleanLiteral,
            Token.NullLiteral,
            Token.NumericLiteral,
            Token.Keyword,
        ):
            return True
        elif typ is Token.Punctuator:
            return token.value == '['
        return False

    def parseGetterMethod(self):
        node = self.createNode()

        isGenerator = False
        previousAllowYield = self.context.allowYield
        self.context.allowYield = not isGenerator
        formalParameters = self.parseFormalParameters()
        if len(formalParameters.params) > 0:
            self.tolerateError(Messages.BadGetterArity)
        method = self.parsePropertyMethod(formalParameters)
        self.context.allowYield = previousAllowYield

        return self.finalize(node, Node.FunctionExpression(None, formalParameters.params, method, isGenerator))

    def parseSetterMethod(self):
        node = self.createNode()

        isGenerator = False
        previousAllowYield = self.context.allowYield
        self.context.allowYield = not isGenerator
        formalParameters = self.parseFormalParameters()
        if len(formalParameters.params) != 1:
            self.tolerateError(Messages.BadSetterArity)
        elif isinstance(formalParameters.params[0], Node.RestElement):
            self.tolerateError(Messages.BadSetterRestParameter)
        method = self.parsePropertyMethod(formalParameters)
        self.context.allowYield = previousAllowYield

        return self.finalize(node, Node.FunctionExpression(None, formalParameters.params, method, isGenerator))

    def parseGeneratorMethod(self):
        node = self.createNode()

        isGenerator = True
        previousAllowYield = self.context.allowYield

        self.context.allowYield = True
        params = self.parseFormalParameters()
        self.context.allowYield = False
        method = self.parsePropertyMethod(params)
        self.context.allowYield = previousAllowYield

        return self.finalize(node, Node.FunctionExpression(None, params.params, method, isGenerator))

    # https://tc39.github.io/ecma262/#sec-generator-function-definitions

    def isStartOfExpression(self):
        start = True

        value = self.lookahead.value
        typ = self.lookahead.type
        if typ is Token.Punctuator:
            start = value in ('[', '(', '{', '+', '-', '!', '~', '++', '--', '/', '/=')  # regular expression literal )

        elif typ is Token.Keyword:
            start = value in ('class', 'delete', 'function', 'let', 'new', 'super', 'this', 'typeof', 'void', 'yield')

        return start

    def parseYieldExpression(self):
        node = self.createNode()
        self.expectKeyword('yield')

        argument = None
        delegate = False
        if not self.hasLineTerminator:
            previousAllowYield = self.context.allowYield
            self.context.allowYield = False
            delegate = self.match('*')
            if delegate:
                self.nextToken()
                argument = self.parseAssignmentExpression()
            elif self.isStartOfExpression():
                argument = self.parseAssignmentExpression()
            self.context.allowYield = previousAllowYield

        return self.finalize(node, Node.YieldExpression(argument, delegate))

    # https://tc39.github.io/ecma262/#sec-class-definitions

    def parseClassElement(self, hasConstructor):
        token = self.lookahead
        node = self.createNode()

        kind = ''
        key = None
        value = None
        computed = False
        isStatic = False
        isAsync = False

        if self.match('*'):
            self.nextToken()

        else:
            computed = self.match('[')
            key = self.parseObjectPropertyKey()
            id = key
            if id.name == 'static' and (self.qualifiedPropertyName(self.lookahead) or self.match('*')):
                token = self.lookahead
                isStatic = True
                computed = self.match('[')
                if self.match('*'):
                    self.nextToken()
                else:
                    key = self.parseObjectPropertyKey()
            if token.type is Token.Identifier and not self.hasLineTerminator and token.value == 'async':
                punctuator = self.lookahead.value
                if punctuator != ':' and punctuator != '(' and punctuator != '*':
                    isAsync = True
                    token = self.lookahead
                    key = self.parseObjectPropertyKey()
                    if token.type is Token.Identifier and token.value == 'constructor':
                        self.tolerateUnexpectedToken(token, Messages.ConstructorIsAsync)

        lookaheadPropertyKey = self.qualifiedPropertyName(self.lookahead)
        if token.type is Token.Identifier:
            if token.value == 'get' and lookaheadPropertyKey:
                kind = 'get'
                computed = self.match('[')
                key = self.parseObjectPropertyKey()
                self.context.allowYield = False
                value = self.parseGetterMethod()
            elif token.value == 'set' and lookaheadPropertyKey:
                kind = 'set'
                computed = self.match('[')
                key = self.parseObjectPropertyKey()
                value = self.parseSetterMethod()
            elif self.config.classProperties and not self.match('('):
                kind = 'init'
                id = self.finalize(node, Node.Identifier(token.value))
                if self.match('='):
                    self.nextToken()
                    value = self.parseAssignmentExpression()

        elif token.type is Token.Punctuator and token.value == '*' and lookaheadPropertyKey:
            kind = 'method'
            computed = self.match('[')
            key = self.parseObjectPropertyKey()
            value = self.parseGeneratorMethod()

        if not kind and key and self.match('('):
            kind = 'method'
            value = self.parsePropertyMethodAsyncFunction() if isAsync else self.parsePropertyMethodFunction()

        if not kind:
            self.throwUnexpectedToken(self.lookahead)

        if not computed:
            if isStatic and self.isPropertyKey(key, 'prototype'):
                self.throwUnexpectedToken(token, Messages.StaticPrototype)
            if not isStatic and self.isPropertyKey(key, 'constructor'):
                if kind != 'method' or (value and value.generator):
                    self.throwUnexpectedToken(token, Messages.ConstructorSpecialMethod)
                if hasConstructor.value:
                    self.throwUnexpectedToken(token, Messages.DuplicateConstructor)
                else:
                    hasConstructor.value = True
                kind = 'constructor'

        if kind in ('constructor', 'method', 'get', 'set'):
            return self.finalize(node, Node.MethodDefinition(key, computed, value, kind, isStatic))

        else:
            return self.finalize(node, Node.FieldDefinition(key, computed, value, kind, isStatic))

    def parseClassElementList(self):
        body = []
        hasConstructor = Value(False)

        self.expect('{')
        while not self.match('}'):
            if self.match(';'):
                self.nextToken()
            else:
                body.append(self.parseClassElement(hasConstructor))
        self.expect('}')

        return body

    def parseClassBody(self):
        node = self.createNode()
        elementList = self.parseClassElementList()

        return self.finalize(node, Node.ClassBody(elementList))

    def parseClassDeclaration(self, identifierIsOptional=False):
        node = self.createNode()

        previousStrict = self.context.strict
        self.context.strict = True
        self.expectKeyword('class')

        id = None if identifierIsOptional and self.lookahead.type is not Token.Identifier else self.parseVariableIdentifier()
        superClass = None
        if self.matchKeyword('extends'):
            self.nextToken()
            superClass = self.isolateCoverGrammar(self.parseLeftHandSideExpressionAllowCall)
        classBody = self.parseClassBody()
        self.context.strict = previousStrict

        return self.finalize(node, Node.ClassDeclaration(id, superClass, classBody))

    def parseClassExpression(self):
        node = self.createNode()

        previousStrict = self.context.strict
        self.context.strict = True
        self.expectKeyword('class')
        id = self.parseVariableIdentifier() if self.lookahead.type is Token.Identifier else None
        superClass = None
        if self.matchKeyword('extends'):
            self.nextToken()
            superClass = self.isolateCoverGrammar(self.parseLeftHandSideExpressionAllowCall)
        classBody = self.parseClassBody()
        self.context.strict = previousStrict

        return self.finalize(node, Node.ClassExpression(id, superClass, classBody))

    # https://tc39.github.io/ecma262/#sec-scripts
    # https://tc39.github.io/ecma262/#sec-modules

    def parseModule(self):
        self.context.strict = True
        self.context.isModule = True
        self.scanner.isModule = True
        node = self.createNode()
        body = self.parseDirectivePrologues()
        while self.lookahead.type is not Token.EOF:
            body.append(self.parseStatementListItem())
        return self.finalize(node, Node.Module(body))

    def parseScript(self):
        node = self.createNode()
        body = self.parseDirectivePrologues()
        while self.lookahead.type is not Token.EOF:
            body.append(self.parseStatementListItem())
        return self.finalize(node, Node.Script(body))

    # https://tc39.github.io/ecma262/#sec-imports

    def parseModuleSpecifier(self):
        node = self.createNode()

        if self.lookahead.type is not Token.StringLiteral:
            self.throwError(Messages.InvalidModuleSpecifier)

        token = self.nextToken()
        raw = self.getTokenRaw(token)
        return self.finalize(node, Node.Literal(token.value, raw))

    # import {<foo as bar>} ...
    def parseImportSpecifier(self):
        node = self.createNode()

        if self.lookahead.type is Token.Identifier:
            imported = self.parseVariableIdentifier()
            local = imported
            if self.matchContextualKeyword('as'):
                self.nextToken()
                local = self.parseVariableIdentifier()
        else:
            imported = self.parseIdentifierName()
            local = imported
            if self.matchContextualKeyword('as'):
                self.nextToken()
                local = self.parseVariableIdentifier()
            else:
                self.throwUnexpectedToken(self.nextToken())

        return self.finalize(node, Node.ImportSpecifier(local, imported))

    # {foo, bar as bas
    def parseNamedImports(self):
        self.expect('{')
        specifiers = []
        while not self.match('}'):
            specifiers.append(self.parseImportSpecifier())
            if not self.match('}'):
                self.expect(',')
        self.expect('}')

        return specifiers

    # import <foo> ...
    def parseImportDefaultSpecifier(self):
        node = self.createNode()
        local = self.parseIdentifierName()
        return self.finalize(node, Node.ImportDefaultSpecifier(local))

    # import <* as foo> ...
    def parseImportNamespaceSpecifier(self):
        node = self.createNode()

        self.expect('*')
        if not self.matchContextualKeyword('as'):
            self.throwError(Messages.NoAsAfterImportNamespace)
        self.nextToken()
        local = self.parseIdentifierName()

        return self.finalize(node, Node.ImportNamespaceSpecifier(local))

    def parseImportDeclaration(self):
        if self.context.inFunctionBody:
            self.throwError(Messages.IllegalImportDeclaration)

        node = self.createNode()
        self.expectKeyword('import')

        specifiers = []
        if self.lookahead.type is Token.StringLiteral:
            # import 'foo'
            src = self.parseModuleSpecifier()
        else:
            if self.match('{'):
                # import {bar
                specifiers.extend(self.parseNamedImports())
            elif self.match('*'):
                # import * as foo
                specifiers.append(self.parseImportNamespaceSpecifier())
            elif self.isIdentifierName(self.lookahead) and not self.matchKeyword('default'):
                # import foo
                specifiers.append(self.parseImportDefaultSpecifier())
                if self.match(','):
                    self.nextToken()
                    if self.match('*'):
                        # import foo, * as foo
                        specifiers.append(self.parseImportNamespaceSpecifier())
                    elif self.match('{'):
                        # import foo, {bar
                        specifiers.extend(self.parseNamedImports())
                    else:
                        self.throwUnexpectedToken(self.lookahead)
            else:
                self.throwUnexpectedToken(self.nextToken())

            if not self.matchContextualKeyword('from'):
                message = Messages.UnexpectedToken if self.lookahead.value else Messages.MissingFromClause
                self.throwError(message, self.lookahead.value)
            self.nextToken()
            src = self.parseModuleSpecifier()
        self.consumeSemicolon()

        return self.finalize(node, Node.ImportDeclaration(specifiers, src))

    # https://tc39.github.io/ecma262/#sec-exports

    def parseExportSpecifier(self):
        node = self.createNode()

        local = self.parseIdentifierName()
        exported = local
        if self.matchContextualKeyword('as'):
            self.nextToken()
            exported = self.parseIdentifierName()

        return self.finalize(node, Node.ExportSpecifier(local, exported))

    def parseExportDefaultSpecifier(self):
        node = self.createNode()
        local = self.parseIdentifierName()
        return self.finalize(node, Node.ExportDefaultSpecifier(local))

    def parseExportDeclaration(self):
        if self.context.inFunctionBody:
            self.throwError(Messages.IllegalExportDeclaration)

        node = self.createNode()
        self.expectKeyword('export')

        if self.matchKeyword('default'):
            # export default ...
            self.nextToken()
            if self.matchKeyword('function'):
                # export default function foo (:
                # export default function (:
                declaration = self.parseFunctionDeclaration(True)
                exportDeclaration = self.finalize(node, Node.ExportDefaultDeclaration(declaration))
            elif self.matchKeyword('class'):
                # export default class foo {
                declaration = self.parseClassDeclaration(True)
                exportDeclaration = self.finalize(node, Node.ExportDefaultDeclaration(declaration))
            elif self.matchContextualKeyword('async'):
                # export default async function f (:
                # export default async function (:
                # export default async x => x
                declaration = self.parseFunctionDeclaration(True) if self.matchAsyncFunction() else self.parseAssignmentExpression()
                exportDeclaration = self.finalize(node, Node.ExportDefaultDeclaration(declaration))
            else:
                if self.matchContextualKeyword('from'):
                    self.throwError(Messages.UnexpectedToken, self.lookahead.value)
                # export default {}
                # export default []
                # export default (1 + 2)
                if self.match('{'):
                    declaration = self.parseObjectInitializer()
                elif self.match('['):
                    declaration = self.parseArrayInitializer()
                else:
                    declaration = self.parseAssignmentExpression()
                self.consumeSemicolon()
                exportDeclaration = self.finalize(node, Node.ExportDefaultDeclaration(declaration))

        elif self.match('*'):
            # export * from 'foo'
            self.nextToken()
            if not self.matchContextualKeyword('from'):
                message = Messages.UnexpectedToken if self.lookahead.value else Messages.MissingFromClause
                self.throwError(message, self.lookahead.value)
            self.nextToken()
            src = self.parseModuleSpecifier()
            self.consumeSemicolon()
            exportDeclaration = self.finalize(node, Node.ExportAllDeclaration(src))

        elif self.lookahead.type is Token.Keyword:
            # export var f = 1
            value = self.lookahead.value
            if value in (
                'let',
                'const',
            ):
                declaration = self.parseLexicalDeclaration(Params(inFor=False))
            elif value in (
                'var',
                'class',
                'function',
            ):
                declaration = self.parseStatementListItem()
            else:
                self.throwUnexpectedToken(self.lookahead)
            exportDeclaration = self.finalize(node, Node.ExportNamedDeclaration(declaration, [], None))

        elif self.matchAsyncFunction():
            declaration = self.parseFunctionDeclaration()
            exportDeclaration = self.finalize(node, Node.ExportNamedDeclaration(declaration, [], None))

        else:
            specifiers = []
            source = None
            isExportFromIdentifier = False

            expectSpecifiers = True
            if self.lookahead.type is Token.Identifier:
                specifiers.append(self.parseExportDefaultSpecifier())
                if self.match(','):
                    self.nextToken()
                else:
                    expectSpecifiers = False

            if expectSpecifiers:
                self.expect('{')
                while not self.match('}'):
                    isExportFromIdentifier = isExportFromIdentifier or self.matchKeyword('default')
                    specifiers.append(self.parseExportSpecifier())
                    if not self.match('}'):
                        self.expect(',')
                self.expect('}')

            if self.matchContextualKeyword('from'):
                # export {default} from 'foo'
                # export {foo} from 'foo'
                self.nextToken()
                source = self.parseModuleSpecifier()
                self.consumeSemicolon()
            elif isExportFromIdentifier:
                # export {default}; # missing fromClause
                message = Messages.UnexpectedToken if self.lookahead.value else Messages.MissingFromClause
                self.throwError(message, self.lookahead.value)
            else:
                # export {foo}
                self.consumeSemicolon()
            exportDeclaration = self.finalize(node, Node.ExportNamedDeclaration(None, specifiers, source))

        return exportDeclaration
