1: | <?php declare(strict_types = 1); |
2: | |
3: | namespace PHPStan\PhpDocParser\Parser; |
4: | |
5: | use PHPStan\PhpDocParser\Lexer\Lexer; |
6: | use function array_pop; |
7: | use function assert; |
8: | use function count; |
9: | use function in_array; |
10: | use function strlen; |
11: | |
12: | class TokenIterator |
13: | { |
14: | |
15: | |
16: | private $tokens; |
17: | |
18: | |
19: | private $index; |
20: | |
21: | |
22: | private $savePoints = []; |
23: | |
24: | public function __construct(array $tokens, int $index = 0) |
25: | { |
26: | $this->tokens = $tokens; |
27: | $this->index = $index; |
28: | |
29: | if ($this->tokens[$this->index][Lexer::TYPE_OFFSET] !== Lexer::TOKEN_HORIZONTAL_WS) { |
30: | return; |
31: | } |
32: | |
33: | $this->index++; |
34: | } |
35: | |
36: | |
37: | public function currentTokenValue(): string |
38: | { |
39: | return $this->tokens[$this->index][Lexer::VALUE_OFFSET]; |
40: | } |
41: | |
42: | |
43: | public function currentTokenType(): int |
44: | { |
45: | return $this->tokens[$this->index][Lexer::TYPE_OFFSET]; |
46: | } |
47: | |
48: | |
49: | public function currentTokenOffset(): int |
50: | { |
51: | $offset = 0; |
52: | for ($i = 0; $i < $this->index; $i++) { |
53: | $offset += strlen($this->tokens[$i][Lexer::VALUE_OFFSET]); |
54: | } |
55: | |
56: | return $offset; |
57: | } |
58: | |
59: | |
60: | public function isCurrentTokenValue(string $tokenValue): bool |
61: | { |
62: | return $this->tokens[$this->index][Lexer::VALUE_OFFSET] === $tokenValue; |
63: | } |
64: | |
65: | |
66: | public function isCurrentTokenType(int ...$tokenType): bool |
67: | { |
68: | return in_array($this->tokens[$this->index][Lexer::TYPE_OFFSET], $tokenType, true); |
69: | } |
70: | |
71: | |
72: | public function isPrecededByHorizontalWhitespace(): bool |
73: | { |
74: | return ($this->tokens[$this->index - 1][Lexer::TYPE_OFFSET] ?? -1) === Lexer::TOKEN_HORIZONTAL_WS; |
75: | } |
76: | |
77: | |
78: | |
79: | |
80: | |
81: | public function consumeTokenType(int $tokenType): void |
82: | { |
83: | if ($this->tokens[$this->index][Lexer::TYPE_OFFSET] !== $tokenType) { |
84: | $this->throwError($tokenType); |
85: | } |
86: | |
87: | $this->index++; |
88: | |
89: | if (($this->tokens[$this->index][Lexer::TYPE_OFFSET] ?? -1) !== Lexer::TOKEN_HORIZONTAL_WS) { |
90: | return; |
91: | } |
92: | |
93: | $this->index++; |
94: | } |
95: | |
96: | |
97: | |
98: | |
99: | |
100: | public function consumeTokenValue(int $tokenType, string $tokenValue): void |
101: | { |
102: | if ($this->tokens[$this->index][Lexer::TYPE_OFFSET] !== $tokenType || $this->tokens[$this->index][Lexer::VALUE_OFFSET] !== $tokenValue) { |
103: | $this->throwError($tokenType, $tokenValue); |
104: | } |
105: | |
106: | $this->index++; |
107: | |
108: | if (($this->tokens[$this->index][Lexer::TYPE_OFFSET] ?? -1) !== Lexer::TOKEN_HORIZONTAL_WS) { |
109: | return; |
110: | } |
111: | |
112: | $this->index++; |
113: | } |
114: | |
115: | |
116: | |
117: | public function tryConsumeTokenValue(string $tokenValue): bool |
118: | { |
119: | if ($this->tokens[$this->index][Lexer::VALUE_OFFSET] !== $tokenValue) { |
120: | return false; |
121: | } |
122: | |
123: | $this->index++; |
124: | |
125: | if ($this->tokens[$this->index][Lexer::TYPE_OFFSET] === Lexer::TOKEN_HORIZONTAL_WS) { |
126: | $this->index++; |
127: | } |
128: | |
129: | return true; |
130: | } |
131: | |
132: | |
133: | |
134: | public function tryConsumeTokenType(int $tokenType): bool |
135: | { |
136: | if ($this->tokens[$this->index][Lexer::TYPE_OFFSET] !== $tokenType) { |
137: | return false; |
138: | } |
139: | |
140: | $this->index++; |
141: | |
142: | if ($this->tokens[$this->index][Lexer::TYPE_OFFSET] === Lexer::TOKEN_HORIZONTAL_WS) { |
143: | $this->index++; |
144: | } |
145: | |
146: | return true; |
147: | } |
148: | |
149: | |
150: | public function getSkippedHorizontalWhiteSpaceIfAny(): string |
151: | { |
152: | if ($this->index > 0 && $this->tokens[$this->index - 1][Lexer::TYPE_OFFSET] === Lexer::TOKEN_HORIZONTAL_WS) { |
153: | return $this->tokens[$this->index - 1][Lexer::VALUE_OFFSET]; |
154: | } |
155: | |
156: | return ''; |
157: | } |
158: | |
159: | |
160: | |
161: | public function joinUntil(int ...$tokenType): string |
162: | { |
163: | $s = ''; |
164: | while (!in_array($this->tokens[$this->index][Lexer::TYPE_OFFSET], $tokenType, true)) { |
165: | $s .= $this->tokens[$this->index++][Lexer::VALUE_OFFSET]; |
166: | } |
167: | return $s; |
168: | } |
169: | |
170: | |
171: | public function next(): void |
172: | { |
173: | $this->index++; |
174: | |
175: | if ($this->tokens[$this->index][Lexer::TYPE_OFFSET] !== Lexer::TOKEN_HORIZONTAL_WS) { |
176: | return; |
177: | } |
178: | |
179: | $this->index++; |
180: | } |
181: | |
182: | |
183: | public function forwardToTheEnd(): void |
184: | { |
185: | $lastToken = count($this->tokens) - 1; |
186: | $this->index = $lastToken; |
187: | } |
188: | |
189: | |
190: | public function pushSavePoint(): void |
191: | { |
192: | $this->savePoints[] = $this->index; |
193: | } |
194: | |
195: | |
196: | public function dropSavePoint(): void |
197: | { |
198: | array_pop($this->savePoints); |
199: | } |
200: | |
201: | |
202: | public function rollback(): void |
203: | { |
204: | $index = array_pop($this->savePoints); |
205: | assert($index !== null); |
206: | $this->index = $index; |
207: | } |
208: | |
209: | |
210: | |
211: | |
212: | |
213: | private function throwError(int $expectedTokenType, ?string $expectedTokenValue = null): void |
214: | { |
215: | throw new ParserException( |
216: | $this->currentTokenValue(), |
217: | $this->currentTokenType(), |
218: | $this->currentTokenOffset(), |
219: | $expectedTokenType, |
220: | $expectedTokenValue |
221: | ); |
222: | } |
223: | |
224: | } |
225: | |