|
1
|
|
|
<?php |
|
2
|
|
|
namespace Psalm\Internal\Type; |
|
3
|
|
|
|
|
4
|
|
|
use function array_pop; |
|
5
|
|
|
use function count; |
|
6
|
|
|
use function in_array; |
|
7
|
|
|
use function preg_match; |
|
8
|
|
|
use Psalm\Exception\TypeParseTreeException; |
|
9
|
|
|
use function strlen; |
|
10
|
|
|
use function strtolower; |
|
11
|
|
|
|
|
12
|
|
|
/** |
|
13
|
|
|
* @internal |
|
14
|
|
|
*/ |
|
15
|
|
|
class ParseTreeCreator |
|
16
|
|
|
{ |
|
17
|
|
|
/** @var ParseTree */ |
|
18
|
|
|
private $parse_tree; |
|
19
|
|
|
|
|
20
|
|
|
/** @var ParseTree */ |
|
21
|
|
|
private $current_leaf; |
|
22
|
|
|
|
|
23
|
|
|
/** @var array<int, array{0: string, 1: int}> */ |
|
24
|
|
|
private $type_tokens; |
|
25
|
|
|
|
|
26
|
|
|
/** @var int */ |
|
27
|
|
|
private $type_token_count; |
|
28
|
|
|
|
|
29
|
|
|
/** @var int */ |
|
30
|
|
|
private $t = 0; |
|
31
|
|
|
|
|
32
|
|
|
/** |
|
33
|
|
|
* @param array<int, array{0: string, 1: int}> $type_tokens |
|
34
|
|
|
*/ |
|
35
|
|
|
public function __construct(array $type_tokens) |
|
36
|
|
|
{ |
|
37
|
|
|
$this->type_tokens = $type_tokens; |
|
38
|
|
|
$this->type_token_count = count($type_tokens); |
|
39
|
|
|
$this->parse_tree = new ParseTree\Root(); |
|
40
|
|
|
$this->current_leaf = $this->parse_tree; |
|
41
|
|
|
} |
|
42
|
|
|
|
|
43
|
|
|
public function create() : ParseTree |
|
44
|
|
|
{ |
|
45
|
|
|
while ($this->t < $this->type_token_count) { |
|
46
|
|
|
$type_token = $this->type_tokens[$this->t]; |
|
47
|
|
|
|
|
48
|
|
|
switch ($type_token[0]) { |
|
49
|
|
|
case '<': |
|
50
|
|
|
case '{': |
|
51
|
|
|
case ']': |
|
52
|
|
|
throw new TypeParseTreeException('Unexpected token ' . $type_token[0]); |
|
53
|
|
|
|
|
54
|
|
|
case '[': |
|
55
|
|
|
$this->handleOpenSquareBracket(); |
|
56
|
|
|
break; |
|
57
|
|
|
|
|
58
|
|
|
case '(': |
|
59
|
|
|
$this->handleOpenRoundBracket(); |
|
60
|
|
|
break; |
|
61
|
|
|
|
|
62
|
|
|
case ')': |
|
63
|
|
|
$this->handleClosedRoundBracket(); |
|
64
|
|
|
break; |
|
65
|
|
|
|
|
66
|
|
View Code Duplication |
case '>': |
|
|
|
|
|
|
67
|
|
|
do { |
|
68
|
|
|
if ($this->current_leaf->parent === null) { |
|
69
|
|
|
throw new TypeParseTreeException('Cannot parse generic type'); |
|
70
|
|
|
} |
|
71
|
|
|
|
|
72
|
|
|
$this->current_leaf = $this->current_leaf->parent; |
|
73
|
|
|
} while (!$this->current_leaf instanceof ParseTree\GenericTree); |
|
74
|
|
|
|
|
75
|
|
|
$this->current_leaf->terminated = true; |
|
76
|
|
|
|
|
77
|
|
|
break; |
|
78
|
|
|
|
|
79
|
|
View Code Duplication |
case '}': |
|
|
|
|
|
|
80
|
|
|
do { |
|
81
|
|
|
if ($this->current_leaf->parent === null) { |
|
82
|
|
|
throw new TypeParseTreeException('Cannot parse array type'); |
|
83
|
|
|
} |
|
84
|
|
|
|
|
85
|
|
|
$this->current_leaf = $this->current_leaf->parent; |
|
86
|
|
|
} while (!$this->current_leaf instanceof ParseTree\ObjectLikeTree); |
|
87
|
|
|
|
|
88
|
|
|
$this->current_leaf->terminated = true; |
|
89
|
|
|
|
|
90
|
|
|
break; |
|
91
|
|
|
|
|
92
|
|
|
case ',': |
|
93
|
|
|
$this->handleComma(); |
|
94
|
|
|
break; |
|
95
|
|
|
|
|
96
|
|
|
case '...': |
|
97
|
|
|
case '=': |
|
98
|
|
|
$this->handleEllipsisOrEquals($type_token); |
|
99
|
|
|
break; |
|
100
|
|
|
|
|
101
|
|
|
case ':': |
|
102
|
|
|
$this->handleColon(); |
|
103
|
|
|
break; |
|
104
|
|
|
|
|
105
|
|
|
case ' ': |
|
106
|
|
|
$this->handleSpace(); |
|
107
|
|
|
break; |
|
108
|
|
|
|
|
109
|
|
|
case '?': |
|
110
|
|
|
$this->handleQuestionMark(); |
|
111
|
|
|
break; |
|
112
|
|
|
|
|
113
|
|
|
case '|': |
|
114
|
|
|
$this->handleBar(); |
|
115
|
|
|
break; |
|
116
|
|
|
|
|
117
|
|
|
case '&': |
|
118
|
|
|
$this->handleAmpersand(); |
|
119
|
|
|
break; |
|
120
|
|
|
|
|
121
|
|
|
case 'is': |
|
122
|
|
|
case 'as': |
|
123
|
|
|
$this->handleIsOrAs($type_token); |
|
124
|
|
|
break; |
|
125
|
|
|
|
|
126
|
|
|
default: |
|
127
|
|
|
$this->handleValue($type_token); |
|
128
|
|
|
break; |
|
129
|
|
|
} |
|
130
|
|
|
|
|
131
|
|
|
$this->t++; |
|
132
|
|
|
} |
|
133
|
|
|
|
|
134
|
|
|
$this->parse_tree->cleanParents(); |
|
135
|
|
|
|
|
136
|
|
|
if ($this->current_leaf !== $this->parse_tree |
|
137
|
|
|
&& ($this->parse_tree instanceof ParseTree\GenericTree |
|
138
|
|
|
|| $this->parse_tree instanceof ParseTree\CallableTree |
|
139
|
|
|
|| $this->parse_tree instanceof ParseTree\ObjectLikeTree) |
|
140
|
|
|
) { |
|
141
|
|
|
throw new TypeParseTreeException( |
|
142
|
|
|
'Unterminated bracket' |
|
143
|
|
|
); |
|
144
|
|
|
} |
|
145
|
|
|
|
|
146
|
|
|
return $this->parse_tree; |
|
147
|
|
|
} |
|
148
|
|
|
|
|
149
|
|
|
/** |
|
150
|
|
|
* @param array{0: string, 1: int} $current_token |
|
151
|
|
|
*/ |
|
152
|
|
|
private function createMethodParam($current_token, ParseTree $current_parent) : void |
|
153
|
|
|
{ |
|
154
|
|
|
$byref = false; |
|
155
|
|
|
$variadic = false; |
|
156
|
|
|
$has_default = false; |
|
157
|
|
|
$default = ''; |
|
158
|
|
|
|
|
159
|
|
|
if ($current_token[0] === '&') { |
|
160
|
|
|
throw new TypeParseTreeException('Magic args cannot be passed by reference'); |
|
161
|
|
|
} |
|
162
|
|
|
|
|
163
|
|
|
if ($current_token[0] === '...') { |
|
164
|
|
|
$variadic = true; |
|
165
|
|
|
|
|
166
|
|
|
++$this->t; |
|
167
|
|
|
$current_token = $this->t < $this->type_token_count ? $this->type_tokens[$this->t] : null; |
|
168
|
|
|
} |
|
169
|
|
|
|
|
170
|
|
|
if (!$current_token || $current_token[0][0] !== '$') { |
|
171
|
|
|
throw new TypeParseTreeException('Unexpected token after space'); |
|
172
|
|
|
} |
|
173
|
|
|
|
|
174
|
|
|
$new_parent_leaf = new ParseTree\MethodParamTree( |
|
175
|
|
|
$current_token[0], |
|
176
|
|
|
$byref, |
|
177
|
|
|
$variadic, |
|
178
|
|
|
$current_parent |
|
179
|
|
|
); |
|
180
|
|
|
|
|
181
|
|
|
for ($j = $this->t + 1; $j < $this->type_token_count; ++$j) { |
|
182
|
|
|
$ahead_type_token = $this->type_tokens[$j]; |
|
183
|
|
|
|
|
184
|
|
|
if ($ahead_type_token[0] === ',' |
|
185
|
|
|
|| ($ahead_type_token[0] === ')' && $this->type_tokens[$j - 1][0] !== '(') |
|
186
|
|
|
) { |
|
187
|
|
|
$this->t = $j - 1; |
|
188
|
|
|
break; |
|
189
|
|
|
} |
|
190
|
|
|
|
|
191
|
|
|
if ($has_default) { |
|
192
|
|
|
$default .= $ahead_type_token[0]; |
|
193
|
|
|
} |
|
194
|
|
|
|
|
195
|
|
|
if ($ahead_type_token[0] === '=') { |
|
196
|
|
|
$has_default = true; |
|
197
|
|
|
continue; |
|
198
|
|
|
} |
|
199
|
|
|
|
|
200
|
|
|
if ($j === $this->type_token_count - 1) { |
|
201
|
|
|
throw new TypeParseTreeException('Unterminated method'); |
|
202
|
|
|
} |
|
203
|
|
|
} |
|
204
|
|
|
|
|
205
|
|
|
$new_parent_leaf->default = $default; |
|
206
|
|
|
|
|
207
|
|
|
if ($this->current_leaf !== $current_parent) { |
|
208
|
|
|
$new_parent_leaf->children = [$this->current_leaf]; |
|
209
|
|
|
$this->current_leaf->parent = $new_parent_leaf; |
|
210
|
|
|
array_pop($current_parent->children); |
|
211
|
|
|
} |
|
212
|
|
|
|
|
213
|
|
|
$current_parent->children[] = $new_parent_leaf; |
|
214
|
|
|
|
|
215
|
|
|
$this->current_leaf = $new_parent_leaf; |
|
216
|
|
|
} |
|
217
|
|
|
|
|
218
|
|
|
private function handleOpenSquareBracket() : void |
|
219
|
|
|
{ |
|
220
|
|
|
if ($this->current_leaf instanceof ParseTree\Root) { |
|
221
|
|
|
throw new TypeParseTreeException('Unexpected token ['); |
|
222
|
|
|
} |
|
223
|
|
|
|
|
224
|
|
|
$indexed_access = false; |
|
225
|
|
|
|
|
226
|
|
|
$next_token = $this->t + 1 < $this->type_token_count ? $this->type_tokens[$this->t + 1] : null; |
|
227
|
|
|
|
|
228
|
|
|
if (!$next_token || $next_token[0] !== ']') { |
|
229
|
|
|
$next_next_token = $this->t + 2 < $this->type_token_count ? $this->type_tokens[$this->t + 2] : null; |
|
230
|
|
|
|
|
231
|
|
|
if ($next_next_token !== null && $next_next_token[0] === ']') { |
|
232
|
|
|
$indexed_access = true; |
|
233
|
|
|
++$this->t; |
|
234
|
|
|
} else { |
|
235
|
|
|
throw new TypeParseTreeException('Unexpected token ['); |
|
236
|
|
|
} |
|
237
|
|
|
} |
|
238
|
|
|
|
|
239
|
|
|
$current_parent = $this->current_leaf->parent; |
|
240
|
|
|
|
|
241
|
|
|
if ($indexed_access) { |
|
242
|
|
|
if ($next_token === null) { |
|
243
|
|
|
throw new TypeParseTreeException('Unexpected token ['); |
|
244
|
|
|
} |
|
245
|
|
|
|
|
246
|
|
|
$new_parent_leaf = new ParseTree\IndexedAccessTree($next_token[0], $current_parent); |
|
247
|
|
|
} else { |
|
248
|
|
|
if ($this->current_leaf instanceof ParseTree\ObjectLikePropertyTree) { |
|
249
|
|
|
throw new TypeParseTreeException('Unexpected token ['); |
|
250
|
|
|
} |
|
251
|
|
|
|
|
252
|
|
|
$new_parent_leaf = new ParseTree\GenericTree('array', $current_parent); |
|
253
|
|
|
} |
|
254
|
|
|
|
|
255
|
|
|
$this->current_leaf->parent = $new_parent_leaf; |
|
256
|
|
|
$new_parent_leaf->children = [$this->current_leaf]; |
|
257
|
|
|
|
|
258
|
|
|
if ($current_parent) { |
|
259
|
|
|
array_pop($current_parent->children); |
|
260
|
|
|
$current_parent->children[] = $new_parent_leaf; |
|
261
|
|
|
} else { |
|
262
|
|
|
$this->parse_tree = $new_parent_leaf; |
|
263
|
|
|
} |
|
264
|
|
|
|
|
265
|
|
|
$this->current_leaf = $new_parent_leaf; |
|
266
|
|
|
++$this->t; |
|
267
|
|
|
} |
|
268
|
|
|
|
|
269
|
|
|
private function handleOpenRoundBracket() : void |
|
270
|
|
|
{ |
|
271
|
|
|
if ($this->current_leaf instanceof ParseTree\Value) { |
|
272
|
|
|
throw new TypeParseTreeException('Unrecognised token ('); |
|
273
|
|
|
} |
|
274
|
|
|
|
|
275
|
|
|
$new_parent = !$this->current_leaf instanceof ParseTree\Root ? $this->current_leaf : null; |
|
276
|
|
|
|
|
277
|
|
|
$new_leaf = new ParseTree\EncapsulationTree( |
|
278
|
|
|
$new_parent |
|
279
|
|
|
); |
|
280
|
|
|
|
|
281
|
|
|
if ($this->current_leaf instanceof ParseTree\Root) { |
|
282
|
|
|
$this->current_leaf = $this->parse_tree = $new_leaf; |
|
283
|
|
|
return; |
|
284
|
|
|
} |
|
285
|
|
|
|
|
286
|
|
|
if ($new_leaf->parent) { |
|
287
|
|
|
$new_leaf->parent->children[] = $new_leaf; |
|
288
|
|
|
} |
|
289
|
|
|
|
|
290
|
|
|
$this->current_leaf = $new_leaf; |
|
291
|
|
|
} |
|
292
|
|
|
|
|
293
|
|
|
private function handleClosedRoundBracket() : void |
|
294
|
|
|
{ |
|
295
|
|
|
$prev_token = $this->t > 0 ? $this->type_tokens[$this->t - 1] : null; |
|
296
|
|
|
|
|
297
|
|
|
if ($prev_token !== null |
|
298
|
|
|
&& $prev_token[0] === '(' |
|
299
|
|
|
&& $this->current_leaf instanceof ParseTree\CallableTree |
|
300
|
|
|
) { |
|
301
|
|
|
return; |
|
302
|
|
|
} |
|
303
|
|
|
|
|
304
|
|
|
do { |
|
305
|
|
|
if ($this->current_leaf->parent === null) { |
|
306
|
|
|
break; |
|
307
|
|
|
} |
|
308
|
|
|
|
|
309
|
|
|
$this->current_leaf = $this->current_leaf->parent; |
|
310
|
|
|
} while (!$this->current_leaf instanceof ParseTree\EncapsulationTree |
|
311
|
|
|
&& !$this->current_leaf instanceof ParseTree\CallableTree |
|
312
|
|
|
&& !$this->current_leaf instanceof ParseTree\MethodTree); |
|
313
|
|
|
|
|
314
|
|
|
if ($this->current_leaf instanceof ParseTree\EncapsulationTree |
|
315
|
|
|
|| $this->current_leaf instanceof ParseTree\CallableTree |
|
316
|
|
|
) { |
|
317
|
|
|
$this->current_leaf->terminated = true; |
|
318
|
|
|
} |
|
319
|
|
|
} |
|
320
|
|
|
|
|
321
|
|
|
private function handleComma() : void |
|
322
|
|
|
{ |
|
323
|
|
|
if ($this->current_leaf instanceof ParseTree\Root) { |
|
324
|
|
|
throw new TypeParseTreeException('Unexpected token ,'); |
|
325
|
|
|
} |
|
326
|
|
|
|
|
327
|
|
|
if (!$this->current_leaf->parent) { |
|
328
|
|
|
throw new TypeParseTreeException('Cannot parse comma without a parent node'); |
|
329
|
|
|
} |
|
330
|
|
|
|
|
331
|
|
|
$context_node = $this->current_leaf; |
|
332
|
|
|
|
|
333
|
|
|
if ($context_node instanceof ParseTree\GenericTree |
|
334
|
|
|
|| $context_node instanceof ParseTree\ObjectLikeTree |
|
335
|
|
|
|| $context_node instanceof ParseTree\CallableTree |
|
336
|
|
|
|| $context_node instanceof ParseTree\MethodTree |
|
337
|
|
|
) { |
|
338
|
|
|
$context_node = $context_node->parent; |
|
339
|
|
|
} |
|
340
|
|
|
|
|
341
|
|
|
while ($context_node |
|
342
|
|
|
&& !$context_node instanceof ParseTree\GenericTree |
|
343
|
|
|
&& !$context_node instanceof ParseTree\ObjectLikeTree |
|
344
|
|
|
&& !$context_node instanceof ParseTree\CallableTree |
|
345
|
|
|
&& !$context_node instanceof ParseTree\MethodTree |
|
346
|
|
|
) { |
|
347
|
|
|
$context_node = $context_node->parent; |
|
348
|
|
|
} |
|
349
|
|
|
|
|
350
|
|
|
if (!$context_node) { |
|
351
|
|
|
throw new TypeParseTreeException('Cannot parse comma in non-generic/array type'); |
|
352
|
|
|
} |
|
353
|
|
|
|
|
354
|
|
|
$this->current_leaf = $context_node; |
|
355
|
|
|
} |
|
356
|
|
|
|
|
357
|
|
|
/** @param array{0: string, 1: int} $type_token */ |
|
358
|
|
|
private function handleEllipsisOrEquals($type_token) : void |
|
359
|
|
|
{ |
|
360
|
|
|
$prev_token = $this->t > 0 ? $this->type_tokens[$this->t - 1] : null; |
|
361
|
|
|
|
|
362
|
|
|
if ($prev_token && ($prev_token[0] === '...' || $prev_token[0] === '=')) { |
|
363
|
|
|
throw new TypeParseTreeException('Cannot have duplicate tokens'); |
|
364
|
|
|
} |
|
365
|
|
|
|
|
366
|
|
|
$current_parent = $this->current_leaf->parent; |
|
367
|
|
|
|
|
368
|
|
View Code Duplication |
if ($this->current_leaf instanceof ParseTree\MethodTree && $type_token[0] === '...') { |
|
|
|
|
|
|
369
|
|
|
$this->createMethodParam($type_token, $this->current_leaf); |
|
370
|
|
|
return; |
|
371
|
|
|
} |
|
372
|
|
|
|
|
373
|
|
|
while ($current_parent |
|
374
|
|
|
&& !$current_parent instanceof ParseTree\CallableTree |
|
375
|
|
|
&& !$current_parent instanceof ParseTree\CallableParamTree |
|
376
|
|
|
) { |
|
377
|
|
|
$this->current_leaf = $current_parent; |
|
378
|
|
|
$current_parent = $current_parent->parent; |
|
379
|
|
|
} |
|
380
|
|
|
|
|
381
|
|
|
if (!$current_parent || !$this->current_leaf) { |
|
382
|
|
|
if ($this->current_leaf instanceof ParseTree\CallableTree |
|
383
|
|
|
&& $type_token[0] === '...' |
|
384
|
|
|
) { |
|
385
|
|
|
$current_parent = $this->current_leaf; |
|
386
|
|
|
} else { |
|
387
|
|
|
throw new TypeParseTreeException('Unexpected token ' . $type_token[0]); |
|
388
|
|
|
} |
|
389
|
|
|
} |
|
390
|
|
|
|
|
391
|
|
|
if ($current_parent instanceof ParseTree\CallableParamTree) { |
|
392
|
|
|
throw new TypeParseTreeException('Cannot have variadic param with a default'); |
|
393
|
|
|
} |
|
394
|
|
|
|
|
395
|
|
|
$new_leaf = new ParseTree\CallableParamTree($current_parent); |
|
396
|
|
|
$new_leaf->has_default = $type_token[0] === '='; |
|
397
|
|
|
$new_leaf->variadic = $type_token[0] === '...'; |
|
398
|
|
|
|
|
399
|
|
|
if ($current_parent !== $this->current_leaf) { |
|
400
|
|
|
$new_leaf->children = [$this->current_leaf]; |
|
401
|
|
|
$this->current_leaf->parent = $new_leaf; |
|
402
|
|
|
|
|
403
|
|
|
array_pop($current_parent->children); |
|
404
|
|
|
$current_parent->children[] = $new_leaf; |
|
405
|
|
|
} else { |
|
406
|
|
|
$current_parent->children[] = $new_leaf; |
|
407
|
|
|
} |
|
408
|
|
|
|
|
409
|
|
|
$this->current_leaf = $new_leaf; |
|
410
|
|
|
} |
|
411
|
|
|
|
|
412
|
|
|
private function handleColon() : void |
|
413
|
|
|
{ |
|
414
|
|
|
if ($this->current_leaf instanceof ParseTree\Root) { |
|
415
|
|
|
throw new TypeParseTreeException('Unexpected token :'); |
|
416
|
|
|
} |
|
417
|
|
|
|
|
418
|
|
|
$current_parent = $this->current_leaf->parent; |
|
419
|
|
|
|
|
420
|
|
View Code Duplication |
if ($this->current_leaf instanceof ParseTree\CallableTree) { |
|
|
|
|
|
|
421
|
|
|
$new_parent_leaf = new ParseTree\CallableWithReturnTypeTree($current_parent); |
|
422
|
|
|
$this->current_leaf->parent = $new_parent_leaf; |
|
423
|
|
|
$new_parent_leaf->children = [$this->current_leaf]; |
|
424
|
|
|
|
|
425
|
|
|
if ($current_parent) { |
|
426
|
|
|
array_pop($current_parent->children); |
|
427
|
|
|
$current_parent->children[] = $new_parent_leaf; |
|
428
|
|
|
} else { |
|
429
|
|
|
$this->parse_tree = $new_parent_leaf; |
|
430
|
|
|
} |
|
431
|
|
|
|
|
432
|
|
|
$this->current_leaf = $new_parent_leaf; |
|
433
|
|
|
return; |
|
434
|
|
|
} |
|
435
|
|
|
|
|
436
|
|
View Code Duplication |
if ($this->current_leaf instanceof ParseTree\MethodTree) { |
|
|
|
|
|
|
437
|
|
|
$new_parent_leaf = new ParseTree\MethodWithReturnTypeTree($current_parent); |
|
438
|
|
|
$this->current_leaf->parent = $new_parent_leaf; |
|
439
|
|
|
$new_parent_leaf->children = [$this->current_leaf]; |
|
440
|
|
|
|
|
441
|
|
|
if ($current_parent) { |
|
442
|
|
|
array_pop($current_parent->children); |
|
443
|
|
|
$current_parent->children[] = $new_parent_leaf; |
|
444
|
|
|
} else { |
|
445
|
|
|
$this->parse_tree = $new_parent_leaf; |
|
446
|
|
|
} |
|
447
|
|
|
|
|
448
|
|
|
$this->current_leaf = $new_parent_leaf; |
|
449
|
|
|
return; |
|
450
|
|
|
} |
|
451
|
|
|
|
|
452
|
|
|
if ($current_parent && $current_parent instanceof ParseTree\ObjectLikePropertyTree) { |
|
453
|
|
|
return; |
|
454
|
|
|
} |
|
455
|
|
|
|
|
456
|
|
|
while (($current_parent instanceof ParseTree\UnionTree |
|
457
|
|
|
|| $current_parent instanceof ParseTree\CallableWithReturnTypeTree) |
|
458
|
|
|
&& $this->current_leaf->parent |
|
459
|
|
|
) { |
|
460
|
|
|
$this->current_leaf = $this->current_leaf->parent; |
|
461
|
|
|
$current_parent = $this->current_leaf->parent; |
|
462
|
|
|
} |
|
463
|
|
|
|
|
464
|
|
|
if ($current_parent && $current_parent instanceof ParseTree\ConditionalTree) { |
|
465
|
|
|
if (count($current_parent->children) > 1) { |
|
466
|
|
|
throw new TypeParseTreeException('Cannot process colon in conditional twice'); |
|
467
|
|
|
} |
|
468
|
|
|
|
|
469
|
|
|
$this->current_leaf = $current_parent; |
|
470
|
|
|
return; |
|
471
|
|
|
} |
|
472
|
|
|
|
|
473
|
|
|
if (!$current_parent) { |
|
474
|
|
|
throw new TypeParseTreeException('Cannot process colon without parent'); |
|
475
|
|
|
} |
|
476
|
|
|
|
|
477
|
|
|
if (!$this->current_leaf instanceof ParseTree\Value) { |
|
478
|
|
|
throw new TypeParseTreeException('Unexpected LHS of property'); |
|
479
|
|
|
} |
|
480
|
|
|
|
|
481
|
|
|
if (!$current_parent instanceof ParseTree\ObjectLikeTree) { |
|
482
|
|
|
throw new TypeParseTreeException('Saw : outside of object-like array'); |
|
483
|
|
|
} |
|
484
|
|
|
|
|
485
|
|
|
$prev_token = $this->t > 0 ? $this->type_tokens[$this->t - 1] : null; |
|
486
|
|
|
|
|
487
|
|
|
$new_parent_leaf = new ParseTree\ObjectLikePropertyTree($this->current_leaf->value, $current_parent); |
|
488
|
|
|
$new_parent_leaf->possibly_undefined = $prev_token !== null && $prev_token[0] === '?'; |
|
489
|
|
|
$this->current_leaf->parent = $new_parent_leaf; |
|
490
|
|
|
|
|
491
|
|
|
array_pop($current_parent->children); |
|
492
|
|
|
$current_parent->children[] = $new_parent_leaf; |
|
493
|
|
|
|
|
494
|
|
|
$this->current_leaf = $new_parent_leaf; |
|
495
|
|
|
} |
|
496
|
|
|
|
|
497
|
|
|
private function handleSpace() : void |
|
498
|
|
|
{ |
|
499
|
|
|
if ($this->current_leaf instanceof ParseTree\Root) { |
|
500
|
|
|
throw new TypeParseTreeException('Unexpected space'); |
|
501
|
|
|
} |
|
502
|
|
|
|
|
503
|
|
|
if ($this->current_leaf instanceof ParseTree\ObjectLikeTree) { |
|
504
|
|
|
return; |
|
505
|
|
|
} |
|
506
|
|
|
|
|
507
|
|
|
$current_parent = $this->current_leaf->parent; |
|
508
|
|
|
|
|
509
|
|
|
if ($current_parent instanceof ParseTree\CallableTree) { |
|
510
|
|
|
return; |
|
511
|
|
|
} |
|
512
|
|
|
|
|
513
|
|
|
while ($current_parent && !$current_parent instanceof ParseTree\MethodTree) { |
|
514
|
|
|
$this->current_leaf = $current_parent; |
|
515
|
|
|
$current_parent = $current_parent->parent; |
|
516
|
|
|
} |
|
517
|
|
|
|
|
518
|
|
|
$next_token = $this->t + 1 < $this->type_token_count ? $this->type_tokens[$this->t + 1] : null; |
|
519
|
|
|
|
|
520
|
|
|
if (!$current_parent instanceof ParseTree\MethodTree || !$next_token) { |
|
521
|
|
|
throw new TypeParseTreeException('Unexpected space'); |
|
522
|
|
|
} |
|
523
|
|
|
|
|
524
|
|
|
++$this->t; |
|
525
|
|
|
|
|
526
|
|
|
$this->createMethodParam($next_token, $current_parent); |
|
527
|
|
|
} |
|
528
|
|
|
|
|
529
|
|
|
private function handleQuestionMark() : void |
|
530
|
|
|
{ |
|
531
|
|
|
$next_token = $this->t + 1 < $this->type_token_count ? $this->type_tokens[$this->t + 1] : null; |
|
532
|
|
|
|
|
533
|
|
|
if ($next_token === null || $next_token[0] !== ':') { |
|
534
|
|
|
while (($this->current_leaf instanceof ParseTree\Value |
|
535
|
|
|
|| $this->current_leaf instanceof ParseTree\UnionTree |
|
536
|
|
|
|| ($this->current_leaf instanceof ParseTree\ObjectLikeTree |
|
537
|
|
|
&& $this->current_leaf->terminated) |
|
538
|
|
|
|| ($this->current_leaf instanceof ParseTree\GenericTree |
|
539
|
|
|
&& $this->current_leaf->terminated) |
|
540
|
|
|
|| ($this->current_leaf instanceof ParseTree\EncapsulationTree |
|
541
|
|
|
&& $this->current_leaf->terminated) |
|
542
|
|
|
|| ($this->current_leaf instanceof ParseTree\CallableTree |
|
543
|
|
|
&& $this->current_leaf->terminated) |
|
544
|
|
|
|| $this->current_leaf instanceof ParseTree\IntersectionTree) |
|
545
|
|
|
&& $this->current_leaf->parent |
|
546
|
|
|
) { |
|
547
|
|
|
$this->current_leaf = $this->current_leaf->parent; |
|
548
|
|
|
} |
|
549
|
|
|
|
|
550
|
|
|
if ($this->current_leaf instanceof ParseTree\TemplateIsTree && $this->current_leaf->parent) { |
|
551
|
|
|
$current_parent = $this->current_leaf->parent; |
|
552
|
|
|
|
|
553
|
|
|
$new_leaf = new ParseTree\ConditionalTree( |
|
554
|
|
|
$this->current_leaf, |
|
555
|
|
|
$this->current_leaf->parent |
|
556
|
|
|
); |
|
557
|
|
|
|
|
558
|
|
|
$this->current_leaf->parent = $new_leaf; |
|
559
|
|
|
|
|
560
|
|
|
array_pop($current_parent->children); |
|
561
|
|
|
$current_parent->children[] = $new_leaf; |
|
562
|
|
|
$this->current_leaf = $new_leaf; |
|
563
|
|
|
} else { |
|
564
|
|
|
$new_parent = !$this->current_leaf instanceof ParseTree\Root ? $this->current_leaf : null; |
|
565
|
|
|
|
|
566
|
|
|
if (!$next_token) { |
|
567
|
|
|
throw new TypeParseTreeException('Unexpected token ?'); |
|
568
|
|
|
} |
|
569
|
|
|
|
|
570
|
|
|
$new_leaf = new ParseTree\NullableTree( |
|
571
|
|
|
$new_parent |
|
572
|
|
|
); |
|
573
|
|
|
|
|
574
|
|
|
if ($this->current_leaf instanceof ParseTree\Root) { |
|
575
|
|
|
$this->current_leaf = $this->parse_tree = $new_leaf; |
|
576
|
|
|
return; |
|
577
|
|
|
} |
|
578
|
|
|
|
|
579
|
|
|
if ($new_leaf->parent) { |
|
580
|
|
|
$new_leaf->parent->children[] = $new_leaf; |
|
581
|
|
|
} |
|
582
|
|
|
|
|
583
|
|
|
$this->current_leaf = $new_leaf; |
|
584
|
|
|
} |
|
585
|
|
|
} |
|
586
|
|
|
} |
|
587
|
|
|
|
|
588
|
|
|
private function handleBar() : void |
|
589
|
|
|
{ |
|
590
|
|
|
if ($this->current_leaf instanceof ParseTree\Root) { |
|
591
|
|
|
throw new TypeParseTreeException('Unexpected token |'); |
|
592
|
|
|
} |
|
593
|
|
|
|
|
594
|
|
|
$current_parent = $this->current_leaf->parent; |
|
595
|
|
|
|
|
596
|
|
|
if ($current_parent instanceof ParseTree\CallableWithReturnTypeTree) { |
|
597
|
|
|
$this->current_leaf = $current_parent; |
|
598
|
|
|
$current_parent = $current_parent->parent; |
|
599
|
|
|
} |
|
600
|
|
|
|
|
601
|
|
|
if ($current_parent instanceof ParseTree\NullableTree) { |
|
602
|
|
|
$this->current_leaf = $current_parent; |
|
603
|
|
|
$current_parent = $current_parent->parent; |
|
604
|
|
|
} |
|
605
|
|
|
|
|
606
|
|
|
if ($this->current_leaf instanceof ParseTree\UnionTree) { |
|
607
|
|
|
throw new TypeParseTreeException('Unexpected token |'); |
|
608
|
|
|
} |
|
609
|
|
|
|
|
610
|
|
|
if ($current_parent && $current_parent instanceof ParseTree\UnionTree) { |
|
611
|
|
|
$this->current_leaf = $current_parent; |
|
612
|
|
|
return; |
|
613
|
|
|
} |
|
614
|
|
|
|
|
615
|
|
|
if ($current_parent && $current_parent instanceof ParseTree\IntersectionTree) { |
|
616
|
|
|
$this->current_leaf = $current_parent; |
|
617
|
|
|
$current_parent = $this->current_leaf->parent; |
|
618
|
|
|
} |
|
619
|
|
|
|
|
620
|
|
|
if ($current_parent instanceof ParseTree\TemplateIsTree) { |
|
621
|
|
|
$new_parent_leaf = new ParseTree\UnionTree($this->current_leaf); |
|
622
|
|
|
$new_parent_leaf->children = [$this->current_leaf]; |
|
623
|
|
|
$new_parent_leaf->parent = $current_parent; |
|
624
|
|
|
$this->current_leaf->parent = $new_parent_leaf; |
|
625
|
|
|
} else { |
|
626
|
|
|
$new_parent_leaf = new ParseTree\UnionTree($current_parent); |
|
627
|
|
|
$new_parent_leaf->children = [$this->current_leaf]; |
|
628
|
|
|
$this->current_leaf->parent = $new_parent_leaf; |
|
629
|
|
|
} |
|
630
|
|
|
|
|
631
|
|
|
if ($current_parent) { |
|
632
|
|
|
array_pop($current_parent->children); |
|
633
|
|
|
$current_parent->children[] = $new_parent_leaf; |
|
634
|
|
|
} else { |
|
635
|
|
|
$this->parse_tree = $new_parent_leaf; |
|
636
|
|
|
} |
|
637
|
|
|
|
|
638
|
|
|
$this->current_leaf = $new_parent_leaf; |
|
639
|
|
|
} |
|
640
|
|
|
|
|
641
|
|
|
private function handleAmpersand() : void |
|
642
|
|
|
{ |
|
643
|
|
|
if ($this->current_leaf instanceof ParseTree\Root) { |
|
644
|
|
|
throw new TypeParseTreeException( |
|
645
|
|
|
'Unexpected &' |
|
646
|
|
|
); |
|
647
|
|
|
} |
|
648
|
|
|
|
|
649
|
|
|
$current_parent = $this->current_leaf->parent; |
|
650
|
|
|
|
|
651
|
|
|
if ($this->current_leaf instanceof ParseTree\MethodTree && $current_parent) { |
|
652
|
|
|
$this->createMethodParam($this->type_tokens[$this->t], $current_parent); |
|
653
|
|
|
return; |
|
654
|
|
|
} |
|
655
|
|
|
|
|
656
|
|
|
if ($current_parent && $current_parent instanceof ParseTree\IntersectionTree) { |
|
657
|
|
|
$this->current_leaf = $current_parent; |
|
658
|
|
|
return; |
|
659
|
|
|
} |
|
660
|
|
|
|
|
661
|
|
|
$new_parent_leaf = new ParseTree\IntersectionTree($current_parent); |
|
662
|
|
|
$new_parent_leaf->children = [$this->current_leaf]; |
|
663
|
|
|
$this->current_leaf->parent = $new_parent_leaf; |
|
664
|
|
|
|
|
665
|
|
|
if ($current_parent) { |
|
666
|
|
|
array_pop($current_parent->children); |
|
667
|
|
|
$current_parent->children[] = $new_parent_leaf; |
|
668
|
|
|
} else { |
|
669
|
|
|
$this->parse_tree = $new_parent_leaf; |
|
670
|
|
|
} |
|
671
|
|
|
|
|
672
|
|
|
$this->current_leaf = $new_parent_leaf; |
|
673
|
|
|
} |
|
674
|
|
|
|
|
675
|
|
|
/** @param array{0: string, 1: int} $type_token */ |
|
676
|
|
|
private function handleIsOrAs($type_token) : void |
|
677
|
|
|
{ |
|
678
|
|
|
if ($this->t === 0) { |
|
679
|
|
|
$this->handleValue($type_token); |
|
680
|
|
|
} else { |
|
681
|
|
|
$current_parent = $this->current_leaf->parent; |
|
682
|
|
|
|
|
683
|
|
|
if ($current_parent) { |
|
684
|
|
|
array_pop($current_parent->children); |
|
685
|
|
|
} |
|
686
|
|
|
|
|
687
|
|
|
if ($type_token[0] === 'as') { |
|
688
|
|
|
$next_token = $this->t + 1 < $this->type_token_count ? $this->type_tokens[$this->t + 1] : null; |
|
689
|
|
|
|
|
690
|
|
|
if (!$this->current_leaf instanceof ParseTree\Value |
|
691
|
|
|
|| !$current_parent instanceof ParseTree\GenericTree |
|
692
|
|
|
|| !$next_token |
|
693
|
|
|
) { |
|
694
|
|
|
throw new TypeParseTreeException('Unexpected token ' . $type_token[0]); |
|
695
|
|
|
} |
|
696
|
|
|
|
|
697
|
|
|
$this->current_leaf = new ParseTree\TemplateAsTree( |
|
698
|
|
|
$this->current_leaf->value, |
|
699
|
|
|
$next_token[0], |
|
700
|
|
|
$current_parent |
|
701
|
|
|
); |
|
702
|
|
|
|
|
703
|
|
|
$current_parent->children[] = $this->current_leaf; |
|
704
|
|
|
++$this->t; |
|
705
|
|
|
} elseif ($this->current_leaf instanceof ParseTree\Value) { |
|
706
|
|
|
$this->current_leaf = new ParseTree\TemplateIsTree( |
|
707
|
|
|
$this->current_leaf->value, |
|
708
|
|
|
$current_parent |
|
709
|
|
|
); |
|
710
|
|
|
|
|
711
|
|
|
if ($current_parent) { |
|
712
|
|
|
$current_parent->children[] = $this->current_leaf; |
|
713
|
|
|
} |
|
714
|
|
|
} |
|
715
|
|
|
} |
|
716
|
|
|
} |
|
717
|
|
|
|
|
718
|
|
|
/** @param array{0: string, 1: int} $type_token */ |
|
719
|
|
|
private function handleValue($type_token) : void |
|
720
|
|
|
{ |
|
721
|
|
|
$new_parent = !$this->current_leaf instanceof ParseTree\Root ? $this->current_leaf : null; |
|
722
|
|
|
|
|
723
|
|
View Code Duplication |
if ($this->current_leaf instanceof ParseTree\MethodTree && $type_token[0][0] === '$') { |
|
|
|
|
|
|
724
|
|
|
$this->createMethodParam($type_token, $this->current_leaf); |
|
725
|
|
|
return; |
|
726
|
|
|
} |
|
727
|
|
|
|
|
728
|
|
|
$next_token = $this->t + 1 < $this->type_token_count ? $this->type_tokens[$this->t + 1] : null; |
|
729
|
|
|
|
|
730
|
|
|
switch ($next_token[0] ?? null) { |
|
731
|
|
|
case '<': |
|
732
|
|
|
$new_leaf = new ParseTree\GenericTree( |
|
733
|
|
|
$type_token[0], |
|
734
|
|
|
$new_parent |
|
735
|
|
|
); |
|
736
|
|
|
++$this->t; |
|
737
|
|
|
break; |
|
738
|
|
|
|
|
739
|
|
|
case '{': |
|
740
|
|
|
$new_leaf = new ParseTree\ObjectLikeTree( |
|
741
|
|
|
$type_token[0], |
|
742
|
|
|
$new_parent |
|
743
|
|
|
); |
|
744
|
|
|
++$this->t; |
|
745
|
|
|
break; |
|
746
|
|
|
|
|
747
|
|
|
case '(': |
|
748
|
|
|
if (in_array(strtolower($type_token[0]), ['closure', 'callable', '\closure'], true)) { |
|
749
|
|
|
$new_leaf = new ParseTree\CallableTree( |
|
750
|
|
|
$type_token[0], |
|
751
|
|
|
$new_parent |
|
752
|
|
|
); |
|
753
|
|
|
} elseif ($type_token[0] !== 'array' |
|
754
|
|
|
&& $type_token[0][0] !== '\\' |
|
755
|
|
|
&& $this->current_leaf instanceof ParseTree\Root |
|
756
|
|
|
) { |
|
757
|
|
|
$new_leaf = new ParseTree\MethodTree( |
|
758
|
|
|
$type_token[0], |
|
759
|
|
|
$new_parent |
|
760
|
|
|
); |
|
761
|
|
|
} else { |
|
762
|
|
|
throw new TypeParseTreeException( |
|
763
|
|
|
'Bracket must be preceded by “Closure”, “callable” or a valid @method name' |
|
764
|
|
|
); |
|
765
|
|
|
} |
|
766
|
|
|
|
|
767
|
|
|
++$this->t; |
|
768
|
|
|
break; |
|
769
|
|
|
|
|
770
|
|
|
case '::': |
|
771
|
|
|
$nexter_token = $this->t + 2 < $this->type_token_count ? $this->type_tokens[$this->t + 2] : null; |
|
772
|
|
|
|
|
773
|
|
|
if ($this->current_leaf instanceof ParseTree\ObjectLikeTree) { |
|
774
|
|
|
throw new TypeParseTreeException( |
|
775
|
|
|
'Unexpected :: in array key' |
|
776
|
|
|
); |
|
777
|
|
|
} |
|
778
|
|
|
|
|
779
|
|
|
if (!$nexter_token |
|
780
|
|
|
|| (!preg_match('/^([a-zA-Z_][a-zA-Z_0-9]*\*?|\*)$/', $nexter_token[0]) |
|
781
|
|
|
&& strtolower($nexter_token[0]) !== 'class') |
|
782
|
|
|
) { |
|
783
|
|
|
throw new TypeParseTreeException( |
|
784
|
|
|
'Invalid class constant ' . ($nexter_token[0] ?? '<empty>') |
|
785
|
|
|
); |
|
786
|
|
|
} |
|
787
|
|
|
|
|
788
|
|
|
$new_leaf = new ParseTree\Value( |
|
789
|
|
|
$type_token[0] . '::' . $nexter_token[0], |
|
790
|
|
|
$type_token[1], |
|
791
|
|
|
$type_token[1] + 2 + strlen($nexter_token[0]), |
|
792
|
|
|
$new_parent |
|
793
|
|
|
); |
|
794
|
|
|
|
|
795
|
|
|
$this->t += 2; |
|
796
|
|
|
|
|
797
|
|
|
break; |
|
798
|
|
|
|
|
799
|
|
|
default: |
|
800
|
|
|
if ($type_token[0] === '$this') { |
|
801
|
|
|
$type_token[0] = 'static'; |
|
802
|
|
|
} |
|
803
|
|
|
|
|
804
|
|
|
$new_leaf = new ParseTree\Value( |
|
805
|
|
|
$type_token[0], |
|
806
|
|
|
$type_token[1], |
|
807
|
|
|
$type_token[1] + strlen($type_token[0]), |
|
808
|
|
|
$new_parent |
|
809
|
|
|
); |
|
810
|
|
|
break; |
|
811
|
|
|
} |
|
812
|
|
|
|
|
813
|
|
|
if ($this->current_leaf instanceof ParseTree\Root) { |
|
814
|
|
|
$this->current_leaf = $this->parse_tree = $new_leaf; |
|
815
|
|
|
return; |
|
816
|
|
|
} |
|
817
|
|
|
|
|
818
|
|
|
if ($new_leaf->parent) { |
|
819
|
|
|
$new_leaf->parent->children[] = $new_leaf; |
|
820
|
|
|
} |
|
821
|
|
|
|
|
822
|
|
|
$this->current_leaf = $new_leaf; |
|
823
|
|
|
} |
|
824
|
|
|
} |
|
825
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.