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