1<?php declare(strict_types=1);
2
3namespace PhpParser\Lexer\TokenEmulator;
4
5use PhpParser\PhpVersion;
6use PhpParser\Token;
7
8final class NullsafeTokenEmulator extends TokenEmulator {
9    public function getPhpVersion(): PhpVersion {
10        return PhpVersion::fromComponents(8, 0);
11    }
12
13    public function isEmulationNeeded(string $code): bool {
14        return strpos($code, '?->') !== false;
15    }
16
17    public function emulate(string $code, array $tokens): array {
18        // We need to manually iterate and manage a count because we'll change
19        // the tokens array on the way
20        for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
21            $token = $tokens[$i];
22            if ($token->text === '?' && isset($tokens[$i + 1]) && $tokens[$i + 1]->id === \T_OBJECT_OPERATOR) {
23                array_splice($tokens, $i, 2, [
24                    new Token(\T_NULLSAFE_OBJECT_OPERATOR, '?->', $token->line, $token->pos),
25                ]);
26                $c--;
27                continue;
28            }
29
30            // Handle ?-> inside encapsed string.
31            if ($token->id === \T_ENCAPSED_AND_WHITESPACE && isset($tokens[$i - 1])
32                && $tokens[$i - 1]->id === \T_VARIABLE
33                && preg_match('/^\?->([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)/', $token->text, $matches)
34            ) {
35                $replacement = [
36                    new Token(\T_NULLSAFE_OBJECT_OPERATOR, '?->', $token->line, $token->pos),
37                    new Token(\T_STRING, $matches[1], $token->line, $token->pos + 3),
38                ];
39                $matchLen = \strlen($matches[0]);
40                if ($matchLen !== \strlen($token->text)) {
41                    $replacement[] = new Token(
42                        \T_ENCAPSED_AND_WHITESPACE,
43                        \substr($token->text, $matchLen),
44                        $token->line, $token->pos + $matchLen
45                    );
46                }
47                array_splice($tokens, $i, 1, $replacement);
48                $c += \count($replacement) - 1;
49                continue;
50            }
51        }
52
53        return $tokens;
54    }
55
56    public function reverseEmulate(string $code, array $tokens): array {
57        // ?-> was not valid code previously, don't bother.
58        return $tokens;
59    }
60}
61