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