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: /** @var mixed[][] */
16: private $tokens;
17:
18: /** @var int */
19: private $index;
20:
21: /** @var int[] */
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: * @throws ParserException
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: * @throws ParserException
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: /** @phpstan-impure */
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: /** @phpstan-impure */
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: /** @phpstan-impure */
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: /** @phpstan-impure */
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: * @throws ParserException
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: