annotate vendor/nikic/php-parser/lib/PhpParser/ParserAbstract.php @ 0:4c8ae668cc8c

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