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

Breadcrumb

  1. Drupal Core 11.1.x

Parser.php

Same filename in this branch
  1. 11.1.x vendor/open-telemetry/api/Baggage/Propagation/Parser.php
  2. 11.1.x vendor/phpunit/phpunit/src/Metadata/Parser/Parser.php
  3. 11.1.x vendor/sebastian/cli-parser/src/Parser.php
  4. 11.1.x vendor/sebastian/diff/src/Parser.php
  5. 11.1.x vendor/egulias/email-validator/src/Parser.php
  6. 11.1.x vendor/nikic/php-parser/lib/PhpParser/Parser.php
  7. 11.1.x vendor/twig/twig/src/Parser.php
  8. 11.1.x vendor/symfony/css-selector/Parser/Parser.php
  9. 11.1.x vendor/symfony/yaml/Parser.php
  10. 11.1.x vendor/mck89/peast/lib/Peast/Selector/Parser.php
  11. 11.1.x vendor/mck89/peast/lib/Peast/Syntax/JSX/Parser.php

Namespace

Peast\Syntax

File

vendor/mck89/peast/lib/Peast/Syntax/Parser.php

View source
<?php


/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax;


/**
 * Parser class
 * 
 * @author Marco Marchiò <marco.mm89@gmail.com>
 */
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);
                    }
                }
            }
        }
    }

}

Classes

Title Deprecated Summary
Parser Parser class

API Navigation

  • Drupal Core 11.1.x
  • Topics
  • Classes
  • Functions
  • Constants
  • Globals
  • Files
  • Namespaces
  • Deprecated
  • Services
RSS feed
Powered by Drupal