Skip to main content
Drupal API
User account menu
  • Log in

Breadcrumb

  1. Drupal Core 11.1.x
  2. Parser.php

class Parser

Same name in this branch
  1. 11.1.x vendor/open-telemetry/api/Baggage/Propagation/Parser.php \OpenTelemetry\API\Baggage\Propagation\Parser
  2. 11.1.x vendor/sebastian/cli-parser/src/Parser.php \SebastianBergmann\CliParser\Parser
  3. 11.1.x vendor/sebastian/diff/src/Parser.php \SebastianBergmann\Diff\Parser
  4. 11.1.x vendor/egulias/email-validator/src/Parser.php \Egulias\EmailValidator\Parser
  5. 11.1.x vendor/twig/twig/src/Parser.php \Twig\Parser
  6. 11.1.x vendor/symfony/css-selector/Parser/Parser.php \Symfony\Component\CssSelector\Parser\Parser
  7. 11.1.x vendor/symfony/yaml/Parser.php \Symfony\Component\Yaml\Parser
  8. 11.1.x vendor/mck89/peast/lib/Peast/Selector/Parser.php \Peast\Selector\Parser

Parser class

@author Marco Marchiò <marco.mm89@gmail.com>

Hierarchy

  • class \Peast\Syntax\ParserAbstract
    • class \Peast\Syntax\Parser extends \Peast\Syntax\ParserAbstract uses \JSX\Parser

Expanded class hierarchy of Parser

File

vendor/mck89/peast/lib/Peast/Syntax/Parser.php, line 17

Namespace

Peast\Syntax
View source
class Parser extends ParserAbstract {
    use JSX\Parser;
    
    //Identifier parsing mode constants
    
    /**
     * Everything is allowed as identifier, including keywords, null and booleans
     */
    const ID_ALLOW_ALL = 1;
    
    /**
     * Keywords, null and booleans are not allowed in any situation
     */
    const ID_ALLOW_NOTHING = 2;
    
    /**
     * Keywords, null and booleans are not allowed in any situation, future
     * reserved words are allowed if not in strict mode. Keywords that depend on
     * parser context are evaluated only if the parser context allows them.
     */
    const ID_MIXED = 3;
    
    /**
     * Binding identifier parsing rule
     * 
     * @var int 
     */
    protected static $bindingIdentifier = self::ID_MIXED;
    
    /**
     * Labelled identifier parsing rule
     * 
     * @var int 
     */
    protected static $labelledIdentifier = self::ID_MIXED;
    
    /**
     * Identifier reference parsing rule
     * 
     * @var int 
     */
    protected static $identifierReference = self::ID_MIXED;
    
    /**
     * Identifier name parsing rule
     * 
     * @var int 
     */
    protected static $identifierName = self::ID_ALLOW_ALL;
    
    /**
     * Imported binding parsing rule
     * 
     * @var int 
     */
    protected static $importedBinding = self::ID_ALLOW_NOTHING;
    
    /**
     * Assignment operators
     * 
     * @var array 
     */
    protected $assignmentOperators = array(
        "=",
        "+=",
        "-=",
        "*=",
        "/=",
        "%=",
        "<<=",
        ">>=",
        ">>>=",
        "&=",
        "^=",
        "|=",
        "**=",
        "&&=",
        "||=",
        "??=",
    );
    
    /**
     * Logical and binary operators
     * 
     * @var array 
     */
    protected $logicalBinaryOperators = array(
        "??" => 0,
        "||" => 0,
        "&&" => 1,
        "|" => 2,
        "^" => 3,
        "&" => 4,
        "===" => 5,
        "!==" => 5,
        "==" => 5,
        "!=" => 5,
        "<=" => 6,
        ">=" => 6,
        "<" => 6,
        ">" => 6,
        "instanceof" => 6,
        "in" => 6,
        ">>>" => 7,
        "<<" => 7,
        ">>" => 7,
        "+" => 8,
        "-" => 8,
        "*" => 9,
        "/" => 9,
        "%" => 9,
        "**" => 10,
    );
    
    /**
     * Unary operators
     * 
     * @var array 
     */
    protected $unaryOperators = array(
        "delete",
        "void",
        "typeof",
        "++",
        "--",
        "+",
        "-",
        "~",
        "!",
    );
    
    /**
     * Postfix operators
     * 
     * @var array 
     */
    protected $postfixOperators = array(
        "--",
        "++",
    );
    
    /**
     * Array of keywords that depends on a context property
     * 
     * @var array 
     */
    protected $contextKeywords = array(
        "yield" => "allowYield",
        "await" => "allowAwait",
    );
    
    /**
     * Initializes parser context
     * 
     * @return void
     */
    protected function initContext() {
        $context = array(
            "allowReturn" => false,
            "allowIn" => false,
            "allowYield" => false,
            "allowAwait" => false,
        );
        
        //If async/await is not enabled remove the
        
        //relative context properties
        if (!$this->features->asyncAwait) {
            unset($context["allowAwait"]);
            unset($this->contextKeywords["await"]);
        }
        $this->context = (object) $context;
    }
    
    /**
     * Post initialize operations
     * 
     * @return void
     */
    protected function postInit() {
        
        //Remove exponentiation operator if the feature
        
        //is not enabled
        if (!$this->features->exponentiationOperator) {
            Utils::removeArrayValue($this->assignmentOperators, "**=");
            unset($this->logicalBinaryOperators["**"]);
        }
        
        //Remove coalescing operator if the feature
        
        //is not enabled
        if (!$this->features->coalescingOperator) {
            unset($this->logicalBinaryOperators["??"]);
        }
        
        //Remove logical assignment operators if the
        
        //feature is not enabled
        if (!$this->features->logicalAssignmentOperators) {
            foreach (array(
                "&&=",
                "||=",
                "??=",
            ) as $op) {
                Utils::removeArrayValue($this->assignmentOperators, $op);
            }
        }
    }
    
    /**
     * Parses the source
     * 
     * @return Node\Program
     */
    public function parse() {
        if ($this->sourceType === \Peast\Peast::SOURCE_TYPE_MODULE) {
            $this->scanner
                ->setStrictMode(true);
            $body = $this->parseModuleItemList();
        }
        else {
            $body = $this->parseStatementList(true);
        }
        $node = $this->createNode("Program", $body ?: $this->scanner
            ->getPosition());
        $node->setSourceType($this->sourceType);
        if ($body) {
            $node->setBody($body);
        }
        $program = $this->completeNode($node);
        if ($this->scanner
            ->getToken()) {
            $this->error();
        }
        
        //Execute scanner end operations
        $this->scanner
            ->consumeEnd();
        
        //Emit the EndParsing event and pass the resulting program node as
        
        //event data
        $this->eventsEmitter && $this->eventsEmitter
            ->fire("EndParsing", array(
            $program,
        ));
        return $program;
    }
    
    /**
     * Converts an expression node to a pattern node
     * 
     * @param Node\Node $node The node to convert
     * 
     * @return Node\Node
     */
    protected function expressionToPattern($node) {
        if ($node instanceof Node\ArrayExpression) {
            $loc = $node->location;
            $elems = array();
            foreach ($node->getElements() as $elem) {
                $elems[] = $this->expressionToPattern($elem);
            }
            $retNode = $this->createNode("ArrayPattern", $loc->start);
            $retNode->setElements($elems);
            $this->completeNode($retNode, $loc->end);
        }
        elseif ($node instanceof Node\ObjectExpression) {
            $loc = $node->location;
            $props = array();
            foreach ($node->getProperties() as $prop) {
                $props[] = $this->expressionToPattern($prop);
            }
            $retNode = $this->createNode("ObjectPattern", $loc->start);
            $retNode->setProperties($props);
            $this->completeNode($retNode, $loc->end);
        }
        elseif ($node instanceof Node\Property) {
            $loc = $node->location;
            $retNode = $this->createNode("AssignmentProperty", $loc->start);
            // If it's a shorthand property convert the value to an assignment
            // pattern if necessary
            $value = $node->getValue();
            $key = $node->getKey();
            if ($value && $node->getShorthand() && !$value instanceof Node\AssignmentExpression && (!$value instanceof Node\Identifier || $key instanceof Node\Identifier && $key->getName() !== $value->getName())) {
                $loc = $node->location;
                $valNode = $this->createNode("AssignmentPattern", $loc->start);
                $valNode->setLeft($key);
                $valNode->setRight($value);
                $this->completeNode($valNode, $loc->end);
                $value = $valNode;
            }
            else {
                $value = $this->expressionToPattern($value);
            }
            $retNode->setValue($value);
            $retNode->setKey($key);
            $retNode->setMethod($node->getMethod());
            $retNode->setShorthand($node->getShorthand());
            $retNode->setComputed($node->getComputed());
            $this->completeNode($retNode, $loc->end);
        }
        elseif ($node instanceof Node\SpreadElement) {
            $loc = $node->location;
            $retNode = $this->createNode("RestElement", $loc->start);
            $retNode->setArgument($this->expressionToPattern($node->getArgument()));
            $this->completeNode($retNode, $loc->end);
        }
        elseif ($node instanceof Node\AssignmentExpression) {
            $loc = $node->location;
            $retNode = $this->createNode("AssignmentPattern", $loc->start);
            $retNode->setLeft($this->expressionToPattern($node->getLeft()));
            $retNode->setRight($node->getRight());
            $this->completeNode($retNode, $loc->end);
        }
        else {
            $retNode = $node;
        }
        return $retNode;
    }
    
    /**
     * Parses a statement list
     * 
     * @param bool $parseDirectivePrologues True to parse directive prologues
     * 
     * @return Node\Node[]|null
     */
    protected function parseStatementList($parseDirectivePrologues = false) {
        $items = array();
        
        //Get directive prologues and check if strict mode is present
        if ($parseDirectivePrologues) {
            $oldStrictMode = $this->scanner
                ->getStrictMode();
            if ($directives = $this->parseDirectivePrologues()) {
                $items = array_merge($items, $directives[0]);
                
                //If "use strict" is present enable scanner strict mode
                if (in_array("use strict", $directives[1])) {
                    $this->scanner
                        ->setStrictMode(true);
                }
            }
        }
        while ($item = $this->parseStatementListItem()) {
            $items[] = $item;
        }
        
        //Apply previous strict mode
        if ($parseDirectivePrologues) {
            $this->scanner
                ->setStrictMode($oldStrictMode);
        }
        return count($items) ? $items : null;
    }
    
    /**
     * Parses a statement list item
     * 
     * @return Node\Statement|Node\Declaration|null
     */
    protected function parseStatementListItem() {
        if ($declaration = $this->parseDeclaration()) {
            return $declaration;
        }
        elseif ($statement = $this->parseStatement()) {
            return $statement;
        }
        return null;
    }
    
    /**
     * Parses a statement
     * 
     * @return Node\Statement|null
     */
    protected function parseStatement() {
        
        //Here the token value is checked for performance so that functions won't be
        
        //called if not necessary
        $token = $this->scanner
            ->getToken();
        if (!$token) {
            return null;
        }
        $val = $token->value;
        if ($val === "{" && ($statement = $this->parseBlock())) {
            return $statement;
        }
        elseif ($val === "var" && ($statement = $this->parseVariableStatement())) {
            return $statement;
        }
        elseif ($val === ";" && ($statement = $this->parseEmptyStatement())) {
            return $statement;
        }
        elseif ($val === "if" && ($statement = $this->parseIfStatement())) {
            return $statement;
        }
        elseif (($val === "for" || $val === "while" || $val === "do" || $val === "switch") && ($statement = $this->parseBreakableStatement())) {
            return $statement;
        }
        elseif ($val == "continue" && ($statement = $this->parseContinueStatement())) {
            return $statement;
        }
        elseif ($val === "break" && ($statement = $this->parseBreakStatement())) {
            return $statement;
        }
        elseif ($this->context->allowReturn && $val === "return" && ($statement = $this->parseReturnStatement())) {
            return $statement;
        }
        elseif ($val === "with" && ($statement = $this->parseWithStatement())) {
            return $statement;
        }
        elseif ($val === "throw" && ($statement = $this->parseThrowStatement())) {
            return $statement;
        }
        elseif ($val === "try" && ($statement = $this->parseTryStatement())) {
            return $statement;
        }
        elseif ($val === "debugger" && ($statement = $this->parseDebuggerStatement())) {
            return $statement;
        }
        elseif ($statement = $this->parseLabelledStatement()) {
            return $statement;
        }
        elseif ($statement = $this->parseExpressionStatement()) {
            return $statement;
        }
        return null;
    }
    
    /**
     * Parses a declaration
     * 
     * @return Node\Declaration|null
     */
    protected function parseDeclaration() {
        
        //Here the token value is checked for performance so that functions won't be
        
        //called if not necessary
        $token = $this->scanner
            ->getToken();
        if (!$token) {
            return null;
        }
        $val = $token->value;
        if ($declaration = $this->parseFunctionOrGeneratorDeclaration()) {
            return $declaration;
        }
        elseif ($val === "class" && ($declaration = $this->parseClassDeclaration())) {
            return $declaration;
        }
        elseif (($val === "let" || $val === "const") && ($declaration = $this->isolateContext(array(
            "allowIn" => true,
        ), "parseLexicalDeclaration"))) {
            return $declaration;
        }
        return null;
    }
    
    /**
     * Parses a breakable statement
     * 
     * @return Node\Node|null
     */
    protected function parseBreakableStatement() {
        if ($statement = $this->parseIterationStatement()) {
            return $statement;
        }
        elseif ($statement = $this->parseSwitchStatement()) {
            return $statement;
        }
        return null;
    }
    
    /**
     * Parses a block statement
     * 
     * @return Node\BlockStatement|null
     */
    protected function parseBlock() {
        if ($token = $this->scanner
            ->consume("{")) {
            $statements = $this->parseStatementList();
            if ($this->scanner
                ->consume("}")) {
                $node = $this->createNode("BlockStatement", $token);
                if ($statements) {
                    $node->setBody($statements);
                }
                return $this->completeNode($node);
            }
            $this->error();
        }
        return null;
    }
    
    /**
     * Parses a module item list
     * 
     * @return Node\Node[]|null
     */
    protected function parseModuleItemList() {
        $items = array();
        while ($item = $this->parseModuleItem()) {
            $items[] = $item;
        }
        return count($items) ? $items : null;
    }
    
    /**
     * Parses an empty statement
     * 
     * @return Node\EmptyStatement|null
     */
    protected function parseEmptyStatement() {
        if ($token = $this->scanner
            ->consume(";")) {
            $node = $this->createNode("EmptyStatement", $token);
            return $this->completeNode($node);
        }
        return null;
    }
    
    /**
     * Parses a debugger statement
     * 
     * @return Node\DebuggerStatement|null
     */
    protected function parseDebuggerStatement() {
        if ($token = $this->scanner
            ->consume("debugger")) {
            $node = $this->createNode("DebuggerStatement", $token);
            $this->assertEndOfStatement();
            return $this->completeNode($node);
        }
        return null;
    }
    
    /**
     * Parses an if statement
     * 
     * @return Node\IfStatement|null
     */
    protected function parseIfStatement() {
        if ($token = $this->scanner
            ->consume("if")) {
            if ($this->scanner
                ->consume("(") && ($test = $this->isolateContext(array(
                "allowIn" => true,
            ), "parseExpression")) && $this->scanner
                ->consume(")") && (($consequent = $this->parseStatement()) || !$this->scanner
                ->getStrictMode() && ($consequent = $this->parseFunctionOrGeneratorDeclaration(false, false)))) {
                $node = $this->createNode("IfStatement", $token);
                $node->setTest($test);
                $node->setConsequent($consequent);
                if ($this->scanner
                    ->consume("else")) {
                    if (($alternate = $this->parseStatement()) || !$this->scanner
                        ->getStrictMode() && ($alternate = $this->parseFunctionOrGeneratorDeclaration(false, false))) {
                        $node->setAlternate($alternate);
                        return $this->completeNode($node);
                    }
                }
                else {
                    return $this->completeNode($node);
                }
            }
            $this->error();
        }
        return null;
    }
    
    /**
     * Parses a try-catch statement
     * 
     * @return Node\TryStatement|null
     */
    protected function parseTryStatement() {
        if ($token = $this->scanner
            ->consume("try")) {
            if ($block = $this->parseBlock()) {
                $node = $this->createNode("TryStatement", $token);
                $node->setBlock($block);
                if ($handler = $this->parseCatch()) {
                    $node->setHandler($handler);
                }
                if ($finalizer = $this->parseFinally()) {
                    $node->setFinalizer($finalizer);
                }
                if ($handler || $finalizer) {
                    return $this->completeNode($node);
                }
            }
            $this->error();
        }
        return null;
    }
    
    /**
     * Parses the catch block of a try-catch statement
     * 
     * @return Node\CatchClause|null
     */
    protected function parseCatch() {
        if ($token = $this->scanner
            ->consume("catch")) {
            $node = $this->createNode("CatchClause", $token);
            if ($this->scanner
                ->consume("(")) {
                if (!($param = $this->parseCatchParameter()) || !$this->scanner
                    ->consume(")")) {
                    $this->error();
                }
                $node->setParam($param);
            }
            elseif (!$this->features->optionalCatchBinding) {
                $this->error();
            }
            if (!($body = $this->parseBlock())) {
                $this->error();
            }
            $node->setBody($body);
            return $this->completeNode($node);
        }
        return null;
    }
    
    /**
     * Parses the catch parameter of a catch block in a try-catch statement
     * 
     * @return Node\Node|null
     */
    protected function parseCatchParameter() {
        if ($param = $this->parseIdentifier(static::$bindingIdentifier)) {
            return $param;
        }
        elseif ($param = $this->parseBindingPattern()) {
            return $param;
        }
        return null;
    }
    
    /**
     * Parses a finally block in a try-catch statement
     * 
     * @return Node\BlockStatement|null
     */
    protected function parseFinally() {
        if ($this->scanner
            ->consume("finally")) {
            if ($block = $this->parseBlock()) {
                return $block;
            }
            $this->error();
        }
        return null;
    }
    
    /**
     * Parses a continue statement
     * 
     * @return Node\ContinueStatement|null
     */
    protected function parseContinueStatement() {
        if ($token = $this->scanner
            ->consume("continue")) {
            $node = $this->createNode("ContinueStatement", $token);
            if ($this->scanner
                ->noLineTerminators() && ($label = $this->parseIdentifier(static::$labelledIdentifier))) {
                $node->setLabel($label);
                $this->assertEndOfStatement();
            }
            else {
                $this->scanner
                    ->consume(";");
            }
            return $this->completeNode($node);
        }
        return null;
    }
    
    /**
     * Parses a break statement
     * 
     * @return Node\BreakStatement|null
     */
    protected function parseBreakStatement() {
        if ($token = $this->scanner
            ->consume("break")) {
            $node = $this->createNode("BreakStatement", $token);
            if ($this->scanner
                ->noLineTerminators() && ($label = $this->parseIdentifier(static::$labelledIdentifier))) {
                $node->setLabel($label);
                $this->assertEndOfStatement();
            }
            else {
                $this->scanner
                    ->consume(";");
            }
            return $this->completeNode($node);
        }
        return null;
    }
    
    /**
     * Parses a return statement
     * 
     * @return Node\ReturnStatement|null
     */
    protected function parseReturnStatement() {
        if ($token = $this->scanner
            ->consume("return")) {
            $node = $this->createNode("ReturnStatement", $token);
            if ($this->scanner
                ->noLineTerminators()) {
                $argument = $this->isolateContext(array(
                    "allowIn" => true,
                ), "parseExpression");
                if ($argument) {
                    $node->setArgument($argument);
                }
            }
            $this->assertEndOfStatement();
            return $this->completeNode($node);
        }
        return null;
    }
    
    /**
     * Parses a labelled statement
     * 
     * @return Node\LabeledStatement|null
     */
    protected function parseLabelledStatement() {
        if ($label = $this->parseIdentifier(static::$labelledIdentifier, ":")) {
            $this->scanner
                ->consume(":");
            if (($body = $this->parseStatement()) || ($body = $this->parseFunctionOrGeneratorDeclaration(false, false))) {
                
                //Labelled functions are not allowed in strict mode
                if ($body instanceof Node\FunctionDeclaration && $this->scanner
                    ->getStrictMode()) {
                    $this->error("Labelled functions are not allowed in strict mode");
                }
                $node = $this->createNode("LabeledStatement", $label);
                $node->setLabel($label);
                $node->setBody($body);
                return $this->completeNode($node);
            }
            $this->error();
        }
        return null;
    }
    
    /**
     * Parses a throw statement
     * 
     * @return Node\ThrowStatement|null
     */
    protected function parseThrowStatement() {
        if ($token = $this->scanner
            ->consume("throw")) {
            if ($this->scanner
                ->noLineTerminators() && ($argument = $this->isolateContext(array(
                "allowIn" => true,
            ), "parseExpression"))) {
                $this->assertEndOfStatement();
                $node = $this->createNode("ThrowStatement", $token);
                $node->setArgument($argument);
                return $this->completeNode($node);
            }
            $this->error();
        }
        return null;
    }
    
    /**
     * Parses a with statement
     * 
     * @return Node\WithStatement|null
     */
    protected function parseWithStatement() {
        if ($token = $this->scanner
            ->consume("with")) {
            if ($this->scanner
                ->consume("(") && ($object = $this->isolateContext(array(
                "allowIn" => true,
            ), "parseExpression")) && $this->scanner
                ->consume(")") && ($body = $this->parseStatement())) {
                $node = $this->createNode("WithStatement", $token);
                $node->setObject($object);
                $node->setBody($body);
                return $this->completeNode($node);
            }
            $this->error();
        }
        return null;
    }
    
    /**
     * Parses a switch statement
     * 
     * @return Node\SwitchStatement|null
     */
    protected function parseSwitchStatement() {
        if ($token = $this->scanner
            ->consume("switch")) {
            if ($this->scanner
                ->consume("(") && ($discriminant = $this->isolateContext(array(
                "allowIn" => true,
            ), "parseExpression")) && $this->scanner
                ->consume(")") && ($cases = $this->parseCaseBlock()) !== null) {
                $node = $this->createNode("SwitchStatement", $token);
                $node->setDiscriminant($discriminant);
                $node->setCases($cases);
                return $this->completeNode($node);
            }
            $this->error();
        }
        return null;
    }
    
    /**
     * Parses the content of a switch statement
     * 
     * @return Node\SwitchCase[]|null
     */
    protected function parseCaseBlock() {
        if ($this->scanner
            ->consume("{")) {
            $parsedCasesAll = array(
                $this->parseCaseClauses(),
                $this->parseDefaultClause(),
                $this->parseCaseClauses(),
            );
            if ($this->scanner
                ->consume("}")) {
                $cases = array();
                foreach ($parsedCasesAll as $parsedCases) {
                    if ($parsedCases) {
                        if (is_array($parsedCases)) {
                            $cases = array_merge($cases, $parsedCases);
                        }
                        else {
                            $cases[] = $parsedCases;
                        }
                    }
                }
                return $cases;
            }
            elseif ($this->parseDefaultClause()) {
                $this->error("Multiple default clause in switch statement");
            }
            else {
                $this->error();
            }
        }
        return null;
    }
    
    /**
     * Parses cases in a switch statement
     * 
     * @return Node\SwitchCase[]|null
     */
    protected function parseCaseClauses() {
        $cases = array();
        while ($case = $this->parseCaseClause()) {
            $cases[] = $case;
        }
        return count($cases) ? $cases : null;
    }
    
    /**
     * Parses a case in a switch statement
     * 
     * @return Node\SwitchCase|null
     */
    protected function parseCaseClause() {
        if ($token = $this->scanner
            ->consume("case")) {
            if (($test = $this->isolateContext(array(
                "allowIn" => true,
            ), "parseExpression")) && $this->scanner
                ->consume(":")) {
                $node = $this->createNode("SwitchCase", $token);
                $node->setTest($test);
                if ($consequent = $this->parseStatementList()) {
                    $node->setConsequent($consequent);
                }
                return $this->completeNode($node);
            }
            $this->error();
        }
        return null;
    }
    
    /**
     * Parses default case in a switch statement
     * 
     * @return Node\SwitchCase|null
     */
    protected function parseDefaultClause() {
        if ($token = $this->scanner
            ->consume("default")) {
            if ($this->scanner
                ->consume(":")) {
                $node = $this->createNode("SwitchCase", $token);
                if ($consequent = $this->parseStatementList()) {
                    $node->setConsequent($consequent);
                }
                return $this->completeNode($node);
            }
            $this->error();
        }
        return null;
    }
    
    /**
     * Parses an expression statement
     * 
     * @return Node\ExpressionStatement|null
     */
    protected function parseExpressionStatement() {
        $lookaheadTokens = array(
            "{",
            "function",
            "class",
            array(
                "let",
                "[",
            ),
        );
        if ($this->features->asyncAwait) {
            array_splice($lookaheadTokens, 3, 0, array(
                array(
                    "async",
                    true,
                ),
            ));
        }
        if (!$this->scanner
            ->isBefore($lookaheadTokens, true) && ($expression = $this->isolateContext(array(
            "allowIn" => true,
        ), "parseExpression"))) {
            $this->assertEndOfStatement();
            $node = $this->createNode("ExpressionStatement", $expression);
            $node->setExpression($expression);
            return $this->completeNode($node);
        }
        return null;
    }
    
    /**
     * Parses a do-while statement
     * 
     * @return Node\DoWhileStatement|null
     */
    protected function parseDoWhileStatement() {
        if ($token = $this->scanner
            ->consume("do")) {
            if (($body = $this->parseStatement()) && $this->scanner
                ->consume("while") && $this->scanner
                ->consume("(") && ($test = $this->isolateContext(array(
                "allowIn" => true,
            ), "parseExpression")) && $this->scanner
                ->consume(")")) {
                $node = $this->createNode("DoWhileStatement", $token);
                $node->setBody($body);
                $node->setTest($test);
                $node = $this->completeNode($node);
                $this->scanner
                    ->consume(";");
                return $node;
            }
            $this->error();
        }
        return null;
    }
    
    /**
     * Parses a while statement
     * 
     * @return Node\WhileStatement|null
     */
    protected function parseWhileStatement() {
        if ($token = $this->scanner
            ->consume("while")) {
            if ($this->scanner
                ->consume("(") && ($test = $this->isolateContext(array(
                "allowIn" => true,
            ), "parseExpression")) && $this->scanner
                ->consume(")") && ($body = $this->parseStatement())) {
                $node = $this->createNode("WhileStatement", $token);
                $node->setTest($test);
                $node->setBody($body);
                return $this->completeNode($node);
            }
            $this->error();
        }
        return null;
    }
    
    /**
     * Parses a for(var ...) statement
     * 
     * @param Token $forToken Token that corresponds to the "for" keyword
     * 
     * @return Node\Node|null
     */
    protected function parseForVarStatement($forToken) {
        if (!($varToken = $this->scanner
            ->consume("var"))) {
            return null;
        }
        $state = $this->scanner
            ->getState();
        if (($decl = $this->isolateContext(array(
            "allowIn" => false,
        ), "parseVariableDeclarationList")) && ($varEndPosition = $this->scanner
            ->getPosition()) && $this->scanner
            ->consume(";")) {
            $init = $this->createNode("VariableDeclaration", $varToken);
            $init->setKind($init::KIND_VAR);
            $init->setDeclarations($decl);
            $init = $this->completeNode($init, $varEndPosition);
            $test = $this->isolateContext(array(
                "allowIn" => true,
            ), "parseExpression");
            if ($this->scanner
                ->consume(";")) {
                $update = $this->isolateContext(array(
                    "allowIn" => true,
                ), "parseExpression");
                if ($this->scanner
                    ->consume(")") && ($body = $this->parseStatement())) {
                    $node = $this->createNode("ForStatement", $forToken);
                    $node->setInit($init);
                    $node->setTest($test);
                    $node->setUpdate($update);
                    $node->setBody($body);
                    return $this->completeNode($node);
                }
            }
        }
        else {
            $this->scanner
                ->setState($state);
            if ($decl = $this->parseForBinding()) {
                $init = null;
                if ($this->features->forInInitializer && $decl->getId()
                    ->getType() === "Identifier") {
                    $init = $this->parseInitializer();
                }
                if ($init) {
                    $decl->setInit($init);
                    $decl->location->end = $init->location->end;
                }
                $left = $this->createNode("VariableDeclaration", $varToken);
                $left->setKind($left::KIND_VAR);
                $left->setDeclarations(array(
                    $decl,
                ));
                $left = $this->completeNode($left);
                if ($this->scanner
                    ->consume("in")) {
                    if ($init && $this->scanner
                        ->getStrictMode()) {
                        $this->error("For-in variable initializer not allowed in " . "strict mode");
                    }
                    if (($right = $this->isolateContext(array(
                        "allowIn" => true,
                    ), "parseExpression")) && $this->scanner
                        ->consume(")") && ($body = $this->parseStatement())) {
                        $node = $this->createNode("ForInStatement", $forToken);
                        $node->setLeft($left);
                        $node->setRight($right);
                        $node->setBody($body);
                        return $this->completeNode($node);
                    }
                }
                elseif (!$init && $this->scanner
                    ->consume("of")) {
                    if (($right = $this->isolateContext(array(
                        "allowIn" => true,
                    ), "parseAssignmentExpression")) && $this->scanner
                        ->consume(")") && ($body = $this->parseStatement())) {
                        $node = $this->createNode("ForOfStatement", $forToken);
                        $node->setLeft($left);
                        $node->setRight($right);
                        $node->setBody($body);
                        return $this->completeNode($node);
                    }
                }
            }
        }
        $this->error();
    }
    
    /**
     * Parses a for(let ...) or for(const ...) statement
     * 
     * @param Token $forToken Token that corresponds to the "for" keyword
     * 
     * @return Node\Node|null
     */
    protected function parseForLetConstStatement($forToken) {
        $afterBracketState = $this->scanner
            ->getState();
        if (!($init = $this->parseForDeclaration())) {
            return null;
        }
        if ($this->scanner
            ->consume("in")) {
            if (($right = $this->isolateContext(array(
                "allowIn" => true,
            ), "parseExpression")) && $this->scanner
                ->consume(")") && ($body = $this->parseStatement())) {
                $node = $this->createNode("ForInStatement", $forToken);
                $node->setLeft($init);
                $node->setRight($right);
                $node->setBody($body);
                return $this->completeNode($node);
            }
        }
        elseif ($this->scanner
            ->consume("of")) {
            if (($right = $this->isolateContext(array(
                "allowIn" => true,
            ), "parseAssignmentExpression")) && $this->scanner
                ->consume(")") && ($body = $this->parseStatement())) {
                $node = $this->createNode("ForOfStatement", $forToken);
                $node->setLeft($init);
                $node->setRight($right);
                $node->setBody($body);
                return $this->completeNode($node);
            }
        }
        else {
            $this->scanner
                ->setState($afterBracketState);
            if ($init = $this->isolateContext(array(
                "allowIn" => false,
            ), "parseLexicalDeclaration")) {
                $test = $this->isolateContext(array(
                    "allowIn" => true,
                ), "parseExpression");
                if ($this->scanner
                    ->consume(";")) {
                    $update = $this->isolateContext(array(
                        "allowIn" => true,
                    ), "parseExpression");
                    if ($this->scanner
                        ->consume(")") && ($body = $this->parseStatement())) {
                        $node = $this->createNode("ForStatement", $forToken);
                        $node->setInit($init);
                        $node->setTest($test);
                        $node->setUpdate($update);
                        $node->setBody($body);
                        return $this->completeNode($node);
                    }
                }
            }
        }
        $this->error();
    }
    
    /**
     * Parses a for statement that does not start with var, let or const
     * 
     * @param Token $forToken Token that corresponds to the "for" keyword
     * @param bool  $hasAwait True if "for" is followed by "await"
     * 
     * @return Node\Node|null
     */
    protected function parseForNotVarLetConstStatement($forToken, $hasAwait) {
        $state = $this->scanner
            ->getState();
        $notBeforeSB = !$this->scanner
            ->isBefore(array(
            array(
                "let",
                "[",
            ),
        ), true);
        if ($notBeforeSB && (($init = $this->isolateContext(array(
            "allowIn" => false,
        ), "parseExpression")) || true) && $this->scanner
            ->consume(";")) {
            $test = $this->isolateContext(array(
                "allowIn" => true,
            ), "parseExpression");
            if ($this->scanner
                ->consume(";")) {
                $update = $this->isolateContext(array(
                    "allowIn" => true,
                ), "parseExpression");
                if ($this->scanner
                    ->consume(")") && ($body = $this->parseStatement())) {
                    $node = $this->createNode("ForStatement", $forToken);
                    $node->setInit($init);
                    $node->setTest($test);
                    $node->setUpdate($update);
                    $node->setBody($body);
                    return $this->completeNode($node);
                }
            }
        }
        else {
            $this->scanner
                ->setState($state);
            $beforeLetAsyncOf = $this->scanner
                ->isBefore(array(
                "let",
                array(
                    "async",
                    "of",
                ),
            ), true);
            $left = $this->parseLeftHandSideExpression();
            if ($left && $left->getType() === "ChainExpression") {
                $this->error("Optional chain can't appear in left-hand side");
            }
            $left = $this->expressionToPattern($left);
            if ($notBeforeSB && $left && $this->scanner
                ->consume("in")) {
                if (($right = $this->isolateContext(array(
                    "allowIn" => true,
                ), "parseExpression")) && $this->scanner
                    ->consume(")") && ($body = $this->parseStatement())) {
                    $node = $this->createNode("ForInStatement", $forToken);
                    $node->setLeft($left);
                    $node->setRight($right);
                    $node->setBody($body);
                    return $this->completeNode($node);
                }
            }
            elseif (($hasAwait || !$beforeLetAsyncOf) && $left && $this->scanner
                ->consume("of")) {
                if (($right = $this->isolateContext(array(
                    "allowIn" => true,
                ), "parseAssignmentExpression")) && $this->scanner
                    ->consume(")") && ($body = $this->parseStatement())) {
                    $node = $this->createNode("ForOfStatement", $forToken);
                    $node->setLeft($left);
                    $node->setRight($right);
                    $node->setBody($body);
                    return $this->completeNode($node);
                }
            }
        }
        $this->error();
    }
    
    /**
     * Parses do-while, while, for, for-in and for-of statements
     * 
     * @return Node\Node|null
     */
    protected function parseIterationStatement() {
        if ($node = $this->parseWhileStatement()) {
            return $node;
        }
        elseif ($node = $this->parseDoWhileStatement()) {
            return $node;
        }
        elseif ($startForToken = $this->scanner
            ->consume("for")) {
            $forAwait = false;
            if ($this->features->asyncIterationGenerators && $this->context->allowAwait && $this->scanner
                ->consume("await")) {
                $forAwait = true;
            }
            if ($this->scanner
                ->consume("(") && (($node = $this->parseForVarStatement($startForToken)) || ($node = $this->parseForLetConstStatement($startForToken)) || ($node = $this->parseForNotVarLetConstStatement($startForToken, $forAwait)))) {
                if ($forAwait) {
                    if (!$node instanceof Node\ForOfStatement) {
                        $this->error("Async iteration is allowed only with for-of statements", $startForToken->location->start);
                    }
                    $node->setAwait(true);
                }
                return $node;
            }
            $this->error();
        }
        return null;
    }
    
    /**
     * Checks if an async function can start from the current position. Returns
     * the async token or null if not found
     *
     * @param bool $checkFn If false it won't check if the async keyword is
     *                      followed by "function"
     *
     * @return Token
     */
    protected function checkAsyncFunctionStart($checkFn = true) {
        return ($asyncToken = $this->scanner
            ->getToken()) && $asyncToken->value === "async" && (!$checkFn || ($nextToken = $this->scanner
            ->getNextToken()) && $nextToken->value === "function") && $this->scanner
            ->noLineTerminators(true) ? $asyncToken : null;
    }
    
    /**
     * Parses function or generator declaration
     * 
     * @param bool $default        Default mode
     * @param bool $allowGenerator True to allow parsing of generators
     * 
     * @return Node\FunctionDeclaration|null
     */
    protected function parseFunctionOrGeneratorDeclaration($default = false, $allowGenerator = true) {
        $async = null;
        if ($this->features->asyncAwait && ($async = $this->checkAsyncFunctionStart())) {
            $this->scanner
                ->consumeToken();
            if (!$this->features->asyncIterationGenerators) {
                $allowGenerator = false;
            }
        }
        if ($token = $this->scanner
            ->consume("function")) {
            $generator = $allowGenerator && $this->scanner
                ->consume("*");
            $id = $this->parseIdentifier(static::$bindingIdentifier);
            if ($generator || $async) {
                $flags = array(
                    null,
                );
                if ($generator) {
                    $flags["allowYield"] = true;
                }
                if ($async) {
                    $flags["allowAwait"] = true;
                }
            }
            else {
                $flags = null;
            }
            if (($default || $id) && $this->scanner
                ->consume("(") && ($params = $this->isolateContext($flags, "parseFormalParameterList")) !== null && $this->scanner
                ->consume(")") && ($tokenBodyStart = $this->scanner
                ->consume("{")) && (($body = $this->isolateContext($flags, "parseFunctionBody")) || true) && $this->scanner
                ->consume("}")) {
                $body->location->start = $tokenBodyStart->location->start;
                $body->location->end = $this->scanner
                    ->getPosition();
                $node = $this->createNode("FunctionDeclaration", $async ?: $token);
                if ($id) {
                    $node->setId($id);
                }
                $node->setParams($params);
                $node->setBody($body);
                $node->setGenerator($generator);
                $node->setAsync((bool) $async);
                return $this->completeNode($node);
            }
            $this->error();
        }
        return null;
    }
    
    /**
     * Parses function or generator expression
     * 
     * @return Node\FunctionExpression|null
     */
    protected function parseFunctionOrGeneratorExpression() {
        $allowGenerator = true;
        $async = false;
        if ($this->features->asyncAwait && ($async = $this->checkAsyncFunctionStart())) {
            $this->scanner
                ->consumeToken();
            if (!$this->features->asyncIterationGenerators) {
                $allowGenerator = false;
            }
        }
        if ($token = $this->scanner
            ->consume("function")) {
            $generator = $allowGenerator && $this->scanner
                ->consume("*");
            if ($generator || $async) {
                $flags = array(
                    null,
                );
                if ($generator) {
                    $flags["allowYield"] = true;
                }
                if ($async) {
                    $flags["allowAwait"] = true;
                }
            }
            else {
                $flags = null;
            }
            $id = $this->isolateContext($flags, "parseIdentifier", array(
                static::$bindingIdentifier,
            ));
            if ($this->scanner
                ->consume("(") && ($params = $this->isolateContext($flags, "parseFormalParameterList")) !== null && $this->scanner
                ->consume(")") && ($tokenBodyStart = $this->scanner
                ->consume("{")) && (($body = $this->isolateContext($flags, "parseFunctionBody")) || true) && $this->scanner
                ->consume("}")) {
                $body->location->start = $tokenBodyStart->location->start;
                $body->location->end = $this->scanner
                    ->getPosition();
                $node = $this->createNode("FunctionExpression", $async ?: $token);
                $node->setId($id);
                $node->setParams($params);
                $node->setBody($body);
                $node->setGenerator($generator);
                $node->setAsync((bool) $async);
                return $this->completeNode($node);
            }
            $this->error();
        }
        return null;
    }
    
    /**
     * Parses yield statement
     * 
     * @return Node\YieldExpression|null
     */
    protected function parseYieldExpression() {
        if ($token = $this->scanner
            ->consume("yield")) {
            $node = $this->createNode("YieldExpression", $token);
            if ($this->scanner
                ->noLineTerminators()) {
                $delegate = $this->scanner
                    ->consume("*");
                $argument = $this->isolateContext(array(
                    "allowYield" => true,
                ), "parseAssignmentExpression");
                if ($argument) {
                    $node->setArgument($argument);
                    $node->setDelegate($delegate);
                }
            }
            return $this->completeNode($node);
        }
        return null;
    }
    
    /**
     * Parses a parameter list
     * 
     * @return Node\Node[]|null
     */
    protected function parseFormalParameterList() {
        $hasComma = false;
        $list = array();
        while (($param = $this->parseBindingRestElement()) || ($param = $this->parseBindingElement())) {
            $hasComma = false;
            $list[] = $param;
            if ($param->getType() === "RestElement") {
                break;
            }
            elseif ($this->scanner
                ->consume(",")) {
                $hasComma = true;
            }
            else {
                break;
            }
        }
        
        //Check if it ends with a comma, then check if the comma is a trailing comma,
        
        //in that case throw an error if the trailing comma feature is not enabled
        if ($hasComma && !$this->features->trailingCommaFunctionCallDeclaration) {
            $token = $this->scanner
                ->getToken();
            if ($token && $token->value === ")") {
                $this->error();
            }
        }
        return $list;
    }
    
    /**
     * Parses a function body
     * 
     * @return Node\BlockStatement[]|null
     */
    protected function parseFunctionBody() {
        $body = $this->isolateContext(array(
            "allowReturn" => true,
        ), "parseStatementList", array(
            true,
        ));
        $node = $this->createNode("BlockStatement", $body ?: $this->scanner
            ->getPosition());
        if ($body) {
            $node->setBody($body);
        }
        return $this->completeNode($node);
    }
    
    /**
     * Parses a class declaration
     * 
     * @param bool $default Default mode
     * 
     * @return Node\ClassDeclaration|null
     */
    protected function parseClassDeclaration($default = false) {
        if ($token = $this->scanner
            ->consume("class")) {
            
            //Class declarations are strict mode by default
            $prevStrict = $this->scanner
                ->getStrictMode();
            $this->scanner
                ->setStrictMode(true);
            $id = $this->parseIdentifier(static::$bindingIdentifier);
            if (($default || $id) && ($tail = $this->parseClassTail())) {
                $node = $this->createNode("ClassDeclaration", $token);
                if ($id) {
                    $node->setId($id);
                }
                if ($tail[0]) {
                    $node->setSuperClass($tail[0]);
                }
                $node->setBody($tail[1]);
                $this->scanner
                    ->setStrictMode($prevStrict);
                return $this->completeNode($node);
            }
            $this->error();
        }
        return null;
    }
    
    /**
     * Parses a class expression
     * 
     * @return Node\ClassExpression|null
     */
    protected function parseClassExpression() {
        if ($token = $this->scanner
            ->consume("class")) {
            
            //Class expressions are strict mode by default
            $prevStrict = $this->scanner
                ->getStrictMode();
            $this->scanner
                ->setStrictMode(true);
            $id = $this->parseIdentifier(static::$bindingIdentifier);
            $tail = $this->parseClassTail();
            $node = $this->createNode("ClassExpression", $token);
            if ($id) {
                $node->setId($id);
            }
            if ($tail[0]) {
                $node->setSuperClass($tail[0]);
            }
            $node->setBody($tail[1]);
            $this->scanner
                ->setStrictMode($prevStrict);
            return $this->completeNode($node);
        }
        return null;
    }
    
    /**
     * Parses the code that comes after the class keyword and class name. The
     * return value is an array where the first item is the extended class, if
     * any, and the second value is the class body
     * 
     * @return array|null
     */
    protected function parseClassTail() {
        $heritage = $this->parseClassHeritage();
        if ($token = $this->scanner
            ->consume("{")) {
            $body = $this->parseClassBody();
            if ($this->scanner
                ->consume("}")) {
                $body->location->start = $token->location->start;
                $body->location->end = $this->scanner
                    ->getPosition();
                return array(
                    $heritage,
                    $body,
                );
            }
        }
        $this->error();
    }
    
    /**
     * Parses the class extends part
     * 
     * @return Node\Node|null
     */
    protected function parseClassHeritage() {
        if ($this->scanner
            ->consume("extends")) {
            if ($superClass = $this->parseLeftHandSideExpression()) {
                return $superClass;
            }
            $this->error();
        }
        return null;
    }
    
    /**
     * Parses the class body
     * 
     * @return Node\ClassBody|null
     */
    protected function parseClassBody() {
        $body = $this->parseClassElementList();
        $node = $this->createNode("ClassBody", $body ?: $this->scanner
            ->getPosition());
        if ($body) {
            $node->setBody($body);
        }
        return $this->completeNode($node);
    }
    
    /**
     * Parses class elements list
     * 
     * @return Node\MethodDefinition[]|null
     */
    protected function parseClassElementList() {
        $items = array();
        while ($item = $this->parseClassElement()) {
            if ($item !== true) {
                $items[] = $item;
            }
        }
        return count($items) ? $items : null;
    }
    
    /**
     * Parses a class elements
     * 
     * @return Node\MethodDefinition|Node\PropertyDefinition|Node\StaticBlock|bool|null
     */
    protected function parseClassElement() {
        if ($this->scanner
            ->consume(";")) {
            return true;
        }
        if ($this->features->classStaticBlock && $this->scanner
            ->isBefore(array(
            array(
                "static",
                "{",
            ),
        ), true)) {
            return $this->parseClassStaticBlock();
        }
        $staticToken = null;
        $state = $this->scanner
            ->getState();
        
        //This code handles the case where "static" is the method name
        if (!$this->scanner
            ->isBefore(array(
            array(
                "static",
                "(",
            ),
        ), true)) {
            $staticToken = $this->scanner
                ->consume("static");
        }
        if ($def = $this->parseMethodDefinition()) {
            if ($staticToken) {
                $def->setStatic(true);
                $def->location->start = $staticToken->location->start;
            }
            return $def;
        }
        else {
            if ($this->features->classFields) {
                if ($field = $this->parseFieldDefinition()) {
                    if ($staticToken) {
                        $field->setStatic(true);
                        $field->location->start = $staticToken->location->start;
                    }
                }
                elseif ($staticToken) {
                    
                    //Handle the case when "static" is the field name
                    $this->scanner
                        ->setState($state);
                    $field = $this->parseFieldDefinition();
                }
                return $field;
            }
            elseif ($staticToken) {
                $this->error();
            }
        }
        return null;
    }
    
    /**
     * Parses a let or const declaration
     * 
     * @return Node\VariableDeclaration|null
     */
    protected function parseLexicalDeclaration() {
        $state = $this->scanner
            ->getState();
        if ($token = $this->scanner
            ->consumeOneOf(array(
            "let",
            "const",
        ))) {
            $declarations = $this->charSeparatedListOf("parseVariableDeclaration");
            if ($declarations) {
                $this->assertEndOfStatement();
                $node = $this->createNode("VariableDeclaration", $token);
                $node->setKind($token->value);
                $node->setDeclarations($declarations);
                return $this->completeNode($node);
            }
            // "let" can be used as variable name in non-strict mode
            if ($this->scanner
                ->getStrictMode() || $token->value !== "let") {
                $this->error();
            }
            else {
                $this->scanner
                    ->setState($state);
            }
        }
        return null;
    }
    
    /**
     * Parses a var declaration
     * 
     * @return Node\VariableDeclaration|null
     */
    protected function parseVariableStatement() {
        if ($token = $this->scanner
            ->consume("var")) {
            $declarations = $this->isolateContext(array(
                "allowIn" => true,
            ), "parseVariableDeclarationList");
            if ($declarations) {
                $this->assertEndOfStatement();
                $node = $this->createNode("VariableDeclaration", $token);
                $node->setKind($node::KIND_VAR);
                $node->setDeclarations($declarations);
                return $this->completeNode($node);
            }
            $this->error();
        }
        return null;
    }
    
    /**
     * Parses an variable declarations
     * 
     * @return Node\VariableDeclarator[]|null
     */
    protected function parseVariableDeclarationList() {
        return $this->charSeparatedListOf("parseVariableDeclaration");
    }
    
    /**
     * Parses a variable declarations
     * 
     * @return Node\VariableDeclarator|null
     */
    protected function parseVariableDeclaration() {
        if ($id = $this->parseIdentifier(static::$bindingIdentifier)) {
            $node = $this->createNode("VariableDeclarator", $id);
            $node->setId($id);
            if ($init = $this->parseInitializer()) {
                $node->setInit($init);
            }
            return $this->completeNode($node);
        }
        elseif ($id = $this->parseBindingPattern()) {
            if ($init = $this->parseInitializer()) {
                $node = $this->createNode("VariableDeclarator", $id);
                $node->setId($id);
                $node->setInit($init);
                return $this->completeNode($node);
            }
        }
        return null;
    }
    
    /**
     * Parses a let or const declaration in a for statement definition
     * 
     * @return Node\VariableDeclaration|null
     */
    protected function parseForDeclaration() {
        $state = $this->scanner
            ->getState();
        if ($token = $this->scanner
            ->consumeOneOf(array(
            "let",
            "const",
        ))) {
            if ($declaration = $this->parseForBinding()) {
                $node = $this->createNode("VariableDeclaration", $token);
                $node->setKind($token->value);
                $node->setDeclarations(array(
                    $declaration,
                ));
                return $this->completeNode($node);
            }
            // "let" can be used as variable name in non-strict mode
            if ($this->scanner
                ->getStrictMode() || $token->value !== "let") {
                $this->error();
            }
            else {
                $this->scanner
                    ->setState($state);
            }
        }
        return null;
    }
    
    /**
     * Parses a binding pattern or an identifier that come after a const or let
     * declaration in a for statement definition
     * 
     * @return Node\VariableDeclarator|null
     */
    protected function parseForBinding() {
        if (($id = $this->parseIdentifier(static::$bindingIdentifier)) || ($id = $this->parseBindingPattern())) {
            $node = $this->createNode("VariableDeclarator", $id);
            $node->setId($id);
            return $this->completeNode($node);
        }
        return null;
    }
    
    /**
     * Parses a module item
     * 
     * @return Node\Node|null
     */
    protected function parseModuleItem() {
        if ($item = $this->parseImportDeclaration()) {
            return $item;
        }
        elseif ($item = $this->parseExportDeclaration()) {
            return $item;
        }
        elseif ($item = $this->isolateContext(array(
            "allowYield" => false,
            "allowReturn" => false,
            "allowAwait" => $this->features->topLevelAwait,
        ), "parseStatementListItem")) {
            return $item;
        }
        return null;
    }
    
    /**
     * Parses the from keyword and the following string in import and export
     * declarations
     * 
     * @return Node\StringLiteral|null
     */
    protected function parseFromClause() {
        if ($this->scanner
            ->consume("from")) {
            if ($spec = $this->parseStringLiteral()) {
                return $spec;
            }
            $this->error();
        }
        return null;
    }
    
    /**
     * Parses an export declaration
     * 
     * @return Node\ModuleDeclaration|null
     */
    protected function parseExportDeclaration() {
        if ($token = $this->scanner
            ->consume("export")) {
            if ($this->scanner
                ->consume("*")) {
                $exported = null;
                if ($this->features->exportedNameInExportAll && $this->scanner
                    ->consume("as")) {
                    $exported = $this->parseModuleExportName();
                    if (!$exported) {
                        $this->error();
                    }
                }
                if ($source = $this->parseFromClause()) {
                    $this->assertEndOfStatement();
                    $node = $this->createNode("ExportAllDeclaration", $token);
                    $node->setSource($source);
                    $node->setExported($exported);
                    return $this->completeNode($node);
                }
            }
            elseif ($this->scanner
                ->consume("default")) {
                $lookaheadTokens = array(
                    "function",
                    "class",
                );
                if ($this->features->asyncAwait) {
                    $lookaheadTokens[] = array(
                        "async",
                        true,
                    );
                }
                if (($declaration = $this->isolateContext(array(
                    "allowAwait" => $this->features->topLevelAwait,
                ), "parseFunctionOrGeneratorDeclaration", array(
                    true,
                ))) || ($declaration = $this->isolateContext(array(
                    "allowAwait" => $this->features->topLevelAwait,
                ), "parseClassDeclaration", array(
                    true,
                )))) {
                    $node = $this->createNode("ExportDefaultDeclaration", $token);
                    $node->setDeclaration($declaration);
                    return $this->completeNode($node);
                }
                elseif (!$this->scanner
                    ->isBefore($lookaheadTokens, $this->features->asyncAwait) && ($declaration = $this->isolateContext(array(
                    "allowIn" => true,
                    "allowAwait" => $this->features->topLevelAwait,
                ), "parseAssignmentExpression"))) {
                    $this->assertEndOfStatement();
                    $node = $this->createNode("ExportDefaultDeclaration", $token);
                    $node->setDeclaration($declaration);
                    return $this->completeNode($node);
                }
            }
            elseif (($specifiers = $this->parseExportClause()) !== null) {
                $node = $this->createNode("ExportNamedDeclaration", $token);
                $node->setSpecifiers($specifiers);
                if ($source = $this->parseFromClause()) {
                    $node->setSource($source);
                }
                $this->assertEndOfStatement();
                return $this->completeNode($node);
            }
            elseif (($dec = $this->isolateContext(array(
                "allowAwait" => $this->features->topLevelAwait,
            ), "parseVariableStatement")) || ($dec = $this->isolateContext(array(
                "allowAwait" => $this->features->topLevelAwait,
            ), "parseDeclaration"))) {
                $node = $this->createNode("ExportNamedDeclaration", $token);
                $node->setDeclaration($dec);
                return $this->completeNode($node);
            }
            $this->error();
        }
        return null;
    }
    
    /**
     * Parses an export clause
     * 
     * @return Node\ExportSpecifier[]|null
     */
    protected function parseExportClause() {
        if ($this->scanner
            ->consume("{")) {
            $list = array();
            while ($spec = $this->parseExportSpecifier()) {
                $list[] = $spec;
                if (!$this->scanner
                    ->consume(",")) {
                    break;
                }
            }
            if ($this->scanner
                ->consume("}")) {
                return $list;
            }
            $this->error();
        }
        return null;
    }
    
    /**
     * Parses an export specifier
     * 
     * @return Node\ExportSpecifier|null
     */
    protected function parseExportSpecifier() {
        if ($local = $this->parseModuleExportName()) {
            $node = $this->createNode("ExportSpecifier", $local);
            $node->setLocal($local);
            if ($this->scanner
                ->consume("as")) {
                if ($exported = $this->parseModuleExportName()) {
                    $node->setExported($exported);
                    return $this->completeNode($node);
                }
                $this->error();
            }
            else {
                $node->setExported($local);
                return $this->completeNode($node);
            }
        }
        return null;
    }
    
    /**
     * Parses an export name
     * 
     * @return Node\Identifier|Node\StringLiteral|null
     */
    protected function parseModuleExportName() {
        if ($name = $this->parseIdentifier(static::$identifierName)) {
            return $name;
        }
        elseif ($this->features->arbitraryModuleNSNames && ($name = $this->parseStringLiteral())) {
            return $name;
        }
        return null;
    }
    
    /**
     * Parses an import declaration
     * 
     * @return Node\ModuleDeclaration|null
     */
    protected function parseImportDeclaration() {
        
        //Delay parsing of dynamic import so that it is handled
        
        //by the relative method
        if ($this->features->dynamicImport && $this->scanner
            ->isBefore(array(
            array(
                "import",
                "(",
            ),
        ), true)) {
            return null;
        }
        
        //Delay parsing of import.meta so that it is handled
        
        //by the relative method
        if ($this->features->importMeta && $this->scanner
            ->isBefore(array(
            array(
                "import",
                ".",
            ),
        ), true)) {
            return null;
        }
        if ($token = $this->scanner
            ->consume("import")) {
            if ($source = $this->parseStringLiteral()) {
                $this->assertEndOfStatement();
                $node = $this->createNode("ImportDeclaration", $token);
                $node->setSource($source);
                return $this->completeNode($node);
            }
            elseif (($specifiers = $this->parseImportClause()) !== null && ($source = $this->parseFromClause())) {
                $this->assertEndOfStatement();
                $node = $this->createNode("ImportDeclaration", $token);
                $node->setSpecifiers($specifiers);
                $node->setSource($source);
                return $this->completeNode($node);
            }
            $this->error();
        }
        return null;
    }
    
    /**
     * Parses an import clause
     * 
     * @return array|null
     */
    protected function parseImportClause() {
        if ($spec = $this->parseNameSpaceImport()) {
            return array(
                $spec,
            );
        }
        elseif (($specs = $this->parseNamedImports()) !== null) {
            return $specs;
        }
        elseif ($spec = $this->parseIdentifier(static::$importedBinding)) {
            $node = $this->createNode("ImportDefaultSpecifier", $spec);
            $node->setLocal($spec);
            $ret = array(
                $this->completeNode($node),
            );
            if ($this->scanner
                ->consume(",")) {
                if ($spec = $this->parseNameSpaceImport()) {
                    $ret[] = $spec;
                    return $ret;
                }
                elseif (($specs = $this->parseNamedImports()) !== null) {
                    return array_merge($ret, $specs);
                }
                $this->error();
            }
            else {
                return $ret;
            }
        }
        return null;
    }
    
    /**
     * Parses a namespace import
     * 
     * @return Node\ImportNamespaceSpecifier|null
     */
    protected function parseNameSpaceImport() {
        if ($token = $this->scanner
            ->consume("*")) {
            if ($this->scanner
                ->consume("as") && ($local = $this->parseIdentifier(static::$identifierReference))) {
                $node = $this->createNode("ImportNamespaceSpecifier", $token);
                $node->setLocal($local);
                return $this->completeNode($node);
            }
            $this->error();
        }
        return null;
    }
    
    /**
     * Parses a named imports
     * 
     * @return Node\ImportSpecifier[]|null
     */
    protected function parseNamedImports() {
        if ($this->scanner
            ->consume("{")) {
            $list = array();
            while ($spec = $this->parseImportSpecifier()) {
                $list[] = $spec;
                if (!$this->scanner
                    ->consume(",")) {
                    break;
                }
            }
            if ($this->scanner
                ->consume("}")) {
                return $list;
            }
            $this->error();
        }
        return null;
    }
    
    /**
     * Parses an import specifier
     * 
     * @return Node\ImportSpecifier|null
     */
    protected function parseImportSpecifier() {
        $requiredAs = false;
        $imported = $this->parseIdentifier(static::$importedBinding);
        if (!$imported) {
            $imported = $this->parseModuleExportName();
            if (!$imported) {
                return null;
            }
            $requiredAs = true;
        }
        $node = $this->createNode("ImportSpecifier", $imported);
        $node->setImported($imported);
        if ($this->scanner
            ->consume("as")) {
            if (!($local = $this->parseIdentifier(static::$importedBinding))) {
                $this->error();
            }
            $node->setLocal($local);
        }
        elseif ($requiredAs) {
            $this->error();
        }
        else {
            $node->setLocal($imported);
        }
        return $this->completeNode($node);
    }
    
    /**
     * Parses a binding pattern
     * 
     * @return Node\ArrayPattern|Node\ObjectPattern|null
     */
    protected function parseBindingPattern() {
        if ($pattern = $this->parseObjectBindingPattern()) {
            return $pattern;
        }
        elseif ($pattern = $this->parseArrayBindingPattern()) {
            return $pattern;
        }
        return null;
    }
    
    /**
     * Parses an elisions sequence. It returns the number of elisions or null
     * if no elision has been found
     * 
     * @return int
     */
    protected function parseElision() {
        $count = 0;
        while ($this->scanner
            ->consume(",")) {
            $count++;
        }
        return $count ?: null;
    }
    
    /**
     * Parses an array binding pattern
     * 
     * @return Node\ArrayPattern|null
     */
    protected function parseArrayBindingPattern() {
        if ($token = $this->scanner
            ->consume("[")) {
            $elements = array();
            while (true) {
                if ($elision = $this->parseElision()) {
                    $elements = array_merge($elements, array_fill(0, $elision, null));
                }
                if ($element = $this->parseBindingElement()) {
                    $elements[] = $element;
                    if (!$this->scanner
                        ->consume(",")) {
                        break;
                    }
                }
                elseif ($rest = $this->parseBindingRestElement()) {
                    $elements[] = $rest;
                    break;
                }
                else {
                    break;
                }
            }
            if ($this->scanner
                ->consume("]")) {
                $node = $this->createNode("ArrayPattern", $token);
                $node->setElements($elements);
                return $this->completeNode($node);
            }
        }
        return null;
    }
    
    /**
     * Parses a rest element
     * 
     * @return Node\RestElement|null
     */
    protected function parseBindingRestElement() {
        if ($token = $this->scanner
            ->consume("...")) {
            if (($argument = $this->parseIdentifier(static::$bindingIdentifier)) || ($argument = $this->parseBindingPattern())) {
                $node = $this->createNode("RestElement", $token);
                $node->setArgument($argument);
                return $this->completeNode($node);
            }
            $this->error();
        }
        return null;
    }
    
    /**
     * Parses a binding element
     * 
     * @return Node\AssignmentPattern|Node\Identifier|null
     */
    protected function parseBindingElement() {
        if ($el = $this->parseSingleNameBinding()) {
            return $el;
        }
        elseif ($left = $this->parseBindingPattern()) {
            $right = $this->isolateContext(array(
                "allowIn" => true,
            ), "parseInitializer");
            if ($right) {
                $node = $this->createNode("AssignmentPattern", $left);
                $node->setLeft($left);
                $node->setRight($right);
                return $this->completeNode($node);
            }
            else {
                return $left;
            }
        }
        return null;
    }
    
    /**
     * Parses single name binding
     * 
     * @return Node\AssignmentPattern|Node\Identifier|null
     */
    protected function parseSingleNameBinding() {
        if ($left = $this->parseIdentifier(static::$bindingIdentifier)) {
            $right = $this->isolateContext(array(
                "allowIn" => true,
            ), "parseInitializer");
            if ($right) {
                $node = $this->createNode("AssignmentPattern", $left);
                $node->setLeft($left);
                $node->setRight($right);
                return $this->completeNode($node);
            }
            else {
                return $left;
            }
        }
        return null;
    }
    
    /**
     * Parses a property name. The returned value is an array where there first
     * element is the property name and the second element is a boolean
     * indicating if it's a computed property
     * 
     * @return array|null
     */
    protected function parsePropertyName() {
        if ($token = $this->scanner
            ->consume("[")) {
            if (($name = $this->isolateContext(array(
                "allowIn" => true,
            ), "parseAssignmentExpression")) && $this->scanner
                ->consume("]")) {
                return array(
                    $name,
                    true,
                    $token,
                );
            }
            $this->error();
        }
        elseif ($name = $this->parseIdentifier(static::$identifierName)) {
            return array(
                $name,
                false,
            );
        }
        elseif ($name = $this->parseStringLiteral()) {
            return array(
                $name,
                false,
            );
        }
        elseif ($name = $this->parseNumericLiteral()) {
            return array(
                $name,
                false,
            );
        }
        return null;
    }
    
    /**
     * Parses a property name. The returned value is an array where there first
     * element is the property name and the second element is a boolean
     * indicating if it's a computed property
     * 
     * @return array|null
     */
    protected function parseClassElementName() {
        if ($this->features->privateMethodsAndFields && ($name = $this->parsePrivateIdentifier())) {
            return array(
                $name,
                false,
            );
        }
        return $this->parsePropertyName();
    }
    
    /**
     * Parses a field definition
     * 
     * @return Node\StaticBlock
     */
    protected function parseClassStaticBlock() {
        $staticToken = $this->scanner
            ->consume("static");
        $this->scanner
            ->consume("{");
        $statements = $this->isolateContext(array(
            "allowAwait" => true,
        ), "parseStatementList");
        if ($this->scanner
            ->consume("}")) {
            $node = $this->createNode("StaticBlock", $staticToken);
            if ($statements) {
                $node->setBody($statements);
            }
            return $this->completeNode($node);
        }
        $this->error();
    }
    
    /**
     * Parses a field definition
     * 
     * @return Node\PropertyDefinition|null
     */
    protected function parseFieldDefinition() {
        $state = $this->scanner
            ->getState();
        if ($prop = $this->parseClassElementName()) {
            $value = $this->isolateContext(array(
                "allowIn" => true,
            ), "parseInitializer");
            $this->assertEndOfStatement();
            $node = $this->createNode("PropertyDefinition", $prop);
            $node->setKey($prop[0]);
            if ($value) {
                $node->setValue($value);
            }
            $node->setComputed($prop[1]);
            return $this->completeNode($node);
        }
        $this->scanner
            ->setState($state);
        return null;
    }
    
    /**
     * Parses a method definition
     * 
     * @return Node\MethodDefinition|null
     */
    protected function parseMethodDefinition() {
        $state = $this->scanner
            ->getState();
        $generator = $error = $async = false;
        $position = null;
        $kind = Node\MethodDefinition::KIND_METHOD;
        if ($token = $this->scanner
            ->consume("get")) {
            $position = $token;
            $kind = Node\MethodDefinition::KIND_GET;
        }
        elseif ($token = $this->scanner
            ->consume("set")) {
            $position = $token;
            $kind = Node\MethodDefinition::KIND_SET;
        }
        elseif ($token = $this->scanner
            ->consume("*")) {
            $position = $token;
            $error = true;
            $generator = true;
        }
        elseif ($this->features->asyncAwait && ($token = $this->checkAsyncFunctionStart(false))) {
            $this->scanner
                ->consumeToken();
            $position = $token;
            $error = true;
            $async = true;
            if ($this->features->asyncIterationGenerators && $this->scanner
                ->consume("*")) {
                $generator = true;
            }
        }
        
        //Handle the case where get, set and async are methods name and not the
        
        //definition of a getter/setter or the start of an async function
        if (($kind !== Node\MethodDefinition::KIND_METHOD || $async && !$generator) && $this->scanner
            ->consume("(")) {
            $this->scanner
                ->setState($state);
            $kind = Node\MethodDefinition::KIND_METHOD;
            $error = $async = false;
        }
        if ($prop = $this->parseClassElementName()) {
            if (!$position) {
                $position = isset($prop[2]) ? $prop[2] : $prop[0];
            }
            if ($tokenFn = $this->scanner
                ->consume("(")) {
                if ($generator || $async) {
                    $flags = array(
                        null,
                    );
                    if ($generator) {
                        $flags["allowYield"] = true;
                    }
                    if ($async) {
                        $flags["allowAwait"] = true;
                    }
                }
                else {
                    $flags = null;
                }
                $error = true;
                $params = array();
                if ($kind === Node\MethodDefinition::KIND_SET) {
                    $params = $this->isolateContext(null, "parseBindingElement");
                    if ($params) {
                        $params = array(
                            $params,
                        );
                    }
                }
                elseif ($kind === Node\MethodDefinition::KIND_METHOD) {
                    $params = $this->isolateContext($flags, "parseFormalParameterList");
                }
                if ($params !== null && $this->scanner
                    ->consume(")") && ($tokenBodyStart = $this->scanner
                    ->consume("{")) && (($body = $this->isolateContext($flags, "parseFunctionBody")) || true) && $this->scanner
                    ->consume("}")) {
                    if ($prop[0] instanceof Node\Identifier && $prop[0]->getName() === "constructor") {
                        $kind = Node\MethodDefinition::KIND_CONSTRUCTOR;
                    }
                    $body->location->start = $tokenBodyStart->location->start;
                    $body->location->end = $this->scanner
                        ->getPosition();
                    $nodeFn = $this->createNode("FunctionExpression", $tokenFn);
                    $nodeFn->setParams($params);
                    $nodeFn->setBody($body);
                    $nodeFn->setGenerator($generator);
                    $nodeFn->setAsync($async);
                    $node = $this->createNode("MethodDefinition", $position);
                    $node->setKey($prop[0]);
                    $node->setValue($this->completeNode($nodeFn));
                    $node->setKind($kind);
                    $node->setComputed($prop[1]);
                    return $this->completeNode($node);
                }
            }
        }
        elseif ($this->features->classFields && $async && !$generator) {
            $this->scanner
                ->setState($state);
            $error = $async = false;
        }
        if ($error) {
            $this->error();
        }
        else {
            $this->scanner
                ->setState($state);
        }
        return null;
    }
    
    /**
     * Parses parameters in an arrow function. If the parameters are wrapped in
     * round brackets, the returned value is an array where the first element
     * is the parameters list and the second element is the open round brackets,
     * this is needed to know the start position
     * 
     * @return Node\Identifier|array|null
     */
    protected function parseArrowParameters() {
        if ($param = $this->parseIdentifier(static::$bindingIdentifier, "=>")) {
            return $param;
        }
        elseif ($token = $this->scanner
            ->consume("(")) {
            $params = $this->parseFormalParameterList();
            if ($params !== null && $this->scanner
                ->consume(")")) {
                return array(
                    $params,
                    $token,
                );
            }
        }
        return null;
    }
    
    /**
     * Parses the body of an arrow function. The returned value is an array
     * where the first element is the function body and the second element is
     * a boolean indicating if the body is wrapped in curly braces
     *
     * @param bool  $async  Async body mode
     *
     * @return array|null
     */
    protected function parseConciseBody($async = false) {
        if ($token = $this->scanner
            ->consume("{")) {
            if (($body = $this->isolateContext($async ? array(
                null,
                "allowAwait" => true,
            ) : null, "parseFunctionBody")) && $this->scanner
                ->consume("}")) {
                $body->location->start = $token->location->start;
                $body->location->end = $this->scanner
                    ->getPosition();
                return array(
                    $body,
                    false,
                );
            }
            $this->error();
        }
        elseif (!$this->scanner
            ->isBefore(array(
            "{",
        )) && ($body = $this->isolateContext($this->features->asyncAwait ? array(
            "allowYield" => false,
            "allowAwait" => $async,
        ) : array(
            "allowYield" => false,
        ), "parseAssignmentExpression"))) {
            return array(
                $body,
                true,
            );
        }
        return null;
    }
    
    /**
     * Parses an arrow function
     * 
     * @return Node\ArrowFunctionExpression|null
     */
    protected function parseArrowFunction() {
        $state = $this->scanner
            ->getState();
        $async = false;
        if ($this->features->asyncAwait && ($async = $this->checkAsyncFunctionStart(false))) {
            $this->scanner
                ->consumeToken();
        }
        if (($params = $this->parseArrowParameters()) !== null) {
            if ($this->scanner
                ->noLineTerminators() && $this->scanner
                ->consume("=>")) {
                if ($body = $this->parseConciseBody((bool) $async)) {
                    if (is_array($params)) {
                        $pos = $params[1];
                        $params = $params[0];
                    }
                    else {
                        $pos = $params;
                        $params = array(
                            $params,
                        );
                    }
                    if ($async) {
                        $pos = $async;
                    }
                    $node = $this->createNode("ArrowFunctionExpression", $pos);
                    $node->setParams($params);
                    $node->setBody($body[0]);
                    $node->setExpression($body[1]);
                    $node->setAsync((bool) $async);
                    return $this->completeNode($node);
                }
                $this->error();
            }
        }
        $this->scanner
            ->setState($state);
        return null;
    }
    
    /**
     * Parses an object literal
     * 
     * @return Node\ObjectExpression|null
     */
    protected function parseObjectLiteral() {
        if ($token = $this->scanner
            ->consume("{")) {
            $properties = array();
            while ($prop = $this->parsePropertyDefinition()) {
                $properties[] = $prop;
                if (!$this->scanner
                    ->consume(",")) {
                    break;
                }
            }
            if ($this->scanner
                ->consume("}")) {
                $node = $this->createNode("ObjectExpression", $token);
                if ($properties) {
                    $node->setProperties($properties);
                }
                return $this->completeNode($node);
            }
            $this->error();
        }
        return null;
    }
    
    /**
     * Parses a property in an object literal
     * 
     * @return Node\Property|null
     */
    protected function parsePropertyDefinition() {
        if ($this->features->restSpreadProperties && ($prop = $this->parseSpreadElement())) {
            return $prop;
        }
        $state = $this->scanner
            ->getState();
        if (($property = $this->parsePropertyName()) && $this->scanner
            ->consume(":")) {
            $value = $this->isolateContext(array(
                "allowIn" => true,
            ), "parseAssignmentExpression");
            if ($value) {
                $startPos = isset($property[2]) ? $property[2] : $property[0];
                $node = $this->createNode("Property", $startPos);
                $node->setKey($property[0]);
                $node->setValue($value);
                $node->setComputed($property[1]);
                return $this->completeNode($node);
            }
            $this->error();
        }
        $this->scanner
            ->setState($state);
        if ($property = $this->parseMethodDefinition()) {
            $node = $this->createNode("Property", $property);
            $node->setKey($property->getKey());
            $node->setValue($property->getValue());
            $node->setComputed($property->getComputed());
            $kind = $property->getKind();
            if ($kind !== Node\MethodDefinition::KIND_GET && $kind !== Node\MethodDefinition::KIND_SET) {
                $node->setMethod(true);
                $node->setKind(Node\Property::KIND_INIT);
            }
            else {
                $node->setKind($kind);
            }
            return $this->completeNode($node);
        }
        elseif ($key = $this->parseIdentifier(static::$identifierReference)) {
            $node = $this->createNode("Property", $key);
            $node->setShorthand(true);
            $node->setKey($key);
            $value = $this->isolateContext(array(
                "allowIn" => true,
            ), "parseInitializer");
            $node->setValue($value ?: $key);
            return $this->completeNode($node);
        }
        return null;
    }
    
    /**
     * Parses an initializer
     * 
     * @return Node\Node|null
     */
    protected function parseInitializer() {
        if ($this->scanner
            ->consume("=")) {
            if ($value = $this->parseAssignmentExpression()) {
                return $value;
            }
            $this->error();
        }
        return null;
    }
    
    /**
     * Parses an object binding pattern
     * 
     * @return Node\ObjectPattern|null
     */
    protected function parseObjectBindingPattern() {
        $state = $this->scanner
            ->getState();
        if ($token = $this->scanner
            ->consume("{")) {
            $properties = array();
            while ($prop = $this->parseBindingProperty()) {
                $properties[] = $prop;
                if (!$this->scanner
                    ->consume(",")) {
                    break;
                }
            }
            if ($this->features->restSpreadProperties && ($rest = $this->parseRestProperty())) {
                $properties[] = $rest;
            }
            if ($this->scanner
                ->consume("}")) {
                $node = $this->createNode("ObjectPattern", $token);
                if ($properties) {
                    $node->setProperties($properties);
                }
                return $this->completeNode($node);
            }
            $this->scanner
                ->setState($state);
        }
        return null;
    }
    
    /**
     * Parses a rest property
     *
     * @return Node\RestElement|null
     */
    protected function parseRestProperty() {
        $state = $this->scanner
            ->getState();
        if ($token = $this->scanner
            ->consume("...")) {
            if ($argument = $this->parseIdentifier(static::$bindingIdentifier)) {
                $node = $this->createNode("RestElement", $token);
                $node->setArgument($argument);
                return $this->completeNode($node);
            }
            $this->scanner
                ->setState($state);
        }
        return null;
    }
    
    /**
     * Parses a property in an object binding pattern
     * 
     * @return Node\AssignmentProperty|null
     */
    protected function parseBindingProperty() {
        $state = $this->scanner
            ->getState();
        if (($key = $this->parsePropertyName()) && $this->scanner
            ->consume(":")) {
            if ($value = $this->parseBindingElement()) {
                $startPos = isset($key[2]) ? $key[2] : $key[0];
                $node = $this->createNode("AssignmentProperty", $startPos);
                $node->setKey($key[0]);
                $node->setComputed($key[1]);
                $node->setValue($value);
                return $this->completeNode($node);
            }
            $this->scanner
                ->setState($state);
            return null;
        }
        $this->scanner
            ->setState($state);
        if ($property = $this->parseSingleNameBinding()) {
            $node = $this->createNode("AssignmentProperty", $property);
            $node->setShorthand(true);
            if ($property instanceof Node\AssignmentPattern) {
                $node->setKey($property->getLeft());
            }
            else {
                $node->setKey($property);
            }
            $node->setValue($property);
            return $this->completeNode($node);
        }
        return null;
    }
    
    /**
     * Parses an expression
     * 
     * @return Node\Node|null
     */
    protected function parseExpression() {
        $list = $this->charSeparatedListOf("parseAssignmentExpression");
        if (!$list) {
            return null;
        }
        elseif (count($list) === 1) {
            return $list[0];
        }
        else {
            $node = $this->createNode("SequenceExpression", $list);
            $node->setExpressions($list);
            return $this->completeNode($node);
        }
    }
    
    /**
     * Parses an assignment expression
     * 
     * @return Node\Node|null
     */
    protected function parseAssignmentExpression() {
        if ($expr = $this->parseArrowFunction()) {
            return $expr;
        }
        elseif ($this->context->allowYield && ($expr = $this->parseYieldExpression())) {
            return $expr;
        }
        elseif ($expr = $this->parseConditionalExpression()) {
            $exprTypes = array(
                "ConditionalExpression",
                "LogicalExpression",
                "BinaryExpression",
                "UpdateExpression",
                "UnaryExpression",
            );
            if (!in_array($expr->getType(), $exprTypes)) {
                $operators = $this->assignmentOperators;
                if ($operator = $this->scanner
                    ->consumeOneOf($operators)) {
                    if ($expr->getType() === "ChainExpression") {
                        $this->error("Optional chain can't appear in left-hand side");
                    }
                    $right = $this->parseAssignmentExpression();
                    if ($right) {
                        $node = $this->createNode("AssignmentExpression", $expr);
                        $node->setLeft($this->expressionToPattern($expr));
                        $node->setOperator($operator->value);
                        $node->setRight($right);
                        return $this->completeNode($node);
                    }
                    $this->error();
                }
            }
            return $expr;
        }
        return null;
    }
    
    /**
     * Parses a conditional expression
     * 
     * @return Node\Node|null
     */
    protected function parseConditionalExpression() {
        if ($test = $this->parseLogicalBinaryExpression()) {
            if ($this->scanner
                ->consume("?")) {
                $consequent = $this->isolateContext(array(
                    "allowIn" => true,
                ), "parseAssignmentExpression");
                if ($consequent && $this->scanner
                    ->consume(":") && ($alternate = $this->parseAssignmentExpression())) {
                    $node = $this->createNode("ConditionalExpression", $test);
                    $node->setTest($test);
                    $node->setConsequent($consequent);
                    $node->setAlternate($alternate);
                    return $this->completeNode($node);
                }
                $this->error();
            }
            else {
                return $test;
            }
        }
        return null;
    }
    
    /**
     * Parses a logical or a binary expression
     * 
     * @return Node\Node|null
     */
    protected function parseLogicalBinaryExpression() {
        $operators = $this->logicalBinaryOperators;
        if (!$this->context->allowIn) {
            unset($operators["in"]);
        }
        if (!($exp = $this->parseUnaryExpression())) {
            if (!$this->features->classFieldsPrivateIn || !$this->context->allowIn) {
                return null;
            }
            
            //Support "#private in x" syntax
            $state = $this->scanner
                ->getState();
            if (!($exp = $this->parsePrivateIdentifier()) || !$this->scanner
                ->isBefore(array(
                "in",
            ))) {
                if ($exp) {
                    $this->scanner
                        ->setState($state);
                }
                return null;
            }
        }
        $list = array(
            $exp,
        );
        $coalescingFound = $andOrFound = false;
        while ($token = $this->scanner
            ->consumeOneOf(array_keys($operators))) {
            $op = $token->value;
            // Coalescing and logical expressions can't be used together
            if ($op === "??") {
                $coalescingFound = true;
            }
            elseif ($op === "&&" || $op === "||") {
                $andOrFound = true;
            }
            if ($coalescingFound && $andOrFound) {
                $this->error("Logical expressions must be wrapped in parentheses when " . "inside coalesce expressions");
            }
            if (!($exp = $this->parseUnaryExpression())) {
                $this->error();
            }
            $list[] = $op;
            $list[] = $exp;
        }
        $len = count($list);
        if ($len > 1) {
            $maxGrade = max($operators);
            for ($grade = $maxGrade; $grade >= 0; $grade--) {
                $class = $grade < 2 ? "LogicalExpression" : "BinaryExpression";
                $r2l = $grade === 10;
                
                //Exponentiation operator must be parsed right to left
                if ($r2l) {
                    $i = $len - 2;
                    $step = -2;
                }
                else {
                    $i = 1;
                    $step = 2;
                }
                for (; $r2l && $i > 0 || !$r2l && $i < $len; $i += $step) {
                    if ($operators[$list[$i]] === $grade) {
                        $node = $this->createNode($class, $list[$i - 1]);
                        $node->setLeft($list[$i - 1]);
                        $node->setOperator($list[$i]);
                        $node->setRight($list[$i + 1]);
                        $node = $this->completeNode($node, $list[$i + 1]->location->end);
                        array_splice($list, $i - 1, 3, array(
                            $node,
                        ));
                        if (!$r2l) {
                            $i -= $step;
                        }
                        $len = count($list);
                    }
                }
            }
        }
        return $list[0];
    }
    
    /**
     * Parses a unary expression
     * 
     * @return Node\Node|null
     */
    protected function parseUnaryExpression() {
        $operators = $this->unaryOperators;
        if ($this->features->asyncAwait && $this->context->allowAwait) {
            $operators[] = "await";
        }
        if ($expr = $this->parsePostfixExpression()) {
            return $expr;
        }
        elseif ($token = $this->scanner
            ->consumeOneOf($operators)) {
            if ($argument = $this->parseUnaryExpression()) {
                $op = $token->value;
                
                //Deleting a variable without accessing its properties is a
                
                //syntax error in strict mode
                if ($op === "delete" && $this->scanner
                    ->getStrictMode() && $argument instanceof Node\Identifier) {
                    $this->error("Deleting an unqualified identifier is not allowed in strict mode");
                }
                if ($this->features->asyncAwait && $op === "await") {
                    $node = $this->createNode("AwaitExpression", $token);
                }
                else {
                    if ($op === "++" || $op === "--") {
                        if ($argument->getType() === "ChainExpression") {
                            $this->error("Optional chain can't appear in left-hand side");
                        }
                        $node = $this->createNode("UpdateExpression", $token);
                        $node->setPrefix(true);
                    }
                    else {
                        $node = $this->createNode("UnaryExpression", $token);
                    }
                    $node->setOperator($op);
                }
                $node->setArgument($argument);
                return $this->completeNode($node);
            }
            $this->error();
        }
        return null;
    }
    
    /**
     * Parses a postfix expression
     * 
     * @return Node\Node|null
     */
    protected function parsePostfixExpression() {
        if ($argument = $this->parseLeftHandSideExpression()) {
            if ($this->scanner
                ->noLineTerminators() && ($token = $this->scanner
                ->consumeOneOf($this->postfixOperators))) {
                if ($argument->getType() === "ChainExpression") {
                    $this->error("Optional chain can't appear in left-hand side");
                }
                $node = $this->createNode("UpdateExpression", $argument);
                $node->setOperator($token->value);
                $node->setArgument($argument);
                return $this->completeNode($node);
            }
            return $argument;
        }
        return null;
    }
    
    /**
     * Parses a left hand side expression
     * 
     * @return Node\Node|null
     */
    protected function parseLeftHandSideExpression() {
        $object = null;
        $newTokens = array();
        
        //Parse all occurrences of "new"
        if ($this->scanner
            ->isBefore(array(
            "new",
        ))) {
            while ($newToken = $this->scanner
                ->consume("new")) {
                if ($this->scanner
                    ->consume(".")) {
                    
                    //new.target
                    if (!$this->scanner
                        ->consume("target")) {
                        $this->error();
                    }
                    $node = $this->createNode("MetaProperty", $newToken);
                    $node->setMeta("new");
                    $node->setProperty("target");
                    $object = $this->completeNode($node);
                    break;
                }
                $newTokens[] = $newToken;
            }
        }
        elseif ($this->features->importMeta && $this->sourceType === \Peast\Peast::SOURCE_TYPE_MODULE && $this->scanner
            ->isBefore(array(
            array(
                "import",
                ".",
            ),
        ), true)) {
            
            //import.meta
            $importToken = $this->scanner
                ->consume("import");
            $this->scanner
                ->consume(".");
            if (!$this->scanner
                ->consume("meta")) {
                $this->error();
            }
            $node = $this->createNode("MetaProperty", $importToken);
            $node->setMeta("import");
            $node->setProperty("meta");
            $object = $this->completeNode($node);
        }
        $newTokensCount = count($newTokens);
        if (!$object && !($object = $this->parseSuperPropertyOrCall()) && !($this->features->dynamicImport && ($object = $this->parseImportCall())) && !($object = $this->parsePrimaryExpression())) {
            if ($newTokensCount) {
                $this->error();
            }
            return null;
        }
        $valid = true;
        $optionalChain = false;
        $properties = array();
        while (true) {
            $optional = false;
            if ($opToken = $this->scanner
                ->consumeOneOf(array(
                "?.",
                ".",
            ))) {
                $isOptChain = $opToken->value == "?.";
                if ($isOptChain) {
                    $optionalChain = $optional = true;
                }
                if ($this->features->privateMethodsAndFields && ($property = $this->parsePrivateIdentifier()) || ($property = $this->parseIdentifier(static::$identifierName))) {
                    $valid = true;
                    $properties[] = array(
                        "type" => "id",
                        "info" => $property,
                        "optional" => $optional,
                    );
                    continue;
                }
                else {
                    $valid = false;
                    if (!$isOptChain) {
                        break;
                    }
                }
            }
            if ($this->scanner
                ->consume("[")) {
                if (($property = $this->isolateContext(array(
                    "allowIn" => true,
                ), "parseExpression")) && $this->scanner
                    ->consume("]")) {
                    $valid = true;
                    $properties[] = array(
                        "type" => "computed",
                        "info" => array(
                            $property,
                            $this->scanner
                                ->getPosition(),
                        ),
                        "optional" => $optional,
                    );
                }
                else {
                    $valid = false;
                    break;
                }
            }
            elseif ($property = $this->parseTemplateLiteral(true)) {
                if ($optionalChain) {
                    $this->error("Optional chain can't appear in tagged template expressions");
                }
                $valid = true;
                $properties[] = array(
                    "type" => "template",
                    "info" => $property,
                    "optional" => $optional,
                );
            }
            elseif (($args = $this->parseArguments()) !== null) {
                $valid = true;
                $properties[] = array(
                    "type" => "args",
                    "info" => array(
                        $args,
                        $this->scanner
                            ->getPosition(),
                    ),
                    "optional" => $optional,
                );
            }
            else {
                break;
            }
        }
        $propCount = count($properties);
        if (!$valid) {
            $this->error();
        }
        elseif (!$propCount && !$newTokensCount) {
            return $object;
        }
        $node = null;
        $endPos = $object->location->end;
        $optionalChainStarted = false;
        foreach ($properties as $i => $property) {
            $lastNode = $node ?: $object;
            if ($property["optional"]) {
                $optionalChainStarted = true;
            }
            if ($property["type"] === "args") {
                if ($newTokensCount) {
                    if ($optionalChainStarted) {
                        $this->error("Optional chain can't appear in new expressions");
                    }
                    $node = $this->createNode("NewExpression", array_pop($newTokens));
                    $newTokensCount--;
                }
                else {
                    $node = $this->createNode("CallExpression", $lastNode);
                    $node->setOptional($property["optional"]);
                }
                $node->setCallee($lastNode);
                $node->setArguments($property["info"][0]);
                $endPos = $property["info"][1];
            }
            elseif ($property["type"] === "id") {
                $node = $this->createNode("MemberExpression", $lastNode);
                $node->setObject($lastNode);
                $node->setOptional($property["optional"]);
                $node->setProperty($property["info"]);
                $endPos = $property["info"]->location->end;
            }
            elseif ($property["type"] === "computed") {
                $node = $this->createNode("MemberExpression", $lastNode);
                $node->setObject($lastNode);
                $node->setProperty($property["info"][0]);
                $node->setOptional($property["optional"]);
                $node->setComputed(true);
                $endPos = $property["info"][1];
            }
            elseif ($property["type"] === "template") {
                $node = $this->createNode("TaggedTemplateExpression", $object);
                $node->setTag($lastNode);
                $node->setQuasi($property["info"]);
                $endPos = $property["info"]->location->end;
            }
            $node = $this->completeNode($node, $endPos);
        }
        
        //Wrap the result in multiple NewExpression if there are "new" tokens
        if ($newTokensCount) {
            for ($i = $newTokensCount - 1; $i >= 0; $i--) {
                $lastNode = $node ?: $object;
                $node = $this->createNode("NewExpression", $newTokens[$i]);
                $node->setCallee($lastNode);
                $node = $this->completeNode($node);
            }
        }
        
        //Wrap the result in a chain expression if required
        if ($optionalChain) {
            $prevNode = $node;
            $node = $this->createNode("ChainExpression", $prevNode);
            $node->setExpression($prevNode);
            $node = $this->completeNode($node);
        }
        return $node;
    }
    
    /**
     * Parses a spread element
     * 
     * @return Node\SpreadElement|null
     */
    protected function parseSpreadElement() {
        if ($token = $this->scanner
            ->consume("...")) {
            $argument = $this->isolateContext(array(
                "allowIn" => true,
            ), "parseAssignmentExpression");
            if ($argument) {
                $node = $this->createNode("SpreadElement", $token);
                $node->setArgument($argument);
                return $this->completeNode($node);
            }
            $this->error();
        }
        return null;
    }
    
    /**
     * Parses an array literal
     * 
     * @return Node\ArrayExpression|null
     */
    protected function parseArrayLiteral() {
        if ($token = $this->scanner
            ->consume("[")) {
            $elements = array();
            while (true) {
                if ($elision = $this->parseElision()) {
                    $elements = array_merge($elements, array_fill(0, $elision, null));
                }
                if (($element = $this->parseSpreadElement()) || ($element = $this->isolateContext(array(
                    "allowIn" => true,
                ), "parseAssignmentExpression"))) {
                    $elements[] = $element;
                    if (!$this->scanner
                        ->consume(",")) {
                        break;
                    }
                }
                else {
                    break;
                }
            }
            if ($this->scanner
                ->consume("]")) {
                $node = $this->createNode("ArrayExpression", $token);
                $node->setElements($elements);
                return $this->completeNode($node);
            }
            $this->error();
        }
        return null;
    }
    
    /**
     * Parses an arguments list wrapped in round brackets
     * 
     * @return array|null
     */
    protected function parseArguments() {
        if ($this->scanner
            ->consume("(")) {
            if (($args = $this->parseArgumentList()) !== null && $this->scanner
                ->consume(")")) {
                return $args;
            }
            $this->error();
        }
        return null;
    }
    
    /**
     * Parses an arguments list
     * 
     * @return array|null
     */
    protected function parseArgumentList() {
        $list = array();
        $hasComma = false;
        while (true) {
            $spread = $this->scanner
                ->consume("...");
            $exp = $this->isolateContext(array(
                "allowIn" => true,
            ), "parseAssignmentExpression");
            if (!$exp) {
                
                //If there's no expression and the spread dots have been found
                
                //or there is a trailing comma that is not allowed, throw an
                
                //error
                if ($spread || $hasComma && !$this->features->trailingCommaFunctionCallDeclaration) {
                    $this->error();
                }
                break;
            }
            if ($spread) {
                $node = $this->createNode("SpreadElement", $spread);
                $node->setArgument($exp);
                $list[] = $this->completeNode($node);
            }
            else {
                $list[] = $exp;
            }
            if (!$this->scanner
                ->consume(",")) {
                break;
            }
            $hasComma = true;
        }
        return $list;
    }
    
    /**
     * Parses a super call or a super property
     * 
     * @return Node\Node|null
     */
    protected function parseSuperPropertyOrCall() {
        if ($token = $this->scanner
            ->consume("super")) {
            $super = $this->completeNode($this->createNode("Super", $token));
            if (($args = $this->parseArguments()) !== null) {
                $node = $this->createNode("CallExpression", $token);
                $node->setArguments($args);
                $node->setCallee($super);
                return $this->completeNode($node);
            }
            $node = $this->createNode("MemberExpression", $token);
            $node->setObject($super);
            if ($this->scanner
                ->consume(".")) {
                if ($property = $this->parseIdentifier(static::$identifierName)) {
                    $node->setProperty($property);
                    return $this->completeNode($node);
                }
            }
            elseif ($this->scanner
                ->consume("[") && ($property = $this->isolateContext(array(
                "allowIn" => true,
            ), "parseExpression")) && $this->scanner
                ->consume("]")) {
                $node->setProperty($property);
                $node->setComputed(true);
                return $this->completeNode($node);
            }
            $this->error();
        }
        return null;
    }
    
    /**
     * Parses a primary expression
     * 
     * @return Node\Node|null
     */
    protected function parsePrimaryExpression() {
        if ($token = $this->scanner
            ->consume("this")) {
            $node = $this->createNode("ThisExpression", $token);
            return $this->completeNode($node);
        }
        elseif ($exp = $this->parseFunctionOrGeneratorExpression()) {
            return $exp;
        }
        elseif ($exp = $this->parseClassExpression()) {
            return $exp;
        }
        elseif ($exp = $this->parseIdentifier(static::$identifierReference)) {
            return $exp;
        }
        elseif ($exp = $this->parseLiteral()) {
            return $exp;
        }
        elseif ($exp = $this->parseArrayLiteral()) {
            return $exp;
        }
        elseif ($exp = $this->parseObjectLiteral()) {
            return $exp;
        }
        elseif ($exp = $this->parseRegularExpressionLiteral()) {
            return $exp;
        }
        elseif ($exp = $this->parseTemplateLiteral()) {
            return $exp;
        }
        elseif ($this->jsx && ($exp = $this->parseJSXFragment())) {
            return $exp;
        }
        elseif ($this->jsx && ($exp = $this->parseJSXElement())) {
            return $exp;
        }
        elseif ($token = $this->scanner
            ->consume("(")) {
            if (($exp = $this->isolateContext(array(
                "allowIn" => true,
            ), "parseExpression")) && $this->scanner
                ->consume(")")) {
                $node = $this->createNode("ParenthesizedExpression", $token);
                $node->setExpression($exp);
                return $this->completeNode($node);
            }
            $this->error();
        }
        return null;
    }
    
    /**
     * Parses a private identifier
     * 
     * @return Node\PrivateIdentifier|null
     */
    protected function parsePrivateIdentifier() {
        $token = $this->scanner
            ->getToken();
        if (!$token || $token->type !== Token::TYPE_PRIVATE_IDENTIFIER) {
            return null;
        }
        $this->scanner
            ->consumeToken();
        $node = $this->createNode("PrivateIdentifier", $token);
        $node->setName(substr($token->value, 1));
        return $this->completeNode($node);
    }
    
    /**
     * Parses an identifier
     * 
     * @param int   $mode       Parsing mode, one of the id parsing mode
     *                          constants
     * @param string $after     If a string is passed in this parameter, the
     *                          identifier is parsed only if precedes this string
     * 
     * @return Node\Identifier|null
     */
    protected function parseIdentifier($mode, $after = null) {
        $token = $this->scanner
            ->getToken();
        if (!$token) {
            return null;
        }
        if ($after !== null) {
            $next = $this->scanner
                ->getNextToken();
            if (!$next || $next->value !== $after) {
                return null;
            }
        }
        $type = $token->type;
        switch ($type) {
            case Token::TYPE_BOOLEAN_LITERAL:
            case Token::TYPE_NULL_LITERAL:
                if ($mode !== self::ID_ALLOW_ALL) {
                    return null;
                }
                break;
            case Token::TYPE_KEYWORD:
                if ($mode === self::ID_ALLOW_NOTHING) {
                    return null;
                }
                elseif ($mode === self::ID_MIXED && $this->scanner
                    ->isStrictModeKeyword($token)) {
                    return null;
                }
                break;
            default:
                if ($type !== Token::TYPE_IDENTIFIER) {
                    return null;
                }
                break;
        }
        
        //Exclude keywords that depend on parser context
        $value = $token->value;
        if ($mode === self::ID_MIXED && isset($this->contextKeywords[$value]) && $this->context->{$this->contextKeywords[$value]}) {
            return null;
        }
        $this->scanner
            ->consumeToken();
        $node = $this->createNode("Identifier", $token);
        $node->setRawName($value);
        return $this->completeNode($node);
    }
    
    /**
     * Parses a literal
     * 
     * @return Node\Literal|null
     */
    protected function parseLiteral() {
        if ($token = $this->scanner
            ->getToken()) {
            if ($token->type === Token::TYPE_NULL_LITERAL) {
                $this->scanner
                    ->consumeToken();
                $node = $this->createNode("NullLiteral", $token);
                return $this->completeNode($node);
            }
            elseif ($token->type === Token::TYPE_BOOLEAN_LITERAL) {
                $this->scanner
                    ->consumeToken();
                $node = $this->createNode("BooleanLiteral", $token);
                $node->setRaw($token->value);
                return $this->completeNode($node);
            }
            elseif ($literal = $this->parseStringLiteral()) {
                return $literal;
            }
            elseif ($literal = $this->parseNumericLiteral()) {
                return $literal;
            }
        }
        return null;
    }
    
    /**
     * Parses a string literal
     * 
     * @return Node\StringLiteral|null
     */
    protected function parseStringLiteral() {
        $token = $this->scanner
            ->getToken();
        if ($token && $token->type === Token::TYPE_STRING_LITERAL) {
            $val = $token->value;
            $this->checkInvalidEscapeSequences($val);
            $this->scanner
                ->consumeToken();
            $node = $this->createNode("StringLiteral", $token);
            $node->setRaw($val);
            return $this->completeNode($node);
        }
        return null;
    }
    
    /**
     * Parses a numeric literal
     * 
     * @return Node\NumericLiteral|Node\BigIntLiteral|null
     */
    protected function parseNumericLiteral() {
        $token = $this->scanner
            ->getToken();
        if ($token && $token->type === Token::TYPE_NUMERIC_LITERAL) {
            $val = $token->value;
            $this->checkInvalidEscapeSequences($val, true);
            $this->scanner
                ->consumeToken();
            $node = $this->createNode("NumericLiteral", $token);
            $node->setRaw($val);
            return $this->completeNode($node);
        }
        elseif ($token && $token->type === Token::TYPE_BIGINT_LITERAL) {
            $val = $token->value;
            $this->checkInvalidEscapeSequences($val, true);
            $this->scanner
                ->consumeToken();
            $node = $this->createNode("BigIntLiteral", $token);
            $node->setRaw($val);
            return $this->completeNode($node);
        }
        return null;
    }
    
    /**
     * Parses a template literal
     * 
     * @param bool $tagged True if the template is tagged
     * 
     * @return Node\Literal|null
     */
    protected function parseTemplateLiteral($tagged = false) {
        $token = $this->scanner
            ->getToken();
        if (!$token || $token->type !== Token::TYPE_TEMPLATE) {
            return null;
        }
        
        //Do not parse templates parts
        $val = $token->value;
        if ($val[0] !== "`") {
            return null;
        }
        $quasis = $expressions = array();
        $valid = false;
        do {
            $this->scanner
                ->consumeToken();
            $val = $token->value;
            $this->checkInvalidEscapeSequences($val, false, true, $tagged);
            $lastChar = substr($val, -1);
            $quasi = $this->createNode("TemplateElement", $token);
            $quasi->setRawValue($val);
            if ($lastChar === "`") {
                $quasi->setTail(true);
                $quasis[] = $this->completeNode($quasi);
                $valid = true;
                break;
            }
            else {
                $quasis[] = $this->completeNode($quasi);
                $exp = $this->isolateContext(array(
                    "allowIn" => true,
                ), "parseExpression");
                if ($exp) {
                    $expressions[] = $exp;
                }
                else {
                    $valid = false;
                    break;
                }
            }
            $token = $this->scanner
                ->getToken();
        } while ($token && $token->type === Token::TYPE_TEMPLATE);
        if ($valid) {
            $node = $this->createNode("TemplateLiteral", $quasis[0]);
            $node->setQuasis($quasis);
            $node->setExpressions($expressions);
            return $this->completeNode($node);
        }
        $this->error();
    }
    
    /**
     * Parses a regular expression literal
     * 
     * @return Node\Literal|null
     */
    protected function parseRegularExpressionLiteral() {
        if ($token = $this->scanner
            ->reconsumeCurrentTokenAsRegexp()) {
            $this->scanner
                ->consumeToken();
            $node = $this->createNode("RegExpLiteral", $token);
            $node->setRaw($token->value);
            return $this->completeNode($node);
        }
        return null;
    }
    
    /**
     * Parse directive prologues. The result is an array where the first element
     * is the array of parsed nodes and the second element is the array of
     * directive prologues values
     * 
     * @return array|null
     */
    protected function parseDirectivePrologues() {
        $directives = $nodes = array();
        while (($token = $this->scanner
            ->getToken()) && $token->type === Token::TYPE_STRING_LITERAL) {
            $directive = substr($token->value, 1, -1);
            if ($directive === "use strict") {
                $directives[] = $directive;
                $directiveNode = $this->parseStringLiteral();
                $this->assertEndOfStatement();
                $node = $this->createNode("ExpressionStatement", $directiveNode);
                $node->setExpression($directiveNode);
                $nodes[] = $this->completeNode($node);
            }
            else {
                break;
            }
        }
        return count($nodes) ? array(
            $nodes,
            $directives,
        ) : null;
    }
    
    /**
     * Parses an import call
     *
     * @return Node\Node|null
     */
    protected function parseImportCall() {
        if (($token = $this->scanner
            ->consume("import")) && $this->scanner
            ->consume("(")) {
            if (($source = $this->isolateContext(array(
                "allowIn" => true,
            ), "parseAssignmentExpression")) && $this->scanner
                ->consume(")")) {
                $node = $this->createNode("ImportExpression", $token);
                $node->setSource($source);
                return $this->completeNode($node);
            }
            $this->error();
        }
        return null;
    }
    
    /**
     * Checks if the given string or number contains invalid escape sequences
     * 
     * @param string  $val                      Value to check
     * @param bool    $number                   True if the value is a number
     * @param bool    $forceLegacyOctalCheck    True to force legacy octal
     *                                          form check
     * @param bool    $taggedTemplate           True if the value is a tagged
     *                                          template
     * 
     * @return void
     */
    protected function checkInvalidEscapeSequences($val, $number = false, $forceLegacyOctalCheck = false, $taggedTemplate = false) {
        if ($this->features->skipEscapeSeqCheckInTaggedTemplates && $taggedTemplate) {
            return;
        }
        $checkLegacyOctal = $forceLegacyOctalCheck || $this->scanner
            ->getStrictMode();
        if ($number) {
            if ($val && $val[0] === "0" && preg_match("#^0[0-9_]+\$#", $val)) {
                if ($checkLegacyOctal) {
                    $this->error("Octal literals are not allowed in strict mode");
                }
                if ($this->features->numericLiteralSeparator && strpos($val, '_') !== false) {
                    $this->error("Numeric separators are not allowed in legacy octal numbers");
                }
            }
        }
        elseif (strpos($val, "\\") !== false) {
            $hex = "0-9a-fA-F";
            $invalidSyntax = array(
                "x[{$hex}]?[^{$hex}]",
                "x[{$hex}]?\$",
                "u\\{\\}",
                "u\\{(?:[{$hex}]*[^{$hex}\\}]+)+[{$hex}]*\\}",
                "u\\{[^\\}]*\$",
                "u(?!{)[{$hex}]{0,3}[^{$hex}\\{]",
                "u[{$hex}]{0,3}\$",
            );
            if ($checkLegacyOctal) {
                $invalidSyntax[] = "\\d{2}";
                $invalidSyntax[] = "[1-7]";
                $invalidSyntax[] = "0[89]";
            }
            $reg = "#(\\\\+)(" . implode("|", $invalidSyntax) . ")#";
            if (preg_match_all($reg, $val, $matches, PREG_SET_ORDER)) {
                foreach ($matches as $match) {
                    if (strlen($match[1]) % 2) {
                        $first = $match[2][0];
                        if ($first === "u") {
                            $err = "Malformed unicode escape sequence";
                        }
                        elseif ($first === "x") {
                            $err = "Malformed hexadecimal escape sequence";
                        }
                        else {
                            $err = "Octal literals are not allowed in strict mode";
                        }
                        $this->error($err);
                    }
                }
            }
        }
    }

}

Members

Title Sort descending Modifiers Object type Summary Overriden Title
Parser::$assignmentOperators protected property Assignment operators
Parser::$bindingIdentifier protected static property Binding identifier parsing rule
Parser::$contextKeywords protected property Array of keywords that depends on a context property
Parser::$identifierName protected static property Identifier name parsing rule
Parser::$identifierReference protected static property Identifier reference parsing rule
Parser::$importedBinding protected static property Imported binding parsing rule
Parser::$labelledIdentifier protected static property Labelled identifier parsing rule
Parser::$logicalBinaryOperators protected property Logical and binary operators
Parser::$postfixOperators protected property Postfix operators
Parser::$unaryOperators protected property Unary operators
Parser::checkAsyncFunctionStart protected function Checks if an async function can start from the current position. Returns
the async token or null if not found
Parser::checkInvalidEscapeSequences protected function Checks if the given string or number contains invalid escape sequences
Parser::expressionToPattern protected function Converts an expression node to a pattern node
Parser::ID_ALLOW_ALL constant Everything is allowed as identifier, including keywords, null and booleans
Parser::ID_ALLOW_NOTHING constant Keywords, null and booleans are not allowed in any situation
Parser::ID_MIXED constant Keywords, null and booleans are not allowed in any situation, future
reserved words are allowed if not in strict mode. Keywords that depend on
parser context are evaluated only if the parser context allows them.
Parser::initContext protected function Initializes parser context Overrides ParserAbstract::initContext
Parser::parse public function Parses the source Overrides ParserAbstract::parse
Parser::parseArgumentList protected function Parses an arguments list
Parser::parseArguments protected function Parses an arguments list wrapped in round brackets
Parser::parseArrayBindingPattern protected function Parses an array binding pattern
Parser::parseArrayLiteral protected function Parses an array literal
Parser::parseArrowFunction protected function Parses an arrow function
Parser::parseArrowParameters protected function Parses parameters in an arrow function. If the parameters are wrapped in
round brackets, the returned value is an array where the first element
is the parameters list and the second element is the open round brackets,
this is needed to know the start…
Parser::parseAssignmentExpression protected function Parses an assignment expression
Parser::parseBindingElement protected function Parses a binding element
Parser::parseBindingPattern protected function Parses a binding pattern
Parser::parseBindingProperty protected function Parses a property in an object binding pattern
Parser::parseBindingRestElement protected function Parses a rest element
Parser::parseBlock protected function Parses a block statement
Parser::parseBreakableStatement protected function Parses a breakable statement
Parser::parseBreakStatement protected function Parses a break statement
Parser::parseCaseBlock protected function Parses the content of a switch statement
Parser::parseCaseClause protected function Parses a case in a switch statement
Parser::parseCaseClauses protected function Parses cases in a switch statement
Parser::parseCatch protected function Parses the catch block of a try-catch statement
Parser::parseCatchParameter protected function Parses the catch parameter of a catch block in a try-catch statement
Parser::parseClassBody protected function Parses the class body
Parser::parseClassDeclaration protected function Parses a class declaration
Parser::parseClassElement protected function Parses a class elements
Parser::parseClassElementList protected function Parses class elements list
Parser::parseClassElementName protected function Parses a property name. The returned value is an array where there first
element is the property name and the second element is a boolean
indicating if it&#039;s a computed property
Parser::parseClassExpression protected function Parses a class expression
Parser::parseClassHeritage protected function Parses the class extends part
Parser::parseClassStaticBlock protected function Parses a field definition
Parser::parseClassTail protected function Parses the code that comes after the class keyword and class name. The
return value is an array where the first item is the extended class, if
any, and the second value is the class body
Parser::parseConciseBody protected function Parses the body of an arrow function. The returned value is an array
where the first element is the function body and the second element is
a boolean indicating if the body is wrapped in curly braces
Parser::parseConditionalExpression protected function Parses a conditional expression
Parser::parseContinueStatement protected function Parses a continue statement
Parser::parseDebuggerStatement protected function Parses a debugger statement
Parser::parseDeclaration protected function Parses a declaration
Parser::parseDefaultClause protected function Parses default case in a switch statement
Parser::parseDirectivePrologues protected function Parse directive prologues. The result is an array where the first element
is the array of parsed nodes and the second element is the array of
directive prologues values
Parser::parseDoWhileStatement protected function Parses a do-while statement
Parser::parseElision protected function Parses an elisions sequence. It returns the number of elisions or null
if no elision has been found
Parser::parseEmptyStatement protected function Parses an empty statement
Parser::parseExportClause protected function Parses an export clause
Parser::parseExportDeclaration protected function Parses an export declaration
Parser::parseExportSpecifier protected function Parses an export specifier
Parser::parseExpression protected function Parses an expression
Parser::parseExpressionStatement protected function Parses an expression statement
Parser::parseFieldDefinition protected function Parses a field definition
Parser::parseFinally protected function Parses a finally block in a try-catch statement
Parser::parseForBinding protected function Parses a binding pattern or an identifier that come after a const or let
declaration in a for statement definition
Parser::parseForDeclaration protected function Parses a let or const declaration in a for statement definition
Parser::parseForLetConstStatement protected function Parses a for(let ...) or for(const ...) statement
Parser::parseFormalParameterList protected function Parses a parameter list
Parser::parseForNotVarLetConstStatement protected function Parses a for statement that does not start with var, let or const
Parser::parseForVarStatement protected function Parses a for(var ...) statement
Parser::parseFromClause protected function Parses the from keyword and the following string in import and export
declarations
Parser::parseFunctionBody protected function Parses a function body
Parser::parseFunctionOrGeneratorDeclaration protected function Parses function or generator declaration
Parser::parseFunctionOrGeneratorExpression protected function Parses function or generator expression
Parser::parseIdentifier protected function Parses an identifier
Parser::parseIfStatement protected function Parses an if statement
Parser::parseImportCall protected function Parses an import call
Parser::parseImportClause protected function Parses an import clause
Parser::parseImportDeclaration protected function Parses an import declaration
Parser::parseImportSpecifier protected function Parses an import specifier
Parser::parseInitializer protected function Parses an initializer
Parser::parseIterationStatement protected function Parses do-while, while, for, for-in and for-of statements
Parser::parseLabelledStatement protected function Parses a labelled statement
Parser::parseLeftHandSideExpression protected function Parses a left hand side expression
Parser::parseLexicalDeclaration protected function Parses a let or const declaration
Parser::parseLiteral protected function Parses a literal
Parser::parseLogicalBinaryExpression protected function Parses a logical or a binary expression
Parser::parseMethodDefinition protected function Parses a method definition
Parser::parseModuleExportName protected function Parses an export name
Parser::parseModuleItem protected function Parses a module item
Parser::parseModuleItemList protected function Parses a module item list
Parser::parseNamedImports protected function Parses a named imports
Parser::parseNameSpaceImport protected function Parses a namespace import
Parser::parseNumericLiteral protected function Parses a numeric literal
Parser::parseObjectBindingPattern protected function Parses an object binding pattern
Parser::parseObjectLiteral protected function Parses an object literal
Parser::parsePostfixExpression protected function Parses a postfix expression
Parser::parsePrimaryExpression protected function Parses a primary expression
Parser::parsePrivateIdentifier protected function Parses a private identifier
Parser::parsePropertyDefinition protected function Parses a property in an object literal
Parser::parsePropertyName protected function Parses a property name. The returned value is an array where there first
element is the property name and the second element is a boolean
indicating if it&#039;s a computed property
Parser::parseRegularExpressionLiteral protected function Parses a regular expression literal
Parser::parseRestProperty protected function Parses a rest property
Parser::parseReturnStatement protected function Parses a return statement
Parser::parseSingleNameBinding protected function Parses single name binding
Parser::parseSpreadElement protected function Parses a spread element
Parser::parseStatement protected function Parses a statement
Parser::parseStatementList protected function Parses a statement list
Parser::parseStatementListItem protected function Parses a statement list item
Parser::parseStringLiteral protected function Parses a string literal
Parser::parseSuperPropertyOrCall protected function Parses a super call or a super property
Parser::parseSwitchStatement protected function Parses a switch statement
Parser::parseTemplateLiteral protected function Parses a template literal
Parser::parseThrowStatement protected function Parses a throw statement
Parser::parseTryStatement protected function Parses a try-catch statement
Parser::parseUnaryExpression protected function Parses a unary expression
Parser::parseVariableDeclaration protected function Parses a variable declarations
Parser::parseVariableDeclarationList protected function Parses an variable declarations
Parser::parseVariableStatement protected function Parses a var declaration
Parser::parseWhileStatement protected function Parses a while statement
Parser::parseWithStatement protected function Parses a with statement
Parser::parseYieldExpression protected function Parses yield statement
Parser::postInit protected function Post initialize operations Overrides ParserAbstract::postInit
ParserAbstract::$comments protected property Comments handling
ParserAbstract::$context protected property Parser context
ParserAbstract::$eventsEmitter protected property Events emitter
ParserAbstract::$features protected property Parser features
ParserAbstract::$jsx protected property JSX syntax handling
ParserAbstract::$scanner protected property Associated scanner
ParserAbstract::$sourceType protected property Source type
ParserAbstract::assertEndOfStatement protected function Asserts that a valid end of statement follows the current position
ParserAbstract::charSeparatedListOf protected function Parses a character separated list of instructions or null if the
sequence is not valid
ParserAbstract::completeNode protected function Completes a node by adding the end position
ParserAbstract::createNode protected function Creates a node
ParserAbstract::error protected function Throws a syntax error
ParserAbstract::getEventsEmitter public function Returns the parser&#039;s events emitter
ParserAbstract::getFeatures public function Returns the parser features class
ParserAbstract::getScanner public function Returns the scanner associated with the parser
ParserAbstract::isolateContext protected function Calls a method with an isolated parser context, applying the given flags,
but restoring their values after the execution.
ParserAbstract::tokenize public function Returns parsed tokens from the source code
ParserAbstract::__construct public function Class constructor
RSS feed
Powered by Drupal