summaryrefslogtreecommitdiff
path: root/vendor/nikic/php-parser/lib/PhpParser/Lexer
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/nikic/php-parser/lib/PhpParser/Lexer')
-rw-r--r--vendor/nikic/php-parser/lib/PhpParser/Lexer/Emulative.php248
-rw-r--r--vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/AttributeEmulator.php56
-rw-r--r--vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/CoaleseEqualTokenEmulator.php47
-rw-r--r--vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/EnumTokenEmulator.php31
-rw-r--r--vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ExplicitOctalEmulator.php44
-rw-r--r--vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/FlexibleDocStringEmulator.php76
-rw-r--r--vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/FnTokenEmulator.php23
-rw-r--r--vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/KeywordEmulator.php62
-rw-r--r--vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/MatchTokenEmulator.php23
-rw-r--r--vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/NullsafeTokenEmulator.php67
-rw-r--r--vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/NumericLiteralSeparatorEmulator.php105
-rw-r--r--vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ReadonlyTokenEmulator.php23
-rw-r--r--vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ReverseEmulator.php36
-rw-r--r--vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/TokenEmulator.php25
14 files changed, 866 insertions, 0 deletions
diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer/Emulative.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer/Emulative.php
new file mode 100644
index 000000000..5c56e026b
--- /dev/null
+++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer/Emulative.php
@@ -0,0 +1,248 @@
+<?php declare(strict_types=1);
+
+namespace PhpParser\Lexer;
+
+use PhpParser\Error;
+use PhpParser\ErrorHandler;
+use PhpParser\Lexer;
+use PhpParser\Lexer\TokenEmulator\AttributeEmulator;
+use PhpParser\Lexer\TokenEmulator\EnumTokenEmulator;
+use PhpParser\Lexer\TokenEmulator\CoaleseEqualTokenEmulator;
+use PhpParser\Lexer\TokenEmulator\ExplicitOctalEmulator;
+use PhpParser\Lexer\TokenEmulator\FlexibleDocStringEmulator;
+use PhpParser\Lexer\TokenEmulator\FnTokenEmulator;
+use PhpParser\Lexer\TokenEmulator\MatchTokenEmulator;
+use PhpParser\Lexer\TokenEmulator\NullsafeTokenEmulator;
+use PhpParser\Lexer\TokenEmulator\NumericLiteralSeparatorEmulator;
+use PhpParser\Lexer\TokenEmulator\ReadonlyTokenEmulator;
+use PhpParser\Lexer\TokenEmulator\ReverseEmulator;
+use PhpParser\Lexer\TokenEmulator\TokenEmulator;
+
+class Emulative extends Lexer
+{
+ const PHP_7_3 = '7.3dev';
+ const PHP_7_4 = '7.4dev';
+ const PHP_8_0 = '8.0dev';
+ const PHP_8_1 = '8.1dev';
+
+ /** @var mixed[] Patches used to reverse changes introduced in the code */
+ private $patches = [];
+
+ /** @var TokenEmulator[] */
+ private $emulators = [];
+
+ /** @var string */
+ private $targetPhpVersion;
+
+ /**
+ * @param mixed[] $options Lexer options. In addition to the usual options,
+ * accepts a 'phpVersion' string that specifies the
+ * version to emulate. Defaults to newest supported.
+ */
+ public function __construct(array $options = [])
+ {
+ $this->targetPhpVersion = $options['phpVersion'] ?? Emulative::PHP_8_1;
+ unset($options['phpVersion']);
+
+ parent::__construct($options);
+
+ $emulators = [
+ new FlexibleDocStringEmulator(),
+ new FnTokenEmulator(),
+ new MatchTokenEmulator(),
+ new CoaleseEqualTokenEmulator(),
+ new NumericLiteralSeparatorEmulator(),
+ new NullsafeTokenEmulator(),
+ new AttributeEmulator(),
+ new EnumTokenEmulator(),
+ new ReadonlyTokenEmulator(),
+ new ExplicitOctalEmulator(),
+ ];
+
+ // Collect emulators that are relevant for the PHP version we're running
+ // and the PHP version we're targeting for emulation.
+ foreach ($emulators as $emulator) {
+ $emulatorPhpVersion = $emulator->getPhpVersion();
+ if ($this->isForwardEmulationNeeded($emulatorPhpVersion)) {
+ $this->emulators[] = $emulator;
+ } else if ($this->isReverseEmulationNeeded($emulatorPhpVersion)) {
+ $this->emulators[] = new ReverseEmulator($emulator);
+ }
+ }
+ }
+
+ public function startLexing(string $code, ErrorHandler $errorHandler = null) {
+ $emulators = array_filter($this->emulators, function($emulator) use($code) {
+ return $emulator->isEmulationNeeded($code);
+ });
+
+ if (empty($emulators)) {
+ // Nothing to emulate, yay
+ parent::startLexing($code, $errorHandler);
+ return;
+ }
+
+ $this->patches = [];
+ foreach ($emulators as $emulator) {
+ $code = $emulator->preprocessCode($code, $this->patches);
+ }
+
+ $collector = new ErrorHandler\Collecting();
+ parent::startLexing($code, $collector);
+ $this->sortPatches();
+ $this->fixupTokens();
+
+ $errors = $collector->getErrors();
+ if (!empty($errors)) {
+ $this->fixupErrors($errors);
+ foreach ($errors as $error) {
+ $errorHandler->handleError($error);
+ }
+ }
+
+ foreach ($emulators as $emulator) {
+ $this->tokens = $emulator->emulate($code, $this->tokens);
+ }
+ }
+
+ private function isForwardEmulationNeeded(string $emulatorPhpVersion): bool {
+ return version_compare(\PHP_VERSION, $emulatorPhpVersion, '<')
+ && version_compare($this->targetPhpVersion, $emulatorPhpVersion, '>=');
+ }
+
+ private function isReverseEmulationNeeded(string $emulatorPhpVersion): bool {
+ return version_compare(\PHP_VERSION, $emulatorPhpVersion, '>=')
+ && version_compare($this->targetPhpVersion, $emulatorPhpVersion, '<');
+ }
+
+ private function sortPatches()
+ {
+ // Patches may be contributed by different emulators.
+ // Make sure they are sorted by increasing patch position.
+ usort($this->patches, function($p1, $p2) {
+ return $p1[0] <=> $p2[0];
+ });
+ }
+
+ private function fixupTokens()
+ {
+ if (\count($this->patches) === 0) {
+ return;
+ }
+
+ // Load first patch
+ $patchIdx = 0;
+
+ list($patchPos, $patchType, $patchText) = $this->patches[$patchIdx];
+
+ // We use a manual loop over the tokens, because we modify the array on the fly
+ $pos = 0;
+ for ($i = 0, $c = \count($this->tokens); $i < $c; $i++) {
+ $token = $this->tokens[$i];
+ if (\is_string($token)) {
+ if ($patchPos === $pos) {
+ // Only support replacement for string tokens.
+ assert($patchType === 'replace');
+ $this->tokens[$i] = $patchText;
+
+ // Fetch the next patch
+ $patchIdx++;
+ if ($patchIdx >= \count($this->patches)) {
+ // No more patches, we're done
+ return;
+ }
+ list($patchPos, $patchType, $patchText) = $this->patches[$patchIdx];
+ }
+
+ $pos += \strlen($token);
+ continue;
+ }
+
+ $len = \strlen($token[1]);
+ $posDelta = 0;
+ while ($patchPos >= $pos && $patchPos < $pos + $len) {
+ $patchTextLen = \strlen($patchText);
+ if ($patchType === 'remove') {
+ if ($patchPos === $pos && $patchTextLen === $len) {
+ // Remove token entirely
+ array_splice($this->tokens, $i, 1, []);
+ $i--;
+ $c--;
+ } else {
+ // Remove from token string
+ $this->tokens[$i][1] = substr_replace(
+ $token[1], '', $patchPos - $pos + $posDelta, $patchTextLen
+ );
+ $posDelta -= $patchTextLen;
+ }
+ } elseif ($patchType === 'add') {
+ // Insert into the token string
+ $this->tokens[$i][1] = substr_replace(
+ $token[1], $patchText, $patchPos - $pos + $posDelta, 0
+ );
+ $posDelta += $patchTextLen;
+ } else if ($patchType === 'replace') {
+ // Replace inside the token string
+ $this->tokens[$i][1] = substr_replace(
+ $token[1], $patchText, $patchPos - $pos + $posDelta, $patchTextLen
+ );
+ } else {
+ assert(false);
+ }
+
+ // Fetch the next patch
+ $patchIdx++;
+ if ($patchIdx >= \count($this->patches)) {
+ // No more patches, we're done
+ return;
+ }
+
+ list($patchPos, $patchType, $patchText) = $this->patches[$patchIdx];
+
+ // Multiple patches may apply to the same token. Reload the current one to check
+ // If the new patch applies
+ $token = $this->tokens[$i];
+ }
+
+ $pos += $len;
+ }
+
+ // A patch did not apply
+ assert(false);
+ }
+
+ /**
+ * Fixup line and position information in errors.
+ *
+ * @param Error[] $errors
+ */
+ private function fixupErrors(array $errors) {
+ foreach ($errors as $error) {
+ $attrs = $error->getAttributes();
+
+ $posDelta = 0;
+ $lineDelta = 0;
+ foreach ($this->patches as $patch) {
+ list($patchPos, $patchType, $patchText) = $patch;
+ if ($patchPos >= $attrs['startFilePos']) {
+ // No longer relevant
+ break;
+ }
+
+ if ($patchType === 'add') {
+ $posDelta += strlen($patchText);
+ $lineDelta += substr_count($patchText, "\n");
+ } else if ($patchType === 'remove') {
+ $posDelta -= strlen($patchText);
+ $lineDelta -= substr_count($patchText, "\n");
+ }
+ }
+
+ $attrs['startFilePos'] += $posDelta;
+ $attrs['endFilePos'] += $posDelta;
+ $attrs['startLine'] += $lineDelta;
+ $attrs['endLine'] += $lineDelta;
+ $error->setAttributes($attrs);
+ }
+ }
+}
diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/AttributeEmulator.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/AttributeEmulator.php
new file mode 100644
index 000000000..6776a5197
--- /dev/null
+++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/AttributeEmulator.php
@@ -0,0 +1,56 @@
+<?php declare(strict_types=1);
+
+namespace PhpParser\Lexer\TokenEmulator;
+
+use PhpParser\Lexer\Emulative;
+
+final class AttributeEmulator extends TokenEmulator
+{
+ public function getPhpVersion(): string
+ {
+ return Emulative::PHP_8_0;
+ }
+
+ public function isEmulationNeeded(string $code) : bool
+ {
+ return strpos($code, '#[') !== false;
+ }
+
+ public function emulate(string $code, array $tokens): array
+ {
+ // We need to manually iterate and manage a count because we'll change
+ // the tokens array on the way.
+ $line = 1;
+ for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
+ if ($tokens[$i] === '#' && isset($tokens[$i + 1]) && $tokens[$i + 1] === '[') {
+ array_splice($tokens, $i, 2, [
+ [\T_ATTRIBUTE, '#[', $line]
+ ]);
+ $c--;
+ continue;
+ }
+ if (\is_array($tokens[$i])) {
+ $line += substr_count($tokens[$i][1], "\n");
+ }
+ }
+
+ return $tokens;
+ }
+
+ public function reverseEmulate(string $code, array $tokens): array
+ {
+ // TODO
+ return $tokens;
+ }
+
+ public function preprocessCode(string $code, array &$patches): string {
+ $pos = 0;
+ while (false !== $pos = strpos($code, '#[', $pos)) {
+ // Replace #[ with %[
+ $code[$pos] = '%';
+ $patches[] = [$pos, 'replace', '#'];
+ $pos += 2;
+ }
+ return $code;
+ }
+}
diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/CoaleseEqualTokenEmulator.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/CoaleseEqualTokenEmulator.php
new file mode 100644
index 000000000..d91da9214
--- /dev/null
+++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/CoaleseEqualTokenEmulator.php
@@ -0,0 +1,47 @@
+<?php declare(strict_types=1);
+
+namespace PhpParser\Lexer\TokenEmulator;
+
+use PhpParser\Lexer\Emulative;
+
+final class CoaleseEqualTokenEmulator extends TokenEmulator
+{
+ public function getPhpVersion(): string
+ {
+ return Emulative::PHP_7_4;
+ }
+
+ public function isEmulationNeeded(string $code): bool
+ {
+ return strpos($code, '??=') !== false;
+ }
+
+ public function emulate(string $code, array $tokens): array
+ {
+ // We need to manually iterate and manage a count because we'll change
+ // the tokens array on the way
+ $line = 1;
+ for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
+ if (isset($tokens[$i + 1])) {
+ if ($tokens[$i][0] === T_COALESCE && $tokens[$i + 1] === '=') {
+ array_splice($tokens, $i, 2, [
+ [\T_COALESCE_EQUAL, '??=', $line]
+ ]);
+ $c--;
+ continue;
+ }
+ }
+ if (\is_array($tokens[$i])) {
+ $line += substr_count($tokens[$i][1], "\n");
+ }
+ }
+
+ return $tokens;
+ }
+
+ public function reverseEmulate(string $code, array $tokens): array
+ {
+ // ??= was not valid code previously, don't bother.
+ return $tokens;
+ }
+}
diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/EnumTokenEmulator.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/EnumTokenEmulator.php
new file mode 100644
index 000000000..4ddc0b17e
--- /dev/null
+++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/EnumTokenEmulator.php
@@ -0,0 +1,31 @@
+<?php declare(strict_types=1);
+
+namespace PhpParser\Lexer\TokenEmulator;
+
+use PhpParser\Lexer\Emulative;
+
+final class EnumTokenEmulator extends KeywordEmulator
+{
+ public function getPhpVersion(): string
+ {
+ return Emulative::PHP_8_1;
+ }
+
+ public function getKeywordString(): string
+ {
+ return 'enum';
+ }
+
+ public function getKeywordToken(): int
+ {
+ return \T_ENUM;
+ }
+
+ protected function isKeywordContext(array $tokens, int $pos): bool
+ {
+ return parent::isKeywordContext($tokens, $pos)
+ && isset($tokens[$pos + 2])
+ && $tokens[$pos + 1][0] === \T_WHITESPACE
+ && $tokens[$pos + 2][0] === \T_STRING;
+ }
+} \ No newline at end of file
diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ExplicitOctalEmulator.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ExplicitOctalEmulator.php
new file mode 100644
index 000000000..f5f6805b8
--- /dev/null
+++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ExplicitOctalEmulator.php
@@ -0,0 +1,44 @@
+<?php declare(strict_types=1);
+
+namespace PhpParser\Lexer\TokenEmulator;
+
+use PhpParser\Lexer\Emulative;
+
+class ExplicitOctalEmulator extends TokenEmulator {
+ public function getPhpVersion(): string {
+ return Emulative::PHP_8_1;
+ }
+
+ public function isEmulationNeeded(string $code): bool {
+ return strpos($code, '0o') !== false || strpos($code, '0O') !== false;
+ }
+
+ public function emulate(string $code, array $tokens): array {
+ for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
+ if ($tokens[$i][0] == \T_LNUMBER && $tokens[$i][1] === '0' &&
+ isset($tokens[$i + 1]) && $tokens[$i + 1][0] == \T_STRING &&
+ preg_match('/[oO][0-7]+(?:_[0-7]+)*/', $tokens[$i + 1][1])
+ ) {
+ $tokenKind = $this->resolveIntegerOrFloatToken($tokens[$i + 1][1]);
+ array_splice($tokens, $i, 2, [
+ [$tokenKind, '0' . $tokens[$i + 1][1], $tokens[$i][2]],
+ ]);
+ $c--;
+ }
+ }
+ return $tokens;
+ }
+
+ private function resolveIntegerOrFloatToken(string $str): int
+ {
+ $str = substr($str, 1);
+ $str = str_replace('_', '', $str);
+ $num = octdec($str);
+ return is_float($num) ? \T_DNUMBER : \T_LNUMBER;
+ }
+
+ public function reverseEmulate(string $code, array $tokens): array {
+ // Explicit octals were not legal code previously, don't bother.
+ return $tokens;
+ }
+} \ No newline at end of file
diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/FlexibleDocStringEmulator.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/FlexibleDocStringEmulator.php
new file mode 100644
index 000000000..c15d6271f
--- /dev/null
+++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/FlexibleDocStringEmulator.php
@@ -0,0 +1,76 @@
+<?php declare(strict_types=1);
+
+namespace PhpParser\Lexer\TokenEmulator;
+
+use PhpParser\Lexer\Emulative;
+
+final class FlexibleDocStringEmulator extends TokenEmulator
+{
+ const FLEXIBLE_DOC_STRING_REGEX = <<<'REGEX'
+/<<<[ \t]*(['"]?)([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)\1\r?\n
+(?:.*\r?\n)*?
+(?<indentation>\h*)\2(?![a-zA-Z0-9_\x80-\xff])(?<separator>(?:;?[\r\n])?)/x
+REGEX;
+
+ public function getPhpVersion(): string
+ {
+ return Emulative::PHP_7_3;
+ }
+
+ public function isEmulationNeeded(string $code) : bool
+ {
+ return strpos($code, '<<<') !== false;
+ }
+
+ public function emulate(string $code, array $tokens): array
+ {
+ // Handled by preprocessing + fixup.
+ return $tokens;
+ }
+
+ public function reverseEmulate(string $code, array $tokens): array
+ {
+ // Not supported.
+ return $tokens;
+ }
+
+ public function preprocessCode(string $code, array &$patches): string {
+ if (!preg_match_all(self::FLEXIBLE_DOC_STRING_REGEX, $code, $matches, PREG_SET_ORDER|PREG_OFFSET_CAPTURE)) {
+ // No heredoc/nowdoc found
+ return $code;
+ }
+
+ // Keep track of how much we need to adjust string offsets due to the modifications we
+ // already made
+ $posDelta = 0;
+ foreach ($matches as $match) {
+ $indentation = $match['indentation'][0];
+ $indentationStart = $match['indentation'][1];
+
+ $separator = $match['separator'][0];
+ $separatorStart = $match['separator'][1];
+
+ if ($indentation === '' && $separator !== '') {
+ // Ordinary heredoc/nowdoc
+ continue;
+ }
+
+ if ($indentation !== '') {
+ // Remove indentation
+ $indentationLen = strlen($indentation);
+ $code = substr_replace($code, '', $indentationStart + $posDelta, $indentationLen);
+ $patches[] = [$indentationStart + $posDelta, 'add', $indentation];
+ $posDelta -= $indentationLen;
+ }
+
+ if ($separator === '') {
+ // Insert newline as separator
+ $code = substr_replace($code, "\n", $separatorStart + $posDelta, 0);
+ $patches[] = [$separatorStart + $posDelta, 'remove', "\n"];
+ $posDelta += 1;
+ }
+ }
+
+ return $code;
+ }
+}
diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/FnTokenEmulator.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/FnTokenEmulator.php
new file mode 100644
index 000000000..eb7e49634
--- /dev/null
+++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/FnTokenEmulator.php
@@ -0,0 +1,23 @@
+<?php declare(strict_types=1);
+
+namespace PhpParser\Lexer\TokenEmulator;
+
+use PhpParser\Lexer\Emulative;
+
+final class FnTokenEmulator extends KeywordEmulator
+{
+ public function getPhpVersion(): string
+ {
+ return Emulative::PHP_7_4;
+ }
+
+ public function getKeywordString(): string
+ {
+ return 'fn';
+ }
+
+ public function getKeywordToken(): int
+ {
+ return \T_FN;
+ }
+} \ No newline at end of file
diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/KeywordEmulator.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/KeywordEmulator.php
new file mode 100644
index 000000000..ea261cc17
--- /dev/null
+++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/KeywordEmulator.php
@@ -0,0 +1,62 @@
+<?php declare(strict_types=1);
+
+namespace PhpParser\Lexer\TokenEmulator;
+
+abstract class KeywordEmulator extends TokenEmulator
+{
+ abstract function getKeywordString(): string;
+ abstract function getKeywordToken(): int;
+
+ public function isEmulationNeeded(string $code): bool
+ {
+ return strpos(strtolower($code), $this->getKeywordString()) !== false;
+ }
+
+ protected function isKeywordContext(array $tokens, int $pos): bool
+ {
+ $previousNonSpaceToken = $this->getPreviousNonSpaceToken($tokens, $pos);
+ return $previousNonSpaceToken === null || $previousNonSpaceToken[0] !== \T_OBJECT_OPERATOR;
+ }
+
+ public function emulate(string $code, array $tokens): array
+ {
+ $keywordString = $this->getKeywordString();
+ foreach ($tokens as $i => $token) {
+ if ($token[0] === T_STRING && strtolower($token[1]) === $keywordString
+ && $this->isKeywordContext($tokens, $i)) {
+ $tokens[$i][0] = $this->getKeywordToken();
+ }
+ }
+
+ return $tokens;
+ }
+
+ /**
+ * @param mixed[] $tokens
+ * @return mixed[]|null
+ */
+ private function getPreviousNonSpaceToken(array $tokens, int $start)
+ {
+ for ($i = $start - 1; $i >= 0; --$i) {
+ if ($tokens[$i][0] === T_WHITESPACE) {
+ continue;
+ }
+
+ return $tokens[$i];
+ }
+
+ return null;
+ }
+
+ public function reverseEmulate(string $code, array $tokens): array
+ {
+ $keywordToken = $this->getKeywordToken();
+ foreach ($tokens as $i => $token) {
+ if ($token[0] === $keywordToken) {
+ $tokens[$i][0] = \T_STRING;
+ }
+ }
+
+ return $tokens;
+ }
+}
diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/MatchTokenEmulator.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/MatchTokenEmulator.php
new file mode 100644
index 000000000..902a46dfc
--- /dev/null
+++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/MatchTokenEmulator.php
@@ -0,0 +1,23 @@
+<?php declare(strict_types=1);
+
+namespace PhpParser\Lexer\TokenEmulator;
+
+use PhpParser\Lexer\Emulative;
+
+final class MatchTokenEmulator extends KeywordEmulator
+{
+ public function getPhpVersion(): string
+ {
+ return Emulative::PHP_8_0;
+ }
+
+ public function getKeywordString(): string
+ {
+ return 'match';
+ }
+
+ public function getKeywordToken(): int
+ {
+ return \T_MATCH;
+ }
+}
diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/NullsafeTokenEmulator.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/NullsafeTokenEmulator.php
new file mode 100644
index 000000000..1a29c676e
--- /dev/null
+++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/NullsafeTokenEmulator.php
@@ -0,0 +1,67 @@
+<?php declare(strict_types=1);
+
+namespace PhpParser\Lexer\TokenEmulator;
+
+use PhpParser\Lexer\Emulative;
+
+final class NullsafeTokenEmulator extends TokenEmulator
+{
+ public function getPhpVersion(): string
+ {
+ return Emulative::PHP_8_0;
+ }
+
+ public function isEmulationNeeded(string $code): bool
+ {
+ return strpos($code, '?->') !== false;
+ }
+
+ public function emulate(string $code, array $tokens): array
+ {
+ // We need to manually iterate and manage a count because we'll change
+ // the tokens array on the way
+ $line = 1;
+ for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
+ if ($tokens[$i] === '?' && isset($tokens[$i + 1]) && $tokens[$i + 1][0] === \T_OBJECT_OPERATOR) {
+ array_splice($tokens, $i, 2, [
+ [\T_NULLSAFE_OBJECT_OPERATOR, '?->', $line]
+ ]);
+ $c--;
+ continue;
+ }
+
+ // Handle ?-> inside encapsed string.
+ if ($tokens[$i][0] === \T_ENCAPSED_AND_WHITESPACE && isset($tokens[$i - 1])
+ && $tokens[$i - 1][0] === \T_VARIABLE
+ && preg_match('/^\?->([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)/', $tokens[$i][1], $matches)
+ ) {
+ $replacement = [
+ [\T_NULLSAFE_OBJECT_OPERATOR, '?->', $line],
+ [\T_STRING, $matches[1], $line],
+ ];
+ if (\strlen($matches[0]) !== \strlen($tokens[$i][1])) {
+ $replacement[] = [
+ \T_ENCAPSED_AND_WHITESPACE,
+ \substr($tokens[$i][1], \strlen($matches[0])),
+ $line
+ ];
+ }
+ array_splice($tokens, $i, 1, $replacement);
+ $c += \count($replacement) - 1;
+ continue;
+ }
+
+ if (\is_array($tokens[$i])) {
+ $line += substr_count($tokens[$i][1], "\n");
+ }
+ }
+
+ return $tokens;
+ }
+
+ public function reverseEmulate(string $code, array $tokens): array
+ {
+ // ?-> was not valid code previously, don't bother.
+ return $tokens;
+ }
+}
diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/NumericLiteralSeparatorEmulator.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/NumericLiteralSeparatorEmulator.php
new file mode 100644
index 000000000..cdf793e46
--- /dev/null
+++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/NumericLiteralSeparatorEmulator.php
@@ -0,0 +1,105 @@
+<?php declare(strict_types=1);
+
+namespace PhpParser\Lexer\TokenEmulator;
+
+use PhpParser\Lexer\Emulative;
+
+final class NumericLiteralSeparatorEmulator extends TokenEmulator
+{
+ const BIN = '(?:0b[01]+(?:_[01]+)*)';
+ const HEX = '(?:0x[0-9a-f]+(?:_[0-9a-f]+)*)';
+ const DEC = '(?:[0-9]+(?:_[0-9]+)*)';
+ const SIMPLE_FLOAT = '(?:' . self::DEC . '\.' . self::DEC . '?|\.' . self::DEC . ')';
+ const EXP = '(?:e[+-]?' . self::DEC . ')';
+ const FLOAT = '(?:' . self::SIMPLE_FLOAT . self::EXP . '?|' . self::DEC . self::EXP . ')';
+ const NUMBER = '~' . self::FLOAT . '|' . self::BIN . '|' . self::HEX . '|' . self::DEC . '~iA';
+
+ public function getPhpVersion(): string
+ {
+ return Emulative::PHP_7_4;
+ }
+
+ public function isEmulationNeeded(string $code) : bool
+ {
+ return preg_match('~[0-9]_[0-9]~', $code)
+ || preg_match('~0x[0-9a-f]+_[0-9a-f]~i', $code);
+ }
+
+ public function emulate(string $code, array $tokens): array
+ {
+ // We need to manually iterate and manage a count because we'll change
+ // the tokens array on the way
+ $codeOffset = 0;
+ for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
+ $token = $tokens[$i];
+ $tokenLen = \strlen(\is_array($token) ? $token[1] : $token);
+
+ if ($token[0] !== T_LNUMBER && $token[0] !== T_DNUMBER) {
+ $codeOffset += $tokenLen;
+ continue;
+ }
+
+ $res = preg_match(self::NUMBER, $code, $matches, 0, $codeOffset);
+ assert($res, "No number at number token position");
+
+ $match = $matches[0];
+ $matchLen = \strlen($match);
+ if ($matchLen === $tokenLen) {
+ // Original token already holds the full number.
+ $codeOffset += $tokenLen;
+ continue;
+ }
+
+ $tokenKind = $this->resolveIntegerOrFloatToken($match);
+ $newTokens = [[$tokenKind, $match, $token[2]]];
+
+ $numTokens = 1;
+ $len = $tokenLen;
+ while ($matchLen > $len) {
+ $nextToken = $tokens[$i + $numTokens];
+ $nextTokenText = \is_array($nextToken) ? $nextToken[1] : $nextToken;
+ $nextTokenLen = \strlen($nextTokenText);
+
+ $numTokens++;
+ if ($matchLen < $len + $nextTokenLen) {
+ // Split trailing characters into a partial token.
+ assert(is_array($nextToken), "Partial token should be an array token");
+ $partialText = substr($nextTokenText, $matchLen - $len);
+ $newTokens[] = [$nextToken[0], $partialText, $nextToken[2]];
+ break;
+ }
+
+ $len += $nextTokenLen;
+ }
+
+ array_splice($tokens, $i, $numTokens, $newTokens);
+ $c -= $numTokens - \count($newTokens);
+ $codeOffset += $matchLen;
+ }
+
+ return $tokens;
+ }
+
+ private function resolveIntegerOrFloatToken(string $str): int
+ {
+ $str = str_replace('_', '', $str);
+
+ if (stripos($str, '0b') === 0) {
+ $num = bindec($str);
+ } elseif (stripos($str, '0x') === 0) {
+ $num = hexdec($str);
+ } elseif (stripos($str, '0') === 0 && ctype_digit($str)) {
+ $num = octdec($str);
+ } else {
+ $num = +$str;
+ }
+
+ return is_float($num) ? T_DNUMBER : T_LNUMBER;
+ }
+
+ public function reverseEmulate(string $code, array $tokens): array
+ {
+ // Numeric separators were not legal code previously, don't bother.
+ return $tokens;
+ }
+}
diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ReadonlyTokenEmulator.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ReadonlyTokenEmulator.php
new file mode 100644
index 000000000..b97f8d112
--- /dev/null
+++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ReadonlyTokenEmulator.php
@@ -0,0 +1,23 @@
+<?php declare(strict_types=1);
+
+namespace PhpParser\Lexer\TokenEmulator;
+
+use PhpParser\Lexer\Emulative;
+
+final class ReadonlyTokenEmulator extends KeywordEmulator
+{
+ public function getPhpVersion(): string
+ {
+ return Emulative::PHP_8_1;
+ }
+
+ public function getKeywordString(): string
+ {
+ return 'readonly';
+ }
+
+ public function getKeywordToken(): int
+ {
+ return \T_READONLY;
+ }
+} \ No newline at end of file
diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ReverseEmulator.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ReverseEmulator.php
new file mode 100644
index 000000000..90093f66b
--- /dev/null
+++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ReverseEmulator.php
@@ -0,0 +1,36 @@
+<?php declare(strict_types=1);
+
+namespace PhpParser\Lexer\TokenEmulator;
+
+/**
+ * Reverses emulation direction of the inner emulator.
+ */
+final class ReverseEmulator extends TokenEmulator
+{
+ /** @var TokenEmulator Inner emulator */
+ private $emulator;
+
+ public function __construct(TokenEmulator $emulator) {
+ $this->emulator = $emulator;
+ }
+
+ public function getPhpVersion(): string {
+ return $this->emulator->getPhpVersion();
+ }
+
+ public function isEmulationNeeded(string $code): bool {
+ return $this->emulator->isEmulationNeeded($code);
+ }
+
+ public function emulate(string $code, array $tokens): array {
+ return $this->emulator->reverseEmulate($code, $tokens);
+ }
+
+ public function reverseEmulate(string $code, array $tokens): array {
+ return $this->emulator->emulate($code, $tokens);
+ }
+
+ public function preprocessCode(string $code, array &$patches): string {
+ return $code;
+ }
+} \ No newline at end of file
diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/TokenEmulator.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/TokenEmulator.php
new file mode 100644
index 000000000..a020bc0ff
--- /dev/null
+++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/TokenEmulator.php
@@ -0,0 +1,25 @@
+<?php declare(strict_types=1);
+
+namespace PhpParser\Lexer\TokenEmulator;
+
+/** @internal */
+abstract class TokenEmulator
+{
+ abstract public function getPhpVersion(): string;
+
+ abstract public function isEmulationNeeded(string $code): bool;
+
+ /**
+ * @return array Modified Tokens
+ */
+ abstract public function emulate(string $code, array $tokens): array;
+
+ /**
+ * @return array Modified Tokens
+ */
+ abstract public function reverseEmulate(string $code, array $tokens): array;
+
+ public function preprocessCode(string $code, array &$patches): string {
+ return $code;
+ }
+}