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