1<?php declare(strict_types=1);
2
3namespace PhpParser\Lexer\TokenEmulator;
4
5use PhpParser\Token;
6
7abstract class KeywordEmulator extends TokenEmulator {
8    abstract public function getKeywordString(): string;
9    abstract public function getKeywordToken(): int;
10
11    public function isEmulationNeeded(string $code): bool {
12        return strpos(strtolower($code), $this->getKeywordString()) !== false;
13    }
14
15    /** @param Token[] $tokens */
16    protected function isKeywordContext(array $tokens, int $pos): bool {
17        $prevToken = $this->getPreviousNonSpaceToken($tokens, $pos);
18        if ($prevToken === null) {
19            return false;
20        }
21        return $prevToken->id !== \T_OBJECT_OPERATOR
22            && $prevToken->id !== \T_NULLSAFE_OBJECT_OPERATOR;
23    }
24
25    public function emulate(string $code, array $tokens): array {
26        $keywordString = $this->getKeywordString();
27        foreach ($tokens as $i => $token) {
28            if ($token->id === T_STRING && strtolower($token->text) === $keywordString
29                    && $this->isKeywordContext($tokens, $i)) {
30                $token->id = $this->getKeywordToken();
31            }
32        }
33
34        return $tokens;
35    }
36
37    /** @param Token[] $tokens */
38    private function getPreviousNonSpaceToken(array $tokens, int $start): ?Token {
39        for ($i = $start - 1; $i >= 0; --$i) {
40            if ($tokens[$i]->id === T_WHITESPACE) {
41                continue;
42            }
43
44            return $tokens[$i];
45        }
46
47        return null;
48    }
49
50    public function reverseEmulate(string $code, array $tokens): array {
51        $keywordToken = $this->getKeywordToken();
52        foreach ($tokens as $token) {
53            if ($token->id === $keywordToken) {
54                $token->id = \T_STRING;
55            }
56        }
57
58        return $tokens;
59    }
60}
61