annotate vendor/nikic/php-parser/lib/PhpParser/ParserAbstract.php @ 16:c2387f117808

Routine composer update
author Chris Cannam
date Tue, 10 Jul 2018 15:07:59 +0100
parents 5fb285c0d0e3
children 129ea1e6d783
rev   line source
Chris@13 1 <?php declare(strict_types=1);
Chris@0 2
Chris@0 3 namespace PhpParser;
Chris@0 4
Chris@0 5 /*
Chris@0 6 * This parser is based on a skeleton written by Moriyoshi Koizumi, which in
Chris@0 7 * turn is based on work by Masato Bito.
Chris@0 8 */
Chris@13 9 use PhpParser\Node\Expr;
Chris@0 10 use PhpParser\Node\Name;
Chris@0 11 use PhpParser\Node\Param;
Chris@0 12 use PhpParser\Node\Scalar\LNumber;
Chris@0 13 use PhpParser\Node\Scalar\String_;
Chris@0 14 use PhpParser\Node\Stmt\Class_;
Chris@0 15 use PhpParser\Node\Stmt\ClassConst;
Chris@0 16 use PhpParser\Node\Stmt\ClassMethod;
Chris@0 17 use PhpParser\Node\Stmt\Interface_;
Chris@0 18 use PhpParser\Node\Stmt\Namespace_;
Chris@0 19 use PhpParser\Node\Stmt\Property;
Chris@0 20 use PhpParser\Node\Stmt\TryCatch;
Chris@0 21 use PhpParser\Node\Stmt\UseUse;
Chris@13 22 use PhpParser\Node\VarLikeIdentifier;
Chris@0 23
Chris@0 24 abstract class ParserAbstract implements Parser
Chris@0 25 {
Chris@0 26 const SYMBOL_NONE = -1;
Chris@0 27
Chris@0 28 /*
Chris@0 29 * The following members will be filled with generated parsing data:
Chris@0 30 */
Chris@0 31
Chris@0 32 /** @var int Size of $tokenToSymbol map */
Chris@0 33 protected $tokenToSymbolMapSize;
Chris@0 34 /** @var int Size of $action table */
Chris@0 35 protected $actionTableSize;
Chris@0 36 /** @var int Size of $goto table */
Chris@0 37 protected $gotoTableSize;
Chris@0 38
Chris@0 39 /** @var int Symbol number signifying an invalid token */
Chris@0 40 protected $invalidSymbol;
Chris@0 41 /** @var int Symbol number of error recovery token */
Chris@0 42 protected $errorSymbol;
Chris@0 43 /** @var int Action number signifying default action */
Chris@0 44 protected $defaultAction;
Chris@0 45 /** @var int Rule number signifying that an unexpected token was encountered */
Chris@0 46 protected $unexpectedTokenRule;
Chris@0 47
Chris@0 48 protected $YY2TBLSTATE;
Chris@13 49 /** @var int Number of non-leaf states */
Chris@13 50 protected $numNonLeafStates;
Chris@0 51
Chris@13 52 /** @var int[] Map of lexer tokens to internal symbols */
Chris@0 53 protected $tokenToSymbol;
Chris@13 54 /** @var string[] Map of symbols to their names */
Chris@0 55 protected $symbolToName;
Chris@0 56 /** @var array Names of the production rules (only necessary for debugging) */
Chris@0 57 protected $productions;
Chris@0 58
Chris@13 59 /** @var int[] Map of states to a displacement into the $action table. The corresponding action for this
Chris@0 60 * state/symbol pair is $action[$actionBase[$state] + $symbol]. If $actionBase[$state] is 0, the
Chris@0 61 action is defaulted, i.e. $actionDefault[$state] should be used instead. */
Chris@0 62 protected $actionBase;
Chris@13 63 /** @var int[] Table of actions. Indexed according to $actionBase comment. */
Chris@0 64 protected $action;
Chris@13 65 /** @var int[] Table indexed analogously to $action. If $actionCheck[$actionBase[$state] + $symbol] != $symbol
Chris@0 66 * then the action is defaulted, i.e. $actionDefault[$state] should be used instead. */
Chris@0 67 protected $actionCheck;
Chris@13 68 /** @var int[] Map of states to their default action */
Chris@0 69 protected $actionDefault;
Chris@13 70 /** @var callable[] Semantic action callbacks */
Chris@13 71 protected $reduceCallbacks;
Chris@0 72
Chris@13 73 /** @var int[] Map of non-terminals to a displacement into the $goto table. The corresponding goto state for this
Chris@0 74 * non-terminal/state pair is $goto[$gotoBase[$nonTerminal] + $state] (unless defaulted) */
Chris@0 75 protected $gotoBase;
Chris@13 76 /** @var int[] Table of states to goto after reduction. Indexed according to $gotoBase comment. */
Chris@0 77 protected $goto;
Chris@13 78 /** @var int[] Table indexed analogously to $goto. If $gotoCheck[$gotoBase[$nonTerminal] + $state] != $nonTerminal
Chris@0 79 * then the goto state is defaulted, i.e. $gotoDefault[$nonTerminal] should be used. */
Chris@0 80 protected $gotoCheck;
Chris@13 81 /** @var int[] Map of non-terminals to the default state to goto after their reduction */
Chris@0 82 protected $gotoDefault;
Chris@0 83
Chris@13 84 /** @var int[] Map of rules to the non-terminal on their left-hand side, i.e. the non-terminal to use for
Chris@0 85 * determining the state to goto after reduction. */
Chris@0 86 protected $ruleToNonTerminal;
Chris@13 87 /** @var int[] Map of rules to the length of their right-hand side, which is the number of elements that have to
Chris@0 88 * be popped from the stack(s) on reduction. */
Chris@0 89 protected $ruleToLength;
Chris@0 90
Chris@0 91 /*
Chris@0 92 * The following members are part of the parser state:
Chris@0 93 */
Chris@0 94
Chris@0 95 /** @var Lexer Lexer that is used when parsing */
Chris@0 96 protected $lexer;
Chris@0 97 /** @var mixed Temporary value containing the result of last semantic action (reduction) */
Chris@0 98 protected $semValue;
Chris@0 99 /** @var array Semantic value stack (contains values of tokens and semantic action results) */
Chris@0 100 protected $semStack;
Chris@0 101 /** @var array[] Start attribute stack */
Chris@0 102 protected $startAttributeStack;
Chris@0 103 /** @var array[] End attribute stack */
Chris@0 104 protected $endAttributeStack;
Chris@0 105 /** @var array End attributes of last *shifted* token */
Chris@0 106 protected $endAttributes;
Chris@0 107 /** @var array Start attributes of last *read* token */
Chris@0 108 protected $lookaheadStartAttributes;
Chris@0 109
Chris@0 110 /** @var ErrorHandler Error handler */
Chris@0 111 protected $errorHandler;
Chris@0 112 /** @var int Error state, used to avoid error floods */
Chris@0 113 protected $errorState;
Chris@0 114
Chris@0 115 /**
Chris@13 116 * Initialize $reduceCallbacks map.
Chris@13 117 */
Chris@13 118 abstract protected function initReduceCallbacks();
Chris@13 119
Chris@13 120 /**
Chris@0 121 * Creates a parser instance.
Chris@0 122 *
Chris@13 123 * Options: Currently none.
Chris@13 124 *
Chris@0 125 * @param Lexer $lexer A lexer
Chris@13 126 * @param array $options Options array.
Chris@0 127 */
Chris@13 128 public function __construct(Lexer $lexer, array $options = []) {
Chris@0 129 $this->lexer = $lexer;
Chris@0 130
Chris@0 131 if (isset($options['throwOnError'])) {
Chris@0 132 throw new \LogicException(
Chris@0 133 '"throwOnError" is no longer supported, use "errorHandler" instead');
Chris@0 134 }
Chris@13 135
Chris@13 136 $this->initReduceCallbacks();
Chris@0 137 }
Chris@0 138
Chris@0 139 /**
Chris@0 140 * Parses PHP code into a node tree.
Chris@0 141 *
Chris@0 142 * If a non-throwing error handler is used, the parser will continue parsing after an error
Chris@0 143 * occurred and attempt to build a partial AST.
Chris@0 144 *
Chris@0 145 * @param string $code The source code to parse
Chris@0 146 * @param ErrorHandler|null $errorHandler Error handler to use for lexer/parser errors, defaults
Chris@0 147 * to ErrorHandler\Throwing.
Chris@0 148 *
Chris@13 149 * @return Node\Stmt[]|null Array of statements (or null non-throwing error handler is used and
Chris@13 150 * the parser was unable to recover from an error).
Chris@0 151 */
Chris@13 152 public function parse(string $code, ErrorHandler $errorHandler = null) {
Chris@0 153 $this->errorHandler = $errorHandler ?: new ErrorHandler\Throwing;
Chris@0 154
Chris@0 155 $this->lexer->startLexing($code, $this->errorHandler);
Chris@13 156 $result = $this->doParse();
Chris@0 157
Chris@13 158 // Clear out some of the interior state, so we don't hold onto unnecessary
Chris@13 159 // memory between uses of the parser
Chris@13 160 $this->startAttributeStack = [];
Chris@13 161 $this->endAttributeStack = [];
Chris@13 162 $this->semStack = [];
Chris@13 163 $this->semValue = null;
Chris@13 164
Chris@13 165 return $result;
Chris@13 166 }
Chris@13 167
Chris@13 168 protected function doParse() {
Chris@0 169 // We start off with no lookahead-token
Chris@0 170 $symbol = self::SYMBOL_NONE;
Chris@0 171
Chris@0 172 // The attributes for a node are taken from the first and last token of the node.
Chris@0 173 // From the first token only the startAttributes are taken and from the last only
Chris@0 174 // the endAttributes. Both are merged using the array union operator (+).
Chris@13 175 $startAttributes = [];
Chris@13 176 $endAttributes = [];
Chris@0 177 $this->endAttributes = $endAttributes;
Chris@0 178
Chris@0 179 // Keep stack of start and end attributes
Chris@13 180 $this->startAttributeStack = [];
Chris@13 181 $this->endAttributeStack = [$endAttributes];
Chris@0 182
Chris@0 183 // Start off in the initial state and keep a stack of previous states
Chris@0 184 $state = 0;
Chris@13 185 $stateStack = [$state];
Chris@0 186
Chris@0 187 // Semantic value stack (contains values of tokens and semantic action results)
Chris@13 188 $this->semStack = [];
Chris@0 189
Chris@0 190 // Current position in the stack(s)
Chris@13 191 $stackPos = 0;
Chris@0 192
Chris@0 193 $this->errorState = 0;
Chris@0 194
Chris@0 195 for (;;) {
Chris@0 196 //$this->traceNewState($state, $symbol);
Chris@0 197
Chris@13 198 if ($this->actionBase[$state] === 0) {
Chris@0 199 $rule = $this->actionDefault[$state];
Chris@0 200 } else {
Chris@0 201 if ($symbol === self::SYMBOL_NONE) {
Chris@0 202 // Fetch the next token id from the lexer and fetch additional info by-ref.
Chris@0 203 // The end attributes are fetched into a temporary variable and only set once the token is really
Chris@0 204 // shifted (not during read). Otherwise you would sometimes get off-by-one errors, when a rule is
Chris@0 205 // reduced after a token was read but not yet shifted.
Chris@0 206 $tokenId = $this->lexer->getNextToken($tokenValue, $startAttributes, $endAttributes);
Chris@0 207
Chris@0 208 // map the lexer token id to the internally used symbols
Chris@0 209 $symbol = $tokenId >= 0 && $tokenId < $this->tokenToSymbolMapSize
Chris@0 210 ? $this->tokenToSymbol[$tokenId]
Chris@0 211 : $this->invalidSymbol;
Chris@0 212
Chris@0 213 if ($symbol === $this->invalidSymbol) {
Chris@0 214 throw new \RangeException(sprintf(
Chris@0 215 'The lexer returned an invalid token (id=%d, value=%s)',
Chris@0 216 $tokenId, $tokenValue
Chris@0 217 ));
Chris@0 218 }
Chris@0 219
Chris@0 220 // This is necessary to assign some meaningful attributes to /* empty */ productions. They'll get
Chris@0 221 // the attributes of the next token, even though they don't contain it themselves.
Chris@13 222 $this->startAttributeStack[$stackPos+1] = $startAttributes;
Chris@13 223 $this->endAttributeStack[$stackPos+1] = $endAttributes;
Chris@0 224 $this->lookaheadStartAttributes = $startAttributes;
Chris@0 225
Chris@0 226 //$this->traceRead($symbol);
Chris@0 227 }
Chris@0 228
Chris@0 229 $idx = $this->actionBase[$state] + $symbol;
Chris@13 230 if ((($idx >= 0 && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $symbol)
Chris@0 231 || ($state < $this->YY2TBLSTATE
Chris@13 232 && ($idx = $this->actionBase[$state + $this->numNonLeafStates] + $symbol) >= 0
Chris@13 233 && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $symbol))
Chris@13 234 && ($action = $this->action[$idx]) !== $this->defaultAction) {
Chris@0 235 /*
Chris@13 236 * >= numNonLeafStates: shift and reduce
Chris@0 237 * > 0: shift
Chris@0 238 * = 0: accept
Chris@0 239 * < 0: reduce
Chris@0 240 * = -YYUNEXPECTED: error
Chris@0 241 */
Chris@0 242 if ($action > 0) {
Chris@0 243 /* shift */
Chris@0 244 //$this->traceShift($symbol);
Chris@0 245
Chris@13 246 ++$stackPos;
Chris@13 247 $stateStack[$stackPos] = $state = $action;
Chris@13 248 $this->semStack[$stackPos] = $tokenValue;
Chris@13 249 $this->startAttributeStack[$stackPos] = $startAttributes;
Chris@13 250 $this->endAttributeStack[$stackPos] = $endAttributes;
Chris@0 251 $this->endAttributes = $endAttributes;
Chris@0 252 $symbol = self::SYMBOL_NONE;
Chris@0 253
Chris@0 254 if ($this->errorState) {
Chris@0 255 --$this->errorState;
Chris@0 256 }
Chris@0 257
Chris@13 258 if ($action < $this->numNonLeafStates) {
Chris@0 259 continue;
Chris@0 260 }
Chris@0 261
Chris@13 262 /* $yyn >= numNonLeafStates means shift-and-reduce */
Chris@13 263 $rule = $action - $this->numNonLeafStates;
Chris@0 264 } else {
Chris@0 265 $rule = -$action;
Chris@0 266 }
Chris@0 267 } else {
Chris@0 268 $rule = $this->actionDefault[$state];
Chris@0 269 }
Chris@0 270 }
Chris@0 271
Chris@0 272 for (;;) {
Chris@0 273 if ($rule === 0) {
Chris@0 274 /* accept */
Chris@0 275 //$this->traceAccept();
Chris@0 276 return $this->semValue;
Chris@0 277 } elseif ($rule !== $this->unexpectedTokenRule) {
Chris@0 278 /* reduce */
Chris@0 279 //$this->traceReduce($rule);
Chris@0 280
Chris@0 281 try {
Chris@13 282 $this->reduceCallbacks[$rule]($stackPos);
Chris@0 283 } catch (Error $e) {
Chris@0 284 if (-1 === $e->getStartLine() && isset($startAttributes['startLine'])) {
Chris@0 285 $e->setStartLine($startAttributes['startLine']);
Chris@0 286 }
Chris@0 287
Chris@0 288 $this->emitError($e);
Chris@0 289 // Can't recover from this type of error
Chris@0 290 return null;
Chris@0 291 }
Chris@0 292
Chris@0 293 /* Goto - shift nonterminal */
Chris@13 294 $lastEndAttributes = $this->endAttributeStack[$stackPos];
Chris@13 295 $stackPos -= $this->ruleToLength[$rule];
Chris@0 296 $nonTerminal = $this->ruleToNonTerminal[$rule];
Chris@13 297 $idx = $this->gotoBase[$nonTerminal] + $stateStack[$stackPos];
Chris@13 298 if ($idx >= 0 && $idx < $this->gotoTableSize && $this->gotoCheck[$idx] === $nonTerminal) {
Chris@0 299 $state = $this->goto[$idx];
Chris@0 300 } else {
Chris@0 301 $state = $this->gotoDefault[$nonTerminal];
Chris@0 302 }
Chris@0 303
Chris@13 304 ++$stackPos;
Chris@13 305 $stateStack[$stackPos] = $state;
Chris@13 306 $this->semStack[$stackPos] = $this->semValue;
Chris@13 307 $this->endAttributeStack[$stackPos] = $lastEndAttributes;
Chris@0 308 } else {
Chris@0 309 /* error */
Chris@0 310 switch ($this->errorState) {
Chris@0 311 case 0:
Chris@0 312 $msg = $this->getErrorMessage($symbol, $state);
Chris@0 313 $this->emitError(new Error($msg, $startAttributes + $endAttributes));
Chris@0 314 // Break missing intentionally
Chris@0 315 case 1:
Chris@0 316 case 2:
Chris@0 317 $this->errorState = 3;
Chris@0 318
Chris@0 319 // Pop until error-expecting state uncovered
Chris@0 320 while (!(
Chris@0 321 (($idx = $this->actionBase[$state] + $this->errorSymbol) >= 0
Chris@13 322 && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $this->errorSymbol)
Chris@0 323 || ($state < $this->YY2TBLSTATE
Chris@13 324 && ($idx = $this->actionBase[$state + $this->numNonLeafStates] + $this->errorSymbol) >= 0
Chris@13 325 && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $this->errorSymbol)
Chris@13 326 ) || ($action = $this->action[$idx]) === $this->defaultAction) { // Not totally sure about this
Chris@13 327 if ($stackPos <= 0) {
Chris@0 328 // Could not recover from error
Chris@0 329 return null;
Chris@0 330 }
Chris@13 331 $state = $stateStack[--$stackPos];
Chris@0 332 //$this->tracePop($state);
Chris@0 333 }
Chris@0 334
Chris@0 335 //$this->traceShift($this->errorSymbol);
Chris@13 336 ++$stackPos;
Chris@13 337 $stateStack[$stackPos] = $state = $action;
Chris@0 338
Chris@0 339 // We treat the error symbol as being empty, so we reset the end attributes
Chris@0 340 // to the end attributes of the last non-error symbol
Chris@13 341 $this->endAttributeStack[$stackPos] = $this->endAttributeStack[$stackPos - 1];
Chris@13 342 $this->endAttributes = $this->endAttributeStack[$stackPos - 1];
Chris@0 343 break;
Chris@0 344
Chris@0 345 case 3:
Chris@0 346 if ($symbol === 0) {
Chris@0 347 // Reached EOF without recovering from error
Chris@0 348 return null;
Chris@0 349 }
Chris@0 350
Chris@0 351 //$this->traceDiscard($symbol);
Chris@0 352 $symbol = self::SYMBOL_NONE;
Chris@0 353 break 2;
Chris@0 354 }
Chris@0 355 }
Chris@0 356
Chris@13 357 if ($state < $this->numNonLeafStates) {
Chris@0 358 break;
Chris@0 359 }
Chris@0 360
Chris@13 361 /* >= numNonLeafStates means shift-and-reduce */
Chris@13 362 $rule = $state - $this->numNonLeafStates;
Chris@0 363 }
Chris@0 364 }
Chris@0 365
Chris@0 366 throw new \RuntimeException('Reached end of parser loop');
Chris@0 367 }
Chris@0 368
Chris@0 369 protected function emitError(Error $error) {
Chris@0 370 $this->errorHandler->handleError($error);
Chris@0 371 }
Chris@0 372
Chris@13 373 /**
Chris@13 374 * Format error message including expected tokens.
Chris@13 375 *
Chris@13 376 * @param int $symbol Unexpected symbol
Chris@13 377 * @param int $state State at time of error
Chris@13 378 *
Chris@13 379 * @return string Formatted error message
Chris@13 380 */
Chris@13 381 protected function getErrorMessage(int $symbol, int $state) : string {
Chris@0 382 $expectedString = '';
Chris@0 383 if ($expected = $this->getExpectedTokens($state)) {
Chris@0 384 $expectedString = ', expecting ' . implode(' or ', $expected);
Chris@0 385 }
Chris@0 386
Chris@0 387 return 'Syntax error, unexpected ' . $this->symbolToName[$symbol] . $expectedString;
Chris@0 388 }
Chris@0 389
Chris@13 390 /**
Chris@13 391 * Get limited number of expected tokens in given state.
Chris@13 392 *
Chris@13 393 * @param int $state State
Chris@13 394 *
Chris@13 395 * @return string[] Expected tokens. If too many, an empty array is returned.
Chris@13 396 */
Chris@13 397 protected function getExpectedTokens(int $state) : array {
Chris@13 398 $expected = [];
Chris@0 399
Chris@0 400 $base = $this->actionBase[$state];
Chris@0 401 foreach ($this->symbolToName as $symbol => $name) {
Chris@0 402 $idx = $base + $symbol;
Chris@0 403 if ($idx >= 0 && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $symbol
Chris@0 404 || $state < $this->YY2TBLSTATE
Chris@13 405 && ($idx = $this->actionBase[$state + $this->numNonLeafStates] + $symbol) >= 0
Chris@0 406 && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $symbol
Chris@0 407 ) {
Chris@13 408 if ($this->action[$idx] !== $this->unexpectedTokenRule
Chris@13 409 && $this->action[$idx] !== $this->defaultAction
Chris@13 410 && $symbol !== $this->errorSymbol
Chris@0 411 ) {
Chris@13 412 if (count($expected) === 4) {
Chris@0 413 /* Too many expected tokens */
Chris@13 414 return [];
Chris@0 415 }
Chris@0 416
Chris@0 417 $expected[] = $name;
Chris@0 418 }
Chris@0 419 }
Chris@0 420 }
Chris@0 421
Chris@0 422 return $expected;
Chris@0 423 }
Chris@0 424
Chris@0 425 /*
Chris@0 426 * Tracing functions used for debugging the parser.
Chris@0 427 */
Chris@0 428
Chris@0 429 /*
Chris@0 430 protected function traceNewState($state, $symbol) {
Chris@0 431 echo '% State ' . $state
Chris@0 432 . ', Lookahead ' . ($symbol == self::SYMBOL_NONE ? '--none--' : $this->symbolToName[$symbol]) . "\n";
Chris@0 433 }
Chris@0 434
Chris@0 435 protected function traceRead($symbol) {
Chris@0 436 echo '% Reading ' . $this->symbolToName[$symbol] . "\n";
Chris@0 437 }
Chris@0 438
Chris@0 439 protected function traceShift($symbol) {
Chris@0 440 echo '% Shift ' . $this->symbolToName[$symbol] . "\n";
Chris@0 441 }
Chris@0 442
Chris@0 443 protected function traceAccept() {
Chris@0 444 echo "% Accepted.\n";
Chris@0 445 }
Chris@0 446
Chris@0 447 protected function traceReduce($n) {
Chris@0 448 echo '% Reduce by (' . $n . ') ' . $this->productions[$n] . "\n";
Chris@0 449 }
Chris@0 450
Chris@0 451 protected function tracePop($state) {
Chris@0 452 echo '% Recovering, uncovered state ' . $state . "\n";
Chris@0 453 }
Chris@0 454
Chris@0 455 protected function traceDiscard($symbol) {
Chris@0 456 echo '% Discard ' . $this->symbolToName[$symbol] . "\n";
Chris@0 457 }
Chris@0 458 */
Chris@0 459
Chris@0 460 /*
Chris@0 461 * Helper functions invoked by semantic actions
Chris@0 462 */
Chris@0 463
Chris@0 464 /**
Chris@0 465 * Moves statements of semicolon-style namespaces into $ns->stmts and checks various error conditions.
Chris@0 466 *
Chris@13 467 * @param Node\Stmt[] $stmts
Chris@13 468 * @return Node\Stmt[]
Chris@0 469 */
Chris@13 470 protected function handleNamespaces(array $stmts) : array {
Chris@0 471 $hasErrored = false;
Chris@0 472 $style = $this->getNamespacingStyle($stmts);
Chris@0 473 if (null === $style) {
Chris@0 474 // not namespaced, nothing to do
Chris@0 475 return $stmts;
Chris@0 476 } elseif ('brace' === $style) {
Chris@0 477 // For braced namespaces we only have to check that there are no invalid statements between the namespaces
Chris@0 478 $afterFirstNamespace = false;
Chris@0 479 foreach ($stmts as $stmt) {
Chris@0 480 if ($stmt instanceof Node\Stmt\Namespace_) {
Chris@0 481 $afterFirstNamespace = true;
Chris@0 482 } elseif (!$stmt instanceof Node\Stmt\HaltCompiler
Chris@0 483 && !$stmt instanceof Node\Stmt\Nop
Chris@0 484 && $afterFirstNamespace && !$hasErrored) {
Chris@0 485 $this->emitError(new Error(
Chris@0 486 'No code may exist outside of namespace {}', $stmt->getAttributes()));
Chris@0 487 $hasErrored = true; // Avoid one error for every statement
Chris@0 488 }
Chris@0 489 }
Chris@0 490 return $stmts;
Chris@0 491 } else {
Chris@0 492 // For semicolon namespaces we have to move the statements after a namespace declaration into ->stmts
Chris@13 493 $resultStmts = [];
Chris@0 494 $targetStmts =& $resultStmts;
Chris@13 495 $lastNs = null;
Chris@0 496 foreach ($stmts as $stmt) {
Chris@0 497 if ($stmt instanceof Node\Stmt\Namespace_) {
Chris@13 498 if ($lastNs !== null) {
Chris@13 499 $this->fixupNamespaceAttributes($lastNs);
Chris@13 500 }
Chris@0 501 if ($stmt->stmts === null) {
Chris@13 502 $stmt->stmts = [];
Chris@0 503 $targetStmts =& $stmt->stmts;
Chris@0 504 $resultStmts[] = $stmt;
Chris@0 505 } else {
Chris@0 506 // This handles the invalid case of mixed style namespaces
Chris@0 507 $resultStmts[] = $stmt;
Chris@0 508 $targetStmts =& $resultStmts;
Chris@0 509 }
Chris@13 510 $lastNs = $stmt;
Chris@0 511 } elseif ($stmt instanceof Node\Stmt\HaltCompiler) {
Chris@0 512 // __halt_compiler() is not moved into the namespace
Chris@0 513 $resultStmts[] = $stmt;
Chris@0 514 } else {
Chris@0 515 $targetStmts[] = $stmt;
Chris@0 516 }
Chris@0 517 }
Chris@13 518 if ($lastNs !== null) {
Chris@13 519 $this->fixupNamespaceAttributes($lastNs);
Chris@13 520 }
Chris@0 521 return $resultStmts;
Chris@0 522 }
Chris@0 523 }
Chris@0 524
Chris@13 525 private function fixupNamespaceAttributes(Node\Stmt\Namespace_ $stmt) {
Chris@13 526 // We moved the statements into the namespace node, as such the end of the namespace node
Chris@13 527 // needs to be extended to the end of the statements.
Chris@13 528 if (empty($stmt->stmts)) {
Chris@13 529 return;
Chris@13 530 }
Chris@13 531
Chris@13 532 // We only move the builtin end attributes here. This is the best we can do with the
Chris@13 533 // knowledge we have.
Chris@13 534 $endAttributes = ['endLine', 'endFilePos', 'endTokenPos'];
Chris@13 535 $lastStmt = $stmt->stmts[count($stmt->stmts) - 1];
Chris@13 536 foreach ($endAttributes as $endAttribute) {
Chris@13 537 if ($lastStmt->hasAttribute($endAttribute)) {
Chris@13 538 $stmt->setAttribute($endAttribute, $lastStmt->getAttribute($endAttribute));
Chris@13 539 }
Chris@13 540 }
Chris@13 541 }
Chris@13 542
Chris@13 543 /**
Chris@13 544 * Determine namespacing style (semicolon or brace)
Chris@13 545 *
Chris@13 546 * @param Node[] $stmts Top-level statements.
Chris@13 547 *
Chris@13 548 * @return null|string One of "semicolon", "brace" or null (no namespaces)
Chris@13 549 */
Chris@0 550 private function getNamespacingStyle(array $stmts) {
Chris@0 551 $style = null;
Chris@0 552 $hasNotAllowedStmts = false;
Chris@0 553 foreach ($stmts as $i => $stmt) {
Chris@0 554 if ($stmt instanceof Node\Stmt\Namespace_) {
Chris@0 555 $currentStyle = null === $stmt->stmts ? 'semicolon' : 'brace';
Chris@0 556 if (null === $style) {
Chris@0 557 $style = $currentStyle;
Chris@0 558 if ($hasNotAllowedStmts) {
Chris@0 559 $this->emitError(new Error(
Chris@0 560 'Namespace declaration statement has to be the very first statement in the script',
Chris@0 561 $stmt->getLine() // Avoid marking the entire namespace as an error
Chris@0 562 ));
Chris@0 563 }
Chris@0 564 } elseif ($style !== $currentStyle) {
Chris@0 565 $this->emitError(new Error(
Chris@0 566 'Cannot mix bracketed namespace declarations with unbracketed namespace declarations',
Chris@0 567 $stmt->getLine() // Avoid marking the entire namespace as an error
Chris@0 568 ));
Chris@0 569 // Treat like semicolon style for namespace normalization
Chris@0 570 return 'semicolon';
Chris@0 571 }
Chris@0 572 continue;
Chris@0 573 }
Chris@0 574
Chris@0 575 /* declare(), __halt_compiler() and nops can be used before a namespace declaration */
Chris@0 576 if ($stmt instanceof Node\Stmt\Declare_
Chris@0 577 || $stmt instanceof Node\Stmt\HaltCompiler
Chris@0 578 || $stmt instanceof Node\Stmt\Nop) {
Chris@0 579 continue;
Chris@0 580 }
Chris@0 581
Chris@0 582 /* There may be a hashbang line at the very start of the file */
Chris@13 583 if ($i === 0 && $stmt instanceof Node\Stmt\InlineHTML && preg_match('/\A#!.*\r?\n\z/', $stmt->value)) {
Chris@0 584 continue;
Chris@0 585 }
Chris@0 586
Chris@0 587 /* Everything else if forbidden before namespace declarations */
Chris@0 588 $hasNotAllowedStmts = true;
Chris@0 589 }
Chris@0 590 return $style;
Chris@0 591 }
Chris@0 592
Chris@13 593 /**
Chris@13 594 * Fix up parsing of static property calls in PHP 5.
Chris@13 595 *
Chris@13 596 * In PHP 5 A::$b[c][d] and A::$b[c][d]() have very different interpretation. The former is
Chris@13 597 * interpreted as (A::$b)[c][d], while the latter is the same as A::{$b[c][d]}(). We parse the
Chris@13 598 * latter as the former initially and this method fixes the AST into the correct form when we
Chris@13 599 * encounter the "()".
Chris@13 600 *
Chris@13 601 * @param Node\Expr\StaticPropertyFetch|Node\Expr\ArrayDimFetch $prop
Chris@13 602 * @param Node\Arg[] $args
Chris@13 603 * @param array $attributes
Chris@13 604 *
Chris@13 605 * @return Expr\StaticCall
Chris@13 606 */
Chris@13 607 protected function fixupPhp5StaticPropCall($prop, array $args, array $attributes) : Expr\StaticCall {
Chris@13 608 if ($prop instanceof Node\Expr\StaticPropertyFetch) {
Chris@13 609 $name = $prop->name instanceof VarLikeIdentifier
Chris@13 610 ? $prop->name->toString() : $prop->name;
Chris@13 611 $var = new Expr\Variable($name, $prop->name->getAttributes());
Chris@13 612 return new Expr\StaticCall($prop->class, $var, $args, $attributes);
Chris@13 613 } elseif ($prop instanceof Node\Expr\ArrayDimFetch) {
Chris@13 614 $tmp = $prop;
Chris@13 615 while ($tmp->var instanceof Node\Expr\ArrayDimFetch) {
Chris@13 616 $tmp = $tmp->var;
Chris@13 617 }
Chris@13 618
Chris@13 619 /** @var Expr\StaticPropertyFetch $staticProp */
Chris@13 620 $staticProp = $tmp->var;
Chris@13 621
Chris@13 622 // Set start attributes to attributes of innermost node
Chris@13 623 $tmp = $prop;
Chris@13 624 $this->fixupStartAttributes($tmp, $staticProp->name);
Chris@13 625 while ($tmp->var instanceof Node\Expr\ArrayDimFetch) {
Chris@13 626 $tmp = $tmp->var;
Chris@13 627 $this->fixupStartAttributes($tmp, $staticProp->name);
Chris@13 628 }
Chris@13 629
Chris@13 630 $name = $staticProp->name instanceof VarLikeIdentifier
Chris@13 631 ? $staticProp->name->toString() : $staticProp->name;
Chris@13 632 $tmp->var = new Expr\Variable($name, $staticProp->name->getAttributes());
Chris@13 633 return new Expr\StaticCall($staticProp->class, $prop, $args, $attributes);
Chris@13 634 } else {
Chris@13 635 throw new \Exception;
Chris@13 636 }
Chris@13 637 }
Chris@13 638
Chris@13 639 protected function fixupStartAttributes(Node $to, Node $from) {
Chris@13 640 $startAttributes = ['startLine', 'startFilePos', 'startTokenPos'];
Chris@13 641 foreach ($startAttributes as $startAttribute) {
Chris@13 642 if ($from->hasAttribute($startAttribute)) {
Chris@13 643 $to->setAttribute($startAttribute, $from->getAttribute($startAttribute));
Chris@13 644 }
Chris@13 645 }
Chris@13 646 }
Chris@13 647
Chris@0 648 protected function handleBuiltinTypes(Name $name) {
Chris@0 649 $scalarTypes = [
Chris@0 650 'bool' => true,
Chris@0 651 'int' => true,
Chris@0 652 'float' => true,
Chris@0 653 'string' => true,
Chris@0 654 'iterable' => true,
Chris@0 655 'void' => true,
Chris@0 656 'object' => true,
Chris@0 657 ];
Chris@0 658
Chris@0 659 if (!$name->isUnqualified()) {
Chris@0 660 return $name;
Chris@0 661 }
Chris@0 662
Chris@13 663 $lowerName = $name->toLowerString();
Chris@13 664 if (!isset($scalarTypes[$lowerName])) {
Chris@13 665 return $name;
Chris@13 666 }
Chris@13 667
Chris@13 668 return new Node\Identifier($lowerName, $name->getAttributes());
Chris@0 669 }
Chris@0 670
Chris@13 671 /**
Chris@13 672 * Get combined start and end attributes at a stack location
Chris@13 673 *
Chris@13 674 * @param int $pos Stack location
Chris@13 675 *
Chris@13 676 * @return array Combined start and end attributes
Chris@13 677 */
Chris@13 678 protected function getAttributesAt(int $pos) : array {
Chris@0 679 return $this->startAttributeStack[$pos] + $this->endAttributeStack[$pos];
Chris@0 680 }
Chris@0 681
Chris@0 682 protected function parseLNumber($str, $attributes, $allowInvalidOctal = false) {
Chris@0 683 try {
Chris@0 684 return LNumber::fromString($str, $attributes, $allowInvalidOctal);
Chris@0 685 } catch (Error $error) {
Chris@0 686 $this->emitError($error);
Chris@0 687 // Use dummy value
Chris@0 688 return new LNumber(0, $attributes);
Chris@0 689 }
Chris@0 690 }
Chris@0 691
Chris@13 692 /**
Chris@13 693 * Parse a T_NUM_STRING token into either an integer or string node.
Chris@13 694 *
Chris@13 695 * @param string $str Number string
Chris@13 696 * @param array $attributes Attributes
Chris@13 697 *
Chris@13 698 * @return LNumber|String_ Integer or string node.
Chris@13 699 */
Chris@13 700 protected function parseNumString(string $str, array $attributes) {
Chris@0 701 if (!preg_match('/^(?:0|-?[1-9][0-9]*)$/', $str)) {
Chris@0 702 return new String_($str, $attributes);
Chris@0 703 }
Chris@0 704
Chris@0 705 $num = +$str;
Chris@0 706 if (!is_int($num)) {
Chris@0 707 return new String_($str, $attributes);
Chris@0 708 }
Chris@0 709
Chris@0 710 return new LNumber($num, $attributes);
Chris@0 711 }
Chris@0 712
Chris@0 713 protected function checkModifier($a, $b, $modifierPos) {
Chris@0 714 // Jumping through some hoops here because verifyModifier() is also used elsewhere
Chris@0 715 try {
Chris@0 716 Class_::verifyModifier($a, $b);
Chris@0 717 } catch (Error $error) {
Chris@0 718 $error->setAttributes($this->getAttributesAt($modifierPos));
Chris@0 719 $this->emitError($error);
Chris@0 720 }
Chris@0 721 }
Chris@0 722
Chris@0 723 protected function checkParam(Param $node) {
Chris@0 724 if ($node->variadic && null !== $node->default) {
Chris@0 725 $this->emitError(new Error(
Chris@0 726 'Variadic parameter cannot have a default value',
Chris@0 727 $node->default->getAttributes()
Chris@0 728 ));
Chris@0 729 }
Chris@0 730 }
Chris@0 731
Chris@0 732 protected function checkTryCatch(TryCatch $node) {
Chris@0 733 if (empty($node->catches) && null === $node->finally) {
Chris@0 734 $this->emitError(new Error(
Chris@0 735 'Cannot use try without catch or finally', $node->getAttributes()
Chris@0 736 ));
Chris@0 737 }
Chris@0 738 }
Chris@0 739
Chris@0 740 protected function checkNamespace(Namespace_ $node) {
Chris@13 741 if ($node->name && $node->name->isSpecialClassName()) {
Chris@0 742 $this->emitError(new Error(
Chris@0 743 sprintf('Cannot use \'%s\' as namespace name', $node->name),
Chris@0 744 $node->name->getAttributes()
Chris@0 745 ));
Chris@0 746 }
Chris@0 747
Chris@0 748 if (null !== $node->stmts) {
Chris@0 749 foreach ($node->stmts as $stmt) {
Chris@0 750 if ($stmt instanceof Namespace_) {
Chris@0 751 $this->emitError(new Error(
Chris@0 752 'Namespace declarations cannot be nested', $stmt->getAttributes()
Chris@0 753 ));
Chris@0 754 }
Chris@0 755 }
Chris@0 756 }
Chris@0 757 }
Chris@0 758
Chris@0 759 protected function checkClass(Class_ $node, $namePos) {
Chris@13 760 if (null !== $node->name && $node->name->isSpecialClassName()) {
Chris@0 761 $this->emitError(new Error(
Chris@0 762 sprintf('Cannot use \'%s\' as class name as it is reserved', $node->name),
Chris@0 763 $this->getAttributesAt($namePos)
Chris@0 764 ));
Chris@0 765 }
Chris@0 766
Chris@13 767 if ($node->extends && $node->extends->isSpecialClassName()) {
Chris@0 768 $this->emitError(new Error(
Chris@0 769 sprintf('Cannot use \'%s\' as class name as it is reserved', $node->extends),
Chris@0 770 $node->extends->getAttributes()
Chris@0 771 ));
Chris@0 772 }
Chris@0 773
Chris@0 774 foreach ($node->implements as $interface) {
Chris@13 775 if ($interface->isSpecialClassName()) {
Chris@0 776 $this->emitError(new Error(
Chris@0 777 sprintf('Cannot use \'%s\' as interface name as it is reserved', $interface),
Chris@0 778 $interface->getAttributes()
Chris@0 779 ));
Chris@0 780 }
Chris@0 781 }
Chris@0 782 }
Chris@0 783
Chris@0 784 protected function checkInterface(Interface_ $node, $namePos) {
Chris@13 785 if (null !== $node->name && $node->name->isSpecialClassName()) {
Chris@0 786 $this->emitError(new Error(
Chris@0 787 sprintf('Cannot use \'%s\' as class name as it is reserved', $node->name),
Chris@0 788 $this->getAttributesAt($namePos)
Chris@0 789 ));
Chris@0 790 }
Chris@0 791
Chris@0 792 foreach ($node->extends as $interface) {
Chris@13 793 if ($interface->isSpecialClassName()) {
Chris@0 794 $this->emitError(new Error(
Chris@0 795 sprintf('Cannot use \'%s\' as interface name as it is reserved', $interface),
Chris@0 796 $interface->getAttributes()
Chris@0 797 ));
Chris@0 798 }
Chris@0 799 }
Chris@0 800 }
Chris@0 801
Chris@0 802 protected function checkClassMethod(ClassMethod $node, $modifierPos) {
Chris@0 803 if ($node->flags & Class_::MODIFIER_STATIC) {
Chris@13 804 switch ($node->name->toLowerString()) {
Chris@0 805 case '__construct':
Chris@0 806 $this->emitError(new Error(
Chris@0 807 sprintf('Constructor %s() cannot be static', $node->name),
Chris@0 808 $this->getAttributesAt($modifierPos)));
Chris@0 809 break;
Chris@0 810 case '__destruct':
Chris@0 811 $this->emitError(new Error(
Chris@0 812 sprintf('Destructor %s() cannot be static', $node->name),
Chris@0 813 $this->getAttributesAt($modifierPos)));
Chris@0 814 break;
Chris@0 815 case '__clone':
Chris@0 816 $this->emitError(new Error(
Chris@0 817 sprintf('Clone method %s() cannot be static', $node->name),
Chris@0 818 $this->getAttributesAt($modifierPos)));
Chris@0 819 break;
Chris@0 820 }
Chris@0 821 }
Chris@0 822 }
Chris@0 823
Chris@0 824 protected function checkClassConst(ClassConst $node, $modifierPos) {
Chris@0 825 if ($node->flags & Class_::MODIFIER_STATIC) {
Chris@0 826 $this->emitError(new Error(
Chris@0 827 "Cannot use 'static' as constant modifier",
Chris@0 828 $this->getAttributesAt($modifierPos)));
Chris@0 829 }
Chris@0 830 if ($node->flags & Class_::MODIFIER_ABSTRACT) {
Chris@0 831 $this->emitError(new Error(
Chris@0 832 "Cannot use 'abstract' as constant modifier",
Chris@0 833 $this->getAttributesAt($modifierPos)));
Chris@0 834 }
Chris@0 835 if ($node->flags & Class_::MODIFIER_FINAL) {
Chris@0 836 $this->emitError(new Error(
Chris@0 837 "Cannot use 'final' as constant modifier",
Chris@0 838 $this->getAttributesAt($modifierPos)));
Chris@0 839 }
Chris@0 840 }
Chris@0 841
Chris@0 842 protected function checkProperty(Property $node, $modifierPos) {
Chris@0 843 if ($node->flags & Class_::MODIFIER_ABSTRACT) {
Chris@0 844 $this->emitError(new Error('Properties cannot be declared abstract',
Chris@0 845 $this->getAttributesAt($modifierPos)));
Chris@0 846 }
Chris@0 847
Chris@0 848 if ($node->flags & Class_::MODIFIER_FINAL) {
Chris@0 849 $this->emitError(new Error('Properties cannot be declared final',
Chris@0 850 $this->getAttributesAt($modifierPos)));
Chris@0 851 }
Chris@0 852 }
Chris@0 853
Chris@0 854 protected function checkUseUse(UseUse $node, $namePos) {
Chris@13 855 if ($node->alias && $node->alias->isSpecialClassName()) {
Chris@0 856 $this->emitError(new Error(
Chris@0 857 sprintf(
Chris@0 858 'Cannot use %s as %s because \'%2$s\' is a special class name',
Chris@0 859 $node->name, $node->alias
Chris@0 860 ),
Chris@0 861 $this->getAttributesAt($namePos)
Chris@0 862 ));
Chris@0 863 }
Chris@0 864 }
Chris@0 865 }