xarxaprod-wp-theme/vendor/mck89/peast/lib/Peast/Syntax/Parser.php

4066 lines
128 KiB
PHP
Raw Permalink Normal View History

2024-01-09 16:13:20 +01:00
<?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;
}
}
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);
}
}
}
}
}
}