* * 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Ć² */ 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; } } if ($hasComma && !$this->features->trailingCommaFunctionCallDeclaration) { $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 and set are methods name and not the //definition of a getter/setter if ($kind !== Node\MethodDefinition::KIND_METHOD && $this->scanner->consume("(") ) { $this->scanner->setState($state); $kind = Node\MethodDefinition::KIND_METHOD; $error = 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); } } } 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); } } } } } }