1: | <?php declare(strict_types = 1); |
2: | |
3: | namespace PHPStan\PhpDocParser\Parser; |
4: | |
5: | use LogicException; |
6: | use PHPStan\PhpDocParser\Ast; |
7: | use PHPStan\PhpDocParser\Lexer\Lexer; |
8: | use function strpos; |
9: | use function trim; |
10: | |
11: | class TypeParser |
12: | { |
13: | |
14: | |
15: | private $constExprParser; |
16: | |
17: | public function __construct(?ConstExprParser $constExprParser = null) |
18: | { |
19: | $this->constExprParser = $constExprParser; |
20: | } |
21: | |
22: | |
23: | public function parse(TokenIterator $tokens): Ast\Type\TypeNode |
24: | { |
25: | if ($tokens->isCurrentTokenType(Lexer::TOKEN_NULLABLE)) { |
26: | $type = $this->parseNullable($tokens); |
27: | |
28: | } else { |
29: | $type = $this->parseAtomic($tokens); |
30: | |
31: | if ($tokens->isCurrentTokenType(Lexer::TOKEN_UNION)) { |
32: | $type = $this->parseUnion($tokens, $type); |
33: | |
34: | } elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_INTERSECTION)) { |
35: | $type = $this->parseIntersection($tokens, $type); |
36: | } |
37: | } |
38: | |
39: | return $type; |
40: | } |
41: | |
42: | |
43: | private function subParse(TokenIterator $tokens): Ast\Type\TypeNode |
44: | { |
45: | if ($tokens->isCurrentTokenType(Lexer::TOKEN_NULLABLE)) { |
46: | $type = $this->parseNullable($tokens); |
47: | |
48: | } elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_VARIABLE)) { |
49: | $type = $this->parseConditionalForParameter($tokens, $tokens->currentTokenValue()); |
50: | |
51: | } else { |
52: | $type = $this->parseAtomic($tokens); |
53: | |
54: | if ($tokens->isCurrentTokenValue('is')) { |
55: | $type = $this->parseConditional($tokens, $type); |
56: | } else { |
57: | $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); |
58: | |
59: | if ($tokens->isCurrentTokenType(Lexer::TOKEN_UNION)) { |
60: | $type = $this->subParseUnion($tokens, $type); |
61: | |
62: | } elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_INTERSECTION)) { |
63: | $type = $this->subParseIntersection($tokens, $type); |
64: | } |
65: | } |
66: | } |
67: | |
68: | return $type; |
69: | } |
70: | |
71: | |
72: | |
73: | private function parseAtomic(TokenIterator $tokens): Ast\Type\TypeNode |
74: | { |
75: | if ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) { |
76: | $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); |
77: | $type = $this->subParse($tokens); |
78: | $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); |
79: | |
80: | $tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PARENTHESES); |
81: | |
82: | if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) { |
83: | return $this->tryParseArrayOrOffsetAccess($tokens, $type); |
84: | } |
85: | |
86: | return $type; |
87: | } |
88: | |
89: | if ($tokens->tryConsumeTokenType(Lexer::TOKEN_THIS_VARIABLE)) { |
90: | $type = new Ast\Type\ThisTypeNode(); |
91: | |
92: | if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) { |
93: | return $this->tryParseArrayOrOffsetAccess($tokens, $type); |
94: | } |
95: | |
96: | return $type; |
97: | } |
98: | |
99: | $currentTokenValue = $tokens->currentTokenValue(); |
100: | $tokens->pushSavePoint(); |
101: | if ($tokens->tryConsumeTokenType(Lexer::TOKEN_IDENTIFIER)) { |
102: | $type = new Ast\Type\IdentifierTypeNode($currentTokenValue); |
103: | |
104: | if (!$tokens->isCurrentTokenType(Lexer::TOKEN_DOUBLE_COLON)) { |
105: | $tokens->dropSavePoint(); |
106: | if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET)) { |
107: | $tokens->pushSavePoint(); |
108: | |
109: | $isHtml = $this->isHtml($tokens); |
110: | $tokens->rollback(); |
111: | if ($isHtml) { |
112: | return $type; |
113: | } |
114: | |
115: | $type = $this->parseGeneric($tokens, $type); |
116: | |
117: | if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) { |
118: | $type = $this->tryParseArrayOrOffsetAccess($tokens, $type); |
119: | } |
120: | } elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) { |
121: | $type = $this->tryParseCallable($tokens, $type); |
122: | |
123: | } elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) { |
124: | $type = $this->tryParseArrayOrOffsetAccess($tokens, $type); |
125: | |
126: | } elseif ($type->name === 'array' && $tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_CURLY_BRACKET) && !$tokens->isPrecededByHorizontalWhitespace()) { |
127: | $type = $this->parseArrayShape($tokens, $type); |
128: | |
129: | if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) { |
130: | $type = $this->tryParseArrayOrOffsetAccess($tokens, $type); |
131: | } |
132: | } |
133: | |
134: | return $type; |
135: | } else { |
136: | $tokens->rollback(); |
137: | } |
138: | } else { |
139: | $tokens->dropSavePoint(); |
140: | } |
141: | |
142: | $exception = new ParserException( |
143: | $tokens->currentTokenValue(), |
144: | $tokens->currentTokenType(), |
145: | $tokens->currentTokenOffset(), |
146: | Lexer::TOKEN_IDENTIFIER |
147: | ); |
148: | |
149: | if ($this->constExprParser === null) { |
150: | throw $exception; |
151: | } |
152: | |
153: | try { |
154: | $constExpr = $this->constExprParser->parse($tokens, true); |
155: | if ($constExpr instanceof Ast\ConstExpr\ConstExprArrayNode) { |
156: | throw $exception; |
157: | } |
158: | |
159: | return new Ast\Type\ConstTypeNode($constExpr); |
160: | } catch (LogicException $e) { |
161: | throw $exception; |
162: | } |
163: | } |
164: | |
165: | |
166: | |
167: | private function parseUnion(TokenIterator $tokens, Ast\Type\TypeNode $type): Ast\Type\TypeNode |
168: | { |
169: | $types = [$type]; |
170: | |
171: | while ($tokens->tryConsumeTokenType(Lexer::TOKEN_UNION)) { |
172: | $types[] = $this->parseAtomic($tokens); |
173: | } |
174: | |
175: | return new Ast\Type\UnionTypeNode($types); |
176: | } |
177: | |
178: | |
179: | |
180: | private function subParseUnion(TokenIterator $tokens, Ast\Type\TypeNode $type): Ast\Type\TypeNode |
181: | { |
182: | $types = [$type]; |
183: | |
184: | while ($tokens->tryConsumeTokenType(Lexer::TOKEN_UNION)) { |
185: | $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); |
186: | $types[] = $this->parseAtomic($tokens); |
187: | $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); |
188: | } |
189: | |
190: | return new Ast\Type\UnionTypeNode($types); |
191: | } |
192: | |
193: | |
194: | |
195: | private function parseIntersection(TokenIterator $tokens, Ast\Type\TypeNode $type): Ast\Type\TypeNode |
196: | { |
197: | $types = [$type]; |
198: | |
199: | while ($tokens->tryConsumeTokenType(Lexer::TOKEN_INTERSECTION)) { |
200: | $types[] = $this->parseAtomic($tokens); |
201: | } |
202: | |
203: | return new Ast\Type\IntersectionTypeNode($types); |
204: | } |
205: | |
206: | |
207: | |
208: | private function subParseIntersection(TokenIterator $tokens, Ast\Type\TypeNode $type): Ast\Type\TypeNode |
209: | { |
210: | $types = [$type]; |
211: | |
212: | while ($tokens->tryConsumeTokenType(Lexer::TOKEN_INTERSECTION)) { |
213: | $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); |
214: | $types[] = $this->parseAtomic($tokens); |
215: | $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); |
216: | } |
217: | |
218: | return new Ast\Type\IntersectionTypeNode($types); |
219: | } |
220: | |
221: | |
222: | |
223: | private function parseConditional(TokenIterator $tokens, Ast\Type\TypeNode $subjectType): Ast\Type\TypeNode |
224: | { |
225: | $tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); |
226: | |
227: | $negated = false; |
228: | if ($tokens->isCurrentTokenValue('not')) { |
229: | $negated = true; |
230: | $tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); |
231: | } |
232: | |
233: | $targetType = $this->parse($tokens); |
234: | |
235: | $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); |
236: | $tokens->consumeTokenType(Lexer::TOKEN_NULLABLE); |
237: | $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); |
238: | |
239: | $ifType = $this->parse($tokens); |
240: | |
241: | $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); |
242: | $tokens->consumeTokenType(Lexer::TOKEN_COLON); |
243: | $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); |
244: | |
245: | $elseType = $this->parse($tokens); |
246: | |
247: | return new Ast\Type\ConditionalTypeNode($subjectType, $targetType, $ifType, $elseType, $negated); |
248: | } |
249: | |
250: | |
251: | private function parseConditionalForParameter(TokenIterator $tokens, string $parameterName): Ast\Type\TypeNode |
252: | { |
253: | $tokens->consumeTokenType(Lexer::TOKEN_VARIABLE); |
254: | $tokens->consumeTokenValue(Lexer::TOKEN_IDENTIFIER, 'is'); |
255: | |
256: | $negated = false; |
257: | if ($tokens->isCurrentTokenValue('not')) { |
258: | $negated = true; |
259: | $tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); |
260: | } |
261: | |
262: | $targetType = $this->parse($tokens); |
263: | |
264: | $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); |
265: | $tokens->consumeTokenType(Lexer::TOKEN_NULLABLE); |
266: | $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); |
267: | |
268: | $ifType = $this->parse($tokens); |
269: | |
270: | $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); |
271: | $tokens->consumeTokenType(Lexer::TOKEN_COLON); |
272: | $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); |
273: | |
274: | $elseType = $this->parse($tokens); |
275: | |
276: | return new Ast\Type\ConditionalTypeForParameterNode($parameterName, $targetType, $ifType, $elseType, $negated); |
277: | } |
278: | |
279: | |
280: | |
281: | private function parseNullable(TokenIterator $tokens): Ast\Type\TypeNode |
282: | { |
283: | $tokens->consumeTokenType(Lexer::TOKEN_NULLABLE); |
284: | |
285: | $type = $this->parseAtomic($tokens); |
286: | |
287: | return new Ast\Type\NullableTypeNode($type); |
288: | } |
289: | |
290: | |
291: | public function isHtml(TokenIterator $tokens): bool |
292: | { |
293: | $tokens->consumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET); |
294: | |
295: | if (!$tokens->isCurrentTokenType(Lexer::TOKEN_IDENTIFIER)) { |
296: | return false; |
297: | } |
298: | |
299: | $htmlTagName = $tokens->currentTokenValue(); |
300: | |
301: | $tokens->next(); |
302: | |
303: | if (!$tokens->tryConsumeTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET)) { |
304: | return false; |
305: | } |
306: | |
307: | while (!$tokens->isCurrentTokenType(Lexer::TOKEN_END)) { |
308: | if ( |
309: | $tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET) |
310: | && strpos($tokens->currentTokenValue(), '/' . $htmlTagName . '>') !== false |
311: | ) { |
312: | return true; |
313: | } |
314: | |
315: | $tokens->next(); |
316: | } |
317: | |
318: | return false; |
319: | } |
320: | |
321: | |
322: | public function parseGeneric(TokenIterator $tokens, Ast\Type\IdentifierTypeNode $baseType): Ast\Type\GenericTypeNode |
323: | { |
324: | $tokens->consumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET); |
325: | $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); |
326: | $genericTypes = [$this->parse($tokens)]; |
327: | |
328: | $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); |
329: | |
330: | while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)) { |
331: | $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); |
332: | if ($tokens->tryConsumeTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET)) { |
333: | |
334: | return new Ast\Type\GenericTypeNode($baseType, $genericTypes); |
335: | } |
336: | $genericTypes[] = $this->parse($tokens); |
337: | $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); |
338: | } |
339: | |
340: | $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); |
341: | $tokens->consumeTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET); |
342: | |
343: | return new Ast\Type\GenericTypeNode($baseType, $genericTypes); |
344: | } |
345: | |
346: | |
347: | |
348: | private function parseCallable(TokenIterator $tokens, Ast\Type\IdentifierTypeNode $identifier): Ast\Type\TypeNode |
349: | { |
350: | $tokens->consumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES); |
351: | $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); |
352: | |
353: | $parameters = []; |
354: | if (!$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PARENTHESES)) { |
355: | $parameters[] = $this->parseCallableParameter($tokens); |
356: | $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); |
357: | while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)) { |
358: | $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); |
359: | if ($tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PARENTHESES)) { |
360: | break; |
361: | } |
362: | $parameters[] = $this->parseCallableParameter($tokens); |
363: | $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); |
364: | } |
365: | } |
366: | |
367: | $tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PARENTHESES); |
368: | $tokens->consumeTokenType(Lexer::TOKEN_COLON); |
369: | $returnType = $this->parseCallableReturnType($tokens); |
370: | |
371: | return new Ast\Type\CallableTypeNode($identifier, $parameters, $returnType); |
372: | } |
373: | |
374: | |
375: | |
376: | private function parseCallableParameter(TokenIterator $tokens): Ast\Type\CallableTypeParameterNode |
377: | { |
378: | $type = $this->parse($tokens); |
379: | $isReference = $tokens->tryConsumeTokenType(Lexer::TOKEN_REFERENCE); |
380: | $isVariadic = $tokens->tryConsumeTokenType(Lexer::TOKEN_VARIADIC); |
381: | |
382: | if ($tokens->isCurrentTokenType(Lexer::TOKEN_VARIABLE)) { |
383: | $parameterName = $tokens->currentTokenValue(); |
384: | $tokens->consumeTokenType(Lexer::TOKEN_VARIABLE); |
385: | |
386: | } else { |
387: | $parameterName = ''; |
388: | } |
389: | |
390: | $isOptional = $tokens->tryConsumeTokenType(Lexer::TOKEN_EQUAL); |
391: | return new Ast\Type\CallableTypeParameterNode($type, $isReference, $isVariadic, $parameterName, $isOptional); |
392: | } |
393: | |
394: | |
395: | |
396: | private function parseCallableReturnType(TokenIterator $tokens): Ast\Type\TypeNode |
397: | { |
398: | if ($tokens->isCurrentTokenType(Lexer::TOKEN_NULLABLE)) { |
399: | $type = $this->parseNullable($tokens); |
400: | |
401: | } elseif ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) { |
402: | $type = $this->parse($tokens); |
403: | $tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PARENTHESES); |
404: | |
405: | } else { |
406: | $type = new Ast\Type\IdentifierTypeNode($tokens->currentTokenValue()); |
407: | $tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); |
408: | |
409: | if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET)) { |
410: | $type = $this->parseGeneric($tokens, $type); |
411: | |
412: | } elseif ($type->name === 'array' && $tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_CURLY_BRACKET) && !$tokens->isPrecededByHorizontalWhitespace()) { |
413: | $type = $this->parseArrayShape($tokens, $type); |
414: | } |
415: | } |
416: | |
417: | if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) { |
418: | $type = $this->tryParseArrayOrOffsetAccess($tokens, $type); |
419: | } |
420: | |
421: | return $type; |
422: | } |
423: | |
424: | |
425: | |
426: | private function tryParseCallable(TokenIterator $tokens, Ast\Type\IdentifierTypeNode $identifier): Ast\Type\TypeNode |
427: | { |
428: | try { |
429: | $tokens->pushSavePoint(); |
430: | $type = $this->parseCallable($tokens, $identifier); |
431: | $tokens->dropSavePoint(); |
432: | |
433: | } catch (ParserException $e) { |
434: | $tokens->rollback(); |
435: | $type = $identifier; |
436: | } |
437: | |
438: | return $type; |
439: | } |
440: | |
441: | |
442: | |
443: | private function tryParseArrayOrOffsetAccess(TokenIterator $tokens, Ast\Type\TypeNode $type): Ast\Type\TypeNode |
444: | { |
445: | try { |
446: | while ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) { |
447: | $tokens->pushSavePoint(); |
448: | |
449: | $canBeOffsetAccessType = !$tokens->isPrecededByHorizontalWhitespace(); |
450: | $tokens->consumeTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET); |
451: | |
452: | if ($canBeOffsetAccessType && !$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_SQUARE_BRACKET)) { |
453: | $offset = $this->parse($tokens); |
454: | $tokens->consumeTokenType(Lexer::TOKEN_CLOSE_SQUARE_BRACKET); |
455: | $tokens->dropSavePoint(); |
456: | $type = new Ast\Type\OffsetAccessTypeNode($type, $offset); |
457: | } else { |
458: | $tokens->consumeTokenType(Lexer::TOKEN_CLOSE_SQUARE_BRACKET); |
459: | $tokens->dropSavePoint(); |
460: | $type = new Ast\Type\ArrayTypeNode($type); |
461: | } |
462: | } |
463: | |
464: | } catch (ParserException $e) { |
465: | $tokens->rollback(); |
466: | } |
467: | |
468: | return $type; |
469: | } |
470: | |
471: | |
472: | |
473: | private function parseArrayShape(TokenIterator $tokens, Ast\Type\TypeNode $type): Ast\Type\ArrayShapeNode |
474: | { |
475: | $tokens->consumeTokenType(Lexer::TOKEN_OPEN_CURLY_BRACKET); |
476: | if ($tokens->tryConsumeTokenType(Lexer::TOKEN_CLOSE_CURLY_BRACKET)) { |
477: | return new Ast\Type\ArrayShapeNode([]); |
478: | } |
479: | |
480: | $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); |
481: | $items = [$this->parseArrayShapeItem($tokens)]; |
482: | |
483: | $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); |
484: | while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)) { |
485: | $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); |
486: | if ($tokens->tryConsumeTokenType(Lexer::TOKEN_CLOSE_CURLY_BRACKET)) { |
487: | |
488: | return new Ast\Type\ArrayShapeNode($items); |
489: | } |
490: | |
491: | $items[] = $this->parseArrayShapeItem($tokens); |
492: | $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); |
493: | } |
494: | |
495: | $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); |
496: | $tokens->consumeTokenType(Lexer::TOKEN_CLOSE_CURLY_BRACKET); |
497: | |
498: | return new Ast\Type\ArrayShapeNode($items); |
499: | } |
500: | |
501: | |
502: | |
503: | private function parseArrayShapeItem(TokenIterator $tokens): Ast\Type\ArrayShapeItemNode |
504: | { |
505: | try { |
506: | $tokens->pushSavePoint(); |
507: | $key = $this->parseArrayShapeKey($tokens); |
508: | $optional = $tokens->tryConsumeTokenType(Lexer::TOKEN_NULLABLE); |
509: | $tokens->consumeTokenType(Lexer::TOKEN_COLON); |
510: | $value = $this->parse($tokens); |
511: | $tokens->dropSavePoint(); |
512: | |
513: | return new Ast\Type\ArrayShapeItemNode($key, $optional, $value); |
514: | } catch (ParserException $e) { |
515: | $tokens->rollback(); |
516: | $value = $this->parse($tokens); |
517: | |
518: | return new Ast\Type\ArrayShapeItemNode(null, false, $value); |
519: | } |
520: | } |
521: | |
522: | |
523: | |
524: | |
525: | |
526: | private function parseArrayShapeKey(TokenIterator $tokens) |
527: | { |
528: | if ($tokens->isCurrentTokenType(Lexer::TOKEN_INTEGER)) { |
529: | $key = new Ast\ConstExpr\ConstExprIntegerNode($tokens->currentTokenValue()); |
530: | $tokens->next(); |
531: | |
532: | } elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_SINGLE_QUOTED_STRING)) { |
533: | $key = new Ast\ConstExpr\ConstExprStringNode(trim($tokens->currentTokenValue(), "'")); |
534: | $tokens->next(); |
535: | |
536: | } elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_DOUBLE_QUOTED_STRING)) { |
537: | $key = new Ast\ConstExpr\ConstExprStringNode(trim($tokens->currentTokenValue(), '"')); |
538: | $tokens->next(); |
539: | |
540: | } else { |
541: | $key = new Ast\Type\IdentifierTypeNode($tokens->currentTokenValue()); |
542: | $tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); |
543: | } |
544: | |
545: | return $key; |
546: | } |
547: | |
548: | } |
549: | |