1 | <?php |
||||
2 | |||||
3 | namespace Digia\GraphQL\Language; |
||||
4 | |||||
5 | use Digia\GraphQL\Error\InvariantException; |
||||
6 | use Digia\GraphQL\Language\Node\ArgumentNode; |
||||
7 | use Digia\GraphQL\Language\Node\BooleanValueNode; |
||||
8 | use Digia\GraphQL\Language\Node\DirectiveDefinitionNode; |
||||
9 | use Digia\GraphQL\Language\Node\DirectiveNode; |
||||
10 | use Digia\GraphQL\Language\Node\DocumentNode; |
||||
11 | use Digia\GraphQL\Language\Node\EnumTypeDefinitionNode; |
||||
12 | use Digia\GraphQL\Language\Node\EnumTypeExtensionNode; |
||||
13 | use Digia\GraphQL\Language\Node\EnumValueDefinitionNode; |
||||
14 | use Digia\GraphQL\Language\Node\EnumValueNode; |
||||
15 | use Digia\GraphQL\Language\Node\ExecutableDefinitionNodeInterface; |
||||
16 | use Digia\GraphQL\Language\Node\FieldDefinitionNode; |
||||
17 | use Digia\GraphQL\Language\Node\FieldNode; |
||||
18 | use Digia\GraphQL\Language\Node\FloatValueNode; |
||||
19 | use Digia\GraphQL\Language\Node\FragmentDefinitionNode; |
||||
20 | use Digia\GraphQL\Language\Node\FragmentNodeInterface; |
||||
21 | use Digia\GraphQL\Language\Node\FragmentSpreadNode; |
||||
22 | use Digia\GraphQL\Language\Node\InlineFragmentNode; |
||||
23 | use Digia\GraphQL\Language\Node\InputObjectTypeDefinitionNode; |
||||
24 | use Digia\GraphQL\Language\Node\InputObjectTypeExtensionNode; |
||||
25 | use Digia\GraphQL\Language\Node\InputValueDefinitionNode; |
||||
26 | use Digia\GraphQL\Language\Node\InterfaceTypeDefinitionNode; |
||||
27 | use Digia\GraphQL\Language\Node\InterfaceTypeExtensionNode; |
||||
28 | use Digia\GraphQL\Language\Node\IntValueNode; |
||||
29 | use Digia\GraphQL\Language\Node\ListTypeNode; |
||||
30 | use Digia\GraphQL\Language\Node\ListValueNode; |
||||
31 | use Digia\GraphQL\Language\Node\NamedTypeNode; |
||||
32 | use Digia\GraphQL\Language\Node\NameNode; |
||||
33 | use Digia\GraphQL\Language\Node\NodeInterface; |
||||
34 | use Digia\GraphQL\Language\Node\NonNullTypeNode; |
||||
35 | use Digia\GraphQL\Language\Node\NullValueNode; |
||||
36 | use Digia\GraphQL\Language\Node\ObjectFieldNode; |
||||
37 | use Digia\GraphQL\Language\Node\ObjectTypeDefinitionNode; |
||||
38 | use Digia\GraphQL\Language\Node\ObjectTypeExtensionNode; |
||||
39 | use Digia\GraphQL\Language\Node\ObjectValueNode; |
||||
40 | use Digia\GraphQL\Language\Node\OperationDefinitionNode; |
||||
41 | use Digia\GraphQL\Language\Node\OperationTypeDefinitionNode; |
||||
42 | use Digia\GraphQL\Language\Node\ScalarTypeDefinitionNode; |
||||
43 | use Digia\GraphQL\Language\Node\ScalarTypeExtensionNode; |
||||
44 | use Digia\GraphQL\Language\Node\SchemaDefinitionNode; |
||||
45 | use Digia\GraphQL\Language\Node\SchemaExtensionNode; |
||||
46 | use Digia\GraphQL\Language\Node\SelectionSetNode; |
||||
47 | use Digia\GraphQL\Language\Node\StringValueNode; |
||||
48 | use Digia\GraphQL\Language\Node\TypeNodeInterface; |
||||
49 | use Digia\GraphQL\Language\Node\TypeSystemDefinitionNodeInterface; |
||||
50 | use Digia\GraphQL\Language\Node\TypeSystemExtensionNodeInterface; |
||||
51 | use Digia\GraphQL\Language\Node\UnionTypeDefinitionNode; |
||||
52 | use Digia\GraphQL\Language\Node\UnionTypeExtensionNode; |
||||
53 | use Digia\GraphQL\Language\Node\ValueNodeInterface; |
||||
54 | use Digia\GraphQL\Language\Node\VariableDefinitionNode; |
||||
55 | use Digia\GraphQL\Language\Node\VariableNode; |
||||
56 | use function Digia\GraphQL\Util\arraySome; |
||||
57 | |||||
58 | /** @noinspection PhpHierarchyChecksInspection */ |
||||
59 | |||||
60 | /** |
||||
61 | * Class Parser |
||||
62 | * @package Digia\GraphQL\Language |
||||
63 | */ |
||||
64 | class Parser implements ParserInterface |
||||
65 | { |
||||
66 | /** |
||||
67 | * @var LexerInterface |
||||
68 | */ |
||||
69 | protected $lexer; |
||||
70 | |||||
71 | /** |
||||
72 | * @param string $name |
||||
73 | * @param array $arguments |
||||
74 | * @return mixed |
||||
75 | * |
||||
76 | * @throws InvariantException |
||||
77 | * @throws SyntaxErrorException |
||||
78 | */ |
||||
79 | public function __call(string $name, array $arguments) |
||||
80 | { |
||||
81 | $lexCallback = \str_replace('parse', 'lex', $name); |
||||
82 | |||||
83 | if (\method_exists($this, $lexCallback)) { |
||||
84 | return $this->parsePartial([$this, $lexCallback], $arguments[0], $arguments[1] ?? []); |
||||
85 | } |
||||
86 | |||||
87 | return $this; |
||||
88 | } |
||||
89 | |||||
90 | /** |
||||
91 | * Given a GraphQL source, parses it into a Document. |
||||
92 | * Throws GraphQLError if a syntax error is encountered. |
||||
93 | * |
||||
94 | * @inheritdoc |
||||
95 | * @throws SyntaxErrorException |
||||
96 | * @throws \ReflectionException |
||||
97 | * @throws InvariantException |
||||
98 | */ |
||||
99 | public function parse(Source $source, array $options = []): DocumentNode |
||||
100 | { |
||||
101 | $this->lexer = $this->createLexer($source, $options); |
||||
102 | |||||
103 | return $this->lexDocument(); |
||||
104 | } |
||||
105 | |||||
106 | /** |
||||
107 | * @param callable $lexCallback |
||||
108 | * @param Source $source |
||||
109 | * @param array $options |
||||
110 | * @return NodeInterface |
||||
111 | * @throws SyntaxErrorException |
||||
112 | */ |
||||
113 | protected function parsePartial(callable $lexCallback, Source $source, array $options = []): NodeInterface |
||||
114 | { |
||||
115 | $this->lexer = $this->createLexer($source, $options); |
||||
116 | |||||
117 | $this->expect(TokenKindEnum::SOF); |
||||
118 | $node = $lexCallback(); |
||||
119 | $this->expect(TokenKindEnum::EOF); |
||||
120 | |||||
121 | return $node; |
||||
122 | } |
||||
123 | |||||
124 | /** |
||||
125 | * Converts a name lex token into a name parse node. |
||||
126 | * |
||||
127 | * @return NameNode |
||||
128 | * @throws SyntaxErrorException |
||||
129 | */ |
||||
130 | protected function lexName(): NameNode |
||||
131 | { |
||||
132 | $token = $this->expect(TokenKindEnum::NAME); |
||||
133 | |||||
134 | return new NameNode($token->getValue(), $this->createLocation($token)); |
||||
135 | } |
||||
136 | |||||
137 | // Implements the parsing rules in the Document section. |
||||
138 | |||||
139 | /** |
||||
140 | * Document : Definition+ |
||||
141 | * |
||||
142 | * @return DocumentNode |
||||
143 | * @throws SyntaxErrorException |
||||
144 | * @throws \ReflectionException |
||||
145 | */ |
||||
146 | protected function lexDocument(): DocumentNode |
||||
147 | { |
||||
148 | $start = $this->lexer->getToken(); |
||||
149 | |||||
150 | $this->expect(TokenKindEnum::SOF); |
||||
151 | |||||
152 | $definitions = []; |
||||
153 | |||||
154 | do { |
||||
155 | $definitions[] = $this->lexDefinition(); |
||||
156 | } while (!$this->skip(TokenKindEnum::EOF)); |
||||
157 | |||||
158 | return new DocumentNode($definitions, $this->createLocation($start)); |
||||
159 | } |
||||
160 | |||||
161 | /** |
||||
162 | * Definition : |
||||
163 | * - ExecutableDefinition |
||||
164 | * - TypeSystemDefinition |
||||
165 | * |
||||
166 | * @return NodeInterface |
||||
167 | * @throws SyntaxErrorException |
||||
168 | * @throws \ReflectionException |
||||
169 | */ |
||||
170 | protected function lexDefinition(): NodeInterface |
||||
171 | { |
||||
172 | if ($this->peek(TokenKindEnum::NAME)) { |
||||
173 | $token = $this->lexer->getToken(); |
||||
174 | |||||
175 | switch ($token->getValue()) { |
||||
176 | case KeywordEnum::QUERY: |
||||
177 | case KeywordEnum::MUTATION: |
||||
178 | case KeywordEnum::SUBSCRIPTION: |
||||
179 | case KeywordEnum::FRAGMENT: |
||||
180 | return $this->lexExecutableDefinition(); |
||||
181 | case KeywordEnum::SCHEMA: |
||||
182 | case KeywordEnum::SCALAR: |
||||
183 | case KeywordEnum::TYPE: |
||||
184 | case KeywordEnum::INTERFACE: |
||||
185 | case KeywordEnum::UNION: |
||||
186 | case KeywordEnum::ENUM: |
||||
187 | case KeywordEnum::INPUT: |
||||
188 | case KeywordEnum::DIRECTIVE: |
||||
189 | return $this->lexTypeSystemDefinition(); |
||||
190 | case KeywordEnum::EXTEND: |
||||
191 | return $this->lexTypeSystemExtension(); |
||||
192 | |||||
193 | } |
||||
194 | } elseif ($this->peek(TokenKindEnum::BRACE_L)) { |
||||
195 | return $this->lexExecutableDefinition(); |
||||
196 | } elseif ($this->peekDescription()) { |
||||
197 | return $this->lexTypeSystemDefinition(); |
||||
198 | } |
||||
199 | |||||
200 | throw $this->unexpected(); |
||||
201 | } |
||||
202 | |||||
203 | /** |
||||
204 | * ExecutableDefinition : |
||||
205 | * - OperationDefinition |
||||
206 | * - FragmentDefinition |
||||
207 | * |
||||
208 | * @return ExecutableDefinitionNodeInterface |
||||
209 | * @throws SyntaxErrorException |
||||
210 | */ |
||||
211 | protected function lexExecutableDefinition(): ExecutableDefinitionNodeInterface |
||||
212 | { |
||||
213 | if ($this->peek(TokenKindEnum::NAME)) { |
||||
214 | // Valid names are: query, mutation, subscription and fragment |
||||
215 | $token = $this->lexer->getToken(); |
||||
216 | |||||
217 | switch ($token->getValue()) { |
||||
218 | case KeywordEnum::QUERY: |
||||
219 | case KeywordEnum::MUTATION: |
||||
220 | case KeywordEnum::SUBSCRIPTION: |
||||
221 | return $this->lexOperationDefinition(); |
||||
222 | case KeywordEnum::FRAGMENT: |
||||
223 | return $this->lexFragmentDefinition(); |
||||
224 | } |
||||
225 | } elseif ($this->peek(TokenKindEnum::BRACE_L)) { |
||||
226 | // Anonymous query |
||||
227 | return $this->lexOperationDefinition(); |
||||
228 | } |
||||
229 | |||||
230 | throw $this->unexpected(); |
||||
231 | } |
||||
232 | |||||
233 | // Implements the parsing rules in the Operations section. |
||||
234 | |||||
235 | /** |
||||
236 | * OperationDefinition : |
||||
237 | * - SelectionSet |
||||
238 | * - OperationType Name? VariableDefinitions? Directives? SelectionSet |
||||
239 | * |
||||
240 | * @return OperationDefinitionNode |
||||
241 | * @throws SyntaxErrorException |
||||
242 | */ |
||||
243 | protected function lexOperationDefinition(): OperationDefinitionNode |
||||
244 | { |
||||
245 | $start = $this->lexer->getToken(); |
||||
246 | |||||
247 | if ($this->peek(TokenKindEnum::BRACE_L)) { |
||||
248 | // Anonymous query |
||||
249 | return new OperationDefinitionNode( |
||||
250 | KeywordEnum::QUERY, |
||||
251 | null, |
||||
252 | [], |
||||
253 | [], |
||||
254 | $this->lexSelectionSet(), |
||||
255 | $this->createLocation($start) |
||||
256 | ); |
||||
257 | } |
||||
258 | |||||
259 | $operation = $this->lexOperationType(); |
||||
260 | |||||
261 | if ($this->peek(TokenKindEnum::NAME)) { |
||||
262 | $name = $this->lexName(); |
||||
263 | } |
||||
264 | |||||
265 | return new OperationDefinitionNode( |
||||
266 | $operation, |
||||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||
267 | $name ?? null, |
||||
268 | $this->lexVariableDefinitions(), |
||||
269 | $this->lexDirectives(), |
||||
270 | $this->lexSelectionSet(), |
||||
271 | $this->createLocation($start) |
||||
272 | ); |
||||
273 | } |
||||
274 | |||||
275 | /** |
||||
276 | * OperationType : one of query mutation subscription |
||||
277 | * |
||||
278 | * @return null|string |
||||
279 | * @throws SyntaxErrorException |
||||
280 | */ |
||||
281 | protected function lexOperationType(): ?string |
||||
282 | { |
||||
283 | $token = $this->expect(TokenKindEnum::NAME); |
||||
284 | $value = $token->getValue(); |
||||
285 | |||||
286 | if (isOperation($value)) { |
||||
0 ignored issues
–
show
It seems like
$value can also be of type null ; however, parameter $value of Digia\GraphQL\Language\isOperation() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
287 | return $value; |
||||
288 | } |
||||
289 | |||||
290 | throw $this->unexpected($token); |
||||
291 | } |
||||
292 | |||||
293 | /** |
||||
294 | * VariableDefinitions : ( VariableDefinition+ ) |
||||
295 | * |
||||
296 | * @return array |
||||
297 | * @throws SyntaxErrorException |
||||
298 | */ |
||||
299 | protected function lexVariableDefinitions(): array |
||||
300 | { |
||||
301 | return $this->peek(TokenKindEnum::PAREN_L) |
||||
302 | ? $this->many( |
||||
303 | TokenKindEnum::PAREN_L, |
||||
304 | [$this, 'lexVariableDefinition'], |
||||
305 | TokenKindEnum::PAREN_R |
||||
306 | ) |
||||
307 | : []; |
||||
308 | } |
||||
309 | |||||
310 | /** |
||||
311 | * VariableDefinition : Variable : Type DefaultValue? |
||||
312 | * |
||||
313 | * @return VariableDefinitionNode |
||||
314 | * @throws SyntaxErrorException |
||||
315 | */ |
||||
316 | protected function lexVariableDefinition(): VariableDefinitionNode |
||||
317 | { |
||||
318 | $start = $this->lexer->getToken(); |
||||
319 | |||||
320 | /** |
||||
321 | * @return TypeNodeInterface |
||||
322 | */ |
||||
323 | $parseType = function (): TypeNodeInterface { |
||||
324 | $this->expect(TokenKindEnum::COLON); |
||||
325 | return $this->lexType(); |
||||
326 | }; |
||||
327 | |||||
328 | return new VariableDefinitionNode( |
||||
329 | $this->lexVariable(), |
||||
330 | $parseType(), |
||||
331 | $this->skip(TokenKindEnum::EQUALS) |
||||
332 | ? $this->lexValue(true) |
||||
333 | : null, |
||||
334 | $this->createLocation($start) |
||||
335 | ); |
||||
336 | } |
||||
337 | |||||
338 | /** |
||||
339 | * Variable : $ Name |
||||
340 | * |
||||
341 | * @return VariableNode |
||||
342 | * @throws SyntaxErrorException |
||||
343 | */ |
||||
344 | protected function lexVariable(): VariableNode |
||||
345 | { |
||||
346 | $start = $this->lexer->getToken(); |
||||
347 | |||||
348 | $this->expect(TokenKindEnum::DOLLAR); |
||||
349 | |||||
350 | return new VariableNode($this->lexName(), $this->createLocation($start)); |
||||
351 | } |
||||
352 | |||||
353 | /** |
||||
354 | * SelectionSet : { Selection+ } |
||||
355 | * |
||||
356 | * @return SelectionSetNode |
||||
357 | * @throws SyntaxErrorException |
||||
358 | */ |
||||
359 | protected function lexSelectionSet(): SelectionSetNode |
||||
360 | { |
||||
361 | $start = $this->lexer->getToken(); |
||||
362 | |||||
363 | return new SelectionSetNode( |
||||
364 | $this->many( |
||||
365 | TokenKindEnum::BRACE_L, |
||||
366 | [$this, 'lexSelection'], |
||||
367 | TokenKindEnum::BRACE_R |
||||
368 | ), |
||||
369 | $this->createLocation($start) |
||||
370 | ); |
||||
371 | } |
||||
372 | |||||
373 | /** |
||||
374 | * Selection : |
||||
375 | * - Field |
||||
376 | * - FragmentSpread |
||||
377 | * - InlineFragment |
||||
378 | * |
||||
379 | * @return NodeInterface|FragmentNodeInterface|FieldNode |
||||
380 | * @throws SyntaxErrorException |
||||
381 | */ |
||||
382 | protected function lexSelection(): NodeInterface |
||||
383 | { |
||||
384 | return $this->peek(TokenKindEnum::SPREAD) |
||||
385 | ? $this->lexFragment() |
||||
386 | : $this->lexField(); |
||||
387 | } |
||||
388 | |||||
389 | /** |
||||
390 | * Field : Alias? Name Arguments? Directives? SelectionSet? |
||||
391 | * |
||||
392 | * Alias : Name : |
||||
393 | * |
||||
394 | * @return FieldNode |
||||
395 | * @throws SyntaxErrorException |
||||
396 | */ |
||||
397 | protected function lexField(): FieldNode |
||||
398 | { |
||||
399 | $start = $this->lexer->getToken(); |
||||
400 | |||||
401 | $nameOrAlias = $this->lexName(); |
||||
402 | |||||
403 | if ($this->skip(TokenKindEnum::COLON)) { |
||||
404 | $alias = $nameOrAlias; |
||||
405 | $name = $this->lexName(); |
||||
406 | } else { |
||||
407 | $name = $nameOrAlias; |
||||
408 | } |
||||
409 | |||||
410 | return new FieldNode( |
||||
411 | $alias ?? null, |
||||
412 | $name, |
||||
413 | $this->lexArguments(false), |
||||
414 | $this->lexDirectives(), |
||||
415 | $this->peek(TokenKindEnum::BRACE_L) |
||||
416 | ? $this->lexSelectionSet() |
||||
417 | : null, |
||||
418 | $this->createLocation($start) |
||||
419 | ); |
||||
420 | } |
||||
421 | |||||
422 | /** |
||||
423 | * Arguments[Const] : ( Argument[?Const]+ ) |
||||
424 | * |
||||
425 | * @param bool $isConst |
||||
426 | * @return array |
||||
427 | * @throws SyntaxErrorException |
||||
428 | */ |
||||
429 | protected function lexArguments(bool $isConst = false): ?array |
||||
430 | { |
||||
431 | /** |
||||
432 | * @return ArgumentNode |
||||
433 | */ |
||||
434 | $parseFunction = function () use ($isConst): ArgumentNode { |
||||
435 | return $this->lexArgument($isConst); |
||||
436 | }; |
||||
437 | |||||
438 | return $this->peek(TokenKindEnum::PAREN_L) |
||||
439 | ? $this->many( |
||||
440 | TokenKindEnum::PAREN_L, |
||||
441 | $parseFunction, |
||||
442 | TokenKindEnum::PAREN_R |
||||
443 | ) |
||||
444 | : []; |
||||
445 | } |
||||
446 | |||||
447 | /** |
||||
448 | * Argument[Const] : Name : Value[?Const] |
||||
449 | * |
||||
450 | * @param bool $isConst |
||||
451 | * @return ArgumentNode |
||||
452 | * @throws SyntaxErrorException |
||||
453 | */ |
||||
454 | protected function lexArgument(bool $isConst = false): ArgumentNode |
||||
455 | { |
||||
456 | $start = $this->lexer->getToken(); |
||||
457 | |||||
458 | /** |
||||
459 | * @return NodeInterface|TypeNodeInterface|ValueNodeInterface |
||||
460 | */ |
||||
461 | $parseValue = function () use ($isConst): NodeInterface { |
||||
462 | $this->expect(TokenKindEnum::COLON); |
||||
463 | return $this->lexValue($isConst); |
||||
464 | }; |
||||
465 | |||||
466 | return new ArgumentNode( |
||||
467 | $this->lexName(), |
||||
468 | $parseValue(), |
||||
469 | $this->createLocation($start) |
||||
470 | ); |
||||
471 | } |
||||
472 | |||||
473 | // Implements the parsing rules in the Fragments section. |
||||
474 | |||||
475 | /** |
||||
476 | * Corresponds to both FragmentSpread and InlineFragment in the spec. |
||||
477 | * |
||||
478 | * FragmentSpread : ... FragmentName Directives? |
||||
479 | * |
||||
480 | * InlineFragment : ... TypeCondition? Directives? SelectionSet |
||||
481 | * |
||||
482 | * @return FragmentNodeInterface |
||||
483 | * @throws SyntaxErrorException |
||||
484 | */ |
||||
485 | protected function lexFragment(): FragmentNodeInterface |
||||
486 | { |
||||
487 | $start = $this->lexer->getToken(); |
||||
488 | |||||
489 | $this->expect(TokenKindEnum::SPREAD); |
||||
490 | |||||
491 | $token = $this->lexer->getToken(); |
||||
492 | |||||
493 | if (KeywordEnum::ON !== $token->getValue() && $this->peek(TokenKindEnum::NAME)) { |
||||
494 | return new FragmentSpreadNode( |
||||
495 | $this->lexFragmentName($token), |
||||
496 | $this->lexDirectives(), |
||||
497 | null, |
||||
498 | $this->createLocation($start) |
||||
499 | ); |
||||
500 | } |
||||
501 | |||||
502 | if (KeywordEnum::ON === $token->getValue()) { |
||||
503 | $this->lexer->advance(); |
||||
504 | $typeCondition = $this->lexNamedType(); |
||||
505 | } |
||||
506 | |||||
507 | return new InlineFragmentNode( |
||||
508 | $typeCondition ?? null, |
||||
509 | $this->lexDirectives(), |
||||
510 | $this->lexSelectionSet(), |
||||
511 | $this->createLocation($start) |
||||
512 | ); |
||||
513 | } |
||||
514 | |||||
515 | /** |
||||
516 | * FragmentDefinition : |
||||
517 | * - fragment FragmentName on TypeCondition Directives? SelectionSet |
||||
518 | * |
||||
519 | * TypeCondition : NamedType |
||||
520 | * |
||||
521 | * @return FragmentDefinitionNode |
||||
522 | * @throws SyntaxErrorException |
||||
523 | */ |
||||
524 | protected function lexFragmentDefinition(): FragmentDefinitionNode |
||||
525 | { |
||||
526 | $start = $this->lexer->getToken(); |
||||
527 | |||||
528 | $this->expectKeyword(KeywordEnum::FRAGMENT); |
||||
529 | |||||
530 | $parseTypeCondition = function () { |
||||
531 | $this->expectKeyword(KeywordEnum::ON); |
||||
532 | return $this->lexNamedType(); |
||||
533 | }; |
||||
534 | |||||
535 | return new FragmentDefinitionNode( |
||||
536 | $this->lexFragmentName(), |
||||
537 | $this->lexVariableDefinitions(), |
||||
538 | $parseTypeCondition(), |
||||
539 | $this->lexDirectives(), |
||||
540 | $this->lexSelectionSet(), |
||||
541 | $this->createLocation($start) |
||||
542 | ); |
||||
543 | } |
||||
544 | |||||
545 | /** |
||||
546 | * FragmentName : Name but not `on` |
||||
547 | * |
||||
548 | * @param Token|null $token |
||||
549 | * @return NameNode |
||||
550 | * @throws SyntaxErrorException |
||||
551 | */ |
||||
552 | protected function lexFragmentName(?Token $token = null): NameNode |
||||
553 | { |
||||
554 | if (null === $token) { |
||||
555 | $token = $this->lexer->getToken(); |
||||
556 | } |
||||
557 | |||||
558 | if (KeywordEnum::ON === $token->getValue()) { |
||||
559 | throw $this->unexpected(); |
||||
560 | } |
||||
561 | |||||
562 | return $this->lexName(); |
||||
563 | } |
||||
564 | |||||
565 | // Implements the parsing rules in the Values section. |
||||
566 | |||||
567 | /** |
||||
568 | * Value[Const] : |
||||
569 | * - [~Const] Variable |
||||
570 | * - IntValue |
||||
571 | * - FloatValue |
||||
572 | * - StringValue |
||||
573 | * - BooleanValue |
||||
574 | * - NullValue |
||||
575 | * - EnumValue |
||||
576 | * - ListValue[?Const] |
||||
577 | * - ObjectValue[?Const] |
||||
578 | * |
||||
579 | * BooleanValue : one of `true` `false` |
||||
580 | * |
||||
581 | * NullValue : `null` |
||||
582 | * |
||||
583 | * EnumValue : Name but not `true`, `false` or `null` |
||||
584 | * |
||||
585 | * @param bool $isConst |
||||
586 | * @return NodeInterface|ValueNodeInterface|TypeNodeInterface |
||||
587 | * @throws SyntaxErrorException |
||||
588 | */ |
||||
589 | protected function lexValue(bool $isConst = false): NodeInterface |
||||
590 | { |
||||
591 | $token = $this->lexer->getToken(); |
||||
592 | $value = $token->getValue(); |
||||
593 | |||||
594 | switch ($token->getKind()) { |
||||
595 | case TokenKindEnum::BRACKET_L: |
||||
596 | return $this->lexList($isConst); |
||||
597 | case TokenKindEnum::BRACE_L: |
||||
598 | return $this->lexObject($isConst); |
||||
599 | case TokenKindEnum::INT: |
||||
600 | $this->lexer->advance(); |
||||
601 | return new IntValueNode($value, $this->createLocation($token)); |
||||
602 | case TokenKindEnum::FLOAT: |
||||
603 | $this->lexer->advance(); |
||||
604 | return new FloatValueNode($value, $this->createLocation($token)); |
||||
605 | case TokenKindEnum::STRING: |
||||
606 | case TokenKindEnum::BLOCK_STRING: |
||||
607 | return $this->lexStringLiteral(); |
||||
608 | case TokenKindEnum::NAME: |
||||
609 | if ($value === 'true' || $value === 'false') { |
||||
610 | $this->lexer->advance(); |
||||
611 | return new BooleanValueNode($value === 'true', $this->createLocation($token)); |
||||
612 | } |
||||
613 | |||||
614 | if ($value === 'null') { |
||||
615 | $this->lexer->advance(); |
||||
616 | return new NullValueNode($this->createLocation($token)); |
||||
617 | } |
||||
618 | |||||
619 | $this->lexer->advance(); |
||||
620 | return new EnumValueNode($value, $this->createLocation($token)); |
||||
621 | case TokenKindEnum::DOLLAR: |
||||
622 | if (!$isConst) { |
||||
623 | return $this->lexVariable(); |
||||
624 | } |
||||
625 | break; |
||||
626 | } |
||||
627 | |||||
628 | throw $this->unexpected(); |
||||
629 | } |
||||
630 | |||||
631 | /** |
||||
632 | * @return StringValueNode |
||||
633 | */ |
||||
634 | protected function lexStringLiteral(): StringValueNode |
||||
635 | { |
||||
636 | $token = $this->lexer->getToken(); |
||||
637 | |||||
638 | $this->lexer->advance(); |
||||
639 | |||||
640 | return new StringValueNode( |
||||
641 | $token->getValue(), |
||||
642 | TokenKindEnum::BLOCK_STRING === $token->getKind(), |
||||
643 | $this->createLocation($token) |
||||
644 | ); |
||||
645 | } |
||||
646 | |||||
647 | /** |
||||
648 | * ListValue[Const] : |
||||
649 | * - [ ] |
||||
650 | * - [ Value[?Const]+ ] |
||||
651 | * |
||||
652 | * @param bool $isConst |
||||
653 | * @return ListValueNode |
||||
654 | * @throws SyntaxErrorException |
||||
655 | */ |
||||
656 | protected function lexList(bool $isConst): ListValueNode |
||||
657 | { |
||||
658 | $start = $this->lexer->getToken(); |
||||
659 | |||||
660 | $parseFunction = function () use ($isConst) { |
||||
661 | return $this->lexValue($isConst); |
||||
662 | }; |
||||
663 | |||||
664 | return new ListValueNode( |
||||
665 | $this->any( |
||||
666 | TokenKindEnum::BRACKET_L, |
||||
667 | $parseFunction, |
||||
668 | TokenKindEnum::BRACKET_R |
||||
669 | ), |
||||
670 | $this->createLocation($start) |
||||
671 | ); |
||||
672 | } |
||||
673 | |||||
674 | /** |
||||
675 | * ObjectValue[Const] : |
||||
676 | * - { } |
||||
677 | * - { ObjectField[?Const]+ } |
||||
678 | * |
||||
679 | * @param bool $isConst |
||||
680 | * @return ObjectValueNode |
||||
681 | * @throws SyntaxErrorException |
||||
682 | */ |
||||
683 | protected function lexObject(bool $isConst): ObjectValueNode |
||||
684 | { |
||||
685 | $start = $this->lexer->getToken(); |
||||
686 | |||||
687 | $this->expect(TokenKindEnum::BRACE_L); |
||||
688 | |||||
689 | $fields = []; |
||||
690 | |||||
691 | while (!$this->skip(TokenKindEnum::BRACE_R)) { |
||||
692 | $fields[] = $this->lexObjectField($isConst); |
||||
693 | } |
||||
694 | |||||
695 | return new ObjectValueNode($fields, $this->createLocation($start)); |
||||
696 | } |
||||
697 | |||||
698 | /** |
||||
699 | * ObjectField[Const] : Name : Value[?Const] |
||||
700 | * |
||||
701 | * @param bool $isConst |
||||
702 | * @return ObjectFieldNode |
||||
703 | * @throws SyntaxErrorException |
||||
704 | */ |
||||
705 | protected function lexObjectField(bool $isConst): ObjectFieldNode |
||||
706 | { |
||||
707 | $start = $this->lexer->getToken(); |
||||
708 | |||||
709 | /** |
||||
710 | * @param bool $isConst |
||||
711 | * @return NodeInterface|TypeNodeInterface|ValueNodeInterface |
||||
712 | */ |
||||
713 | $parseValue = function (bool $isConst): NodeInterface { |
||||
714 | $this->expect(TokenKindEnum::COLON); |
||||
715 | return $this->lexValue($isConst); |
||||
716 | }; |
||||
717 | |||||
718 | return new ObjectFieldNode( |
||||
719 | $this->lexName(), |
||||
720 | $parseValue($isConst), |
||||
721 | $this->createLocation($start) |
||||
722 | ); |
||||
723 | } |
||||
724 | |||||
725 | // Implements the parsing rules in the Directives section. |
||||
726 | |||||
727 | /** |
||||
728 | * Directives[Const] : Directive[?Const]+ |
||||
729 | * |
||||
730 | * @param bool $isConst |
||||
731 | * @return array |
||||
732 | * @throws SyntaxErrorException |
||||
733 | */ |
||||
734 | protected function lexDirectives(bool $isConst = false): array |
||||
735 | { |
||||
736 | $directives = []; |
||||
737 | |||||
738 | while ($this->peek(TokenKindEnum::AT)) { |
||||
739 | $directives[] = $this->lexDirective($isConst); |
||||
740 | } |
||||
741 | |||||
742 | return $directives; |
||||
743 | } |
||||
744 | |||||
745 | /** |
||||
746 | * Directive[Const] : @ Name Arguments[?Const]? |
||||
747 | * |
||||
748 | * @param bool $isConst |
||||
749 | * @return DirectiveNode |
||||
750 | * @throws SyntaxErrorException |
||||
751 | */ |
||||
752 | protected function lexDirective(bool $isConst = false): DirectiveNode |
||||
753 | { |
||||
754 | $start = $this->lexer->getToken(); |
||||
755 | |||||
756 | $this->expect(TokenKindEnum::AT); |
||||
757 | |||||
758 | return new DirectiveNode( |
||||
759 | $this->lexName(), |
||||
760 | $this->lexArguments($isConst), |
||||
761 | $this->createLocation($start) |
||||
762 | ); |
||||
763 | } |
||||
764 | |||||
765 | // Implements the parsing rules in the Types section. |
||||
766 | |||||
767 | /** |
||||
768 | * Type : |
||||
769 | * - NamedType |
||||
770 | * - ListType |
||||
771 | * - NonNullType |
||||
772 | * |
||||
773 | * @return TypeNodeInterface |
||||
774 | * @throws SyntaxErrorException |
||||
775 | */ |
||||
776 | protected function lexType(): TypeNodeInterface |
||||
777 | { |
||||
778 | $start = $this->lexer->getToken(); |
||||
779 | |||||
780 | if ($this->skip(TokenKindEnum::BRACKET_L)) { |
||||
781 | $type = $this->lexType(); |
||||
782 | |||||
783 | $this->expect(TokenKindEnum::BRACKET_R); |
||||
784 | |||||
785 | $type = new ListTypeNode($type, $this->createLocation($start)); |
||||
786 | } else { |
||||
787 | $type = $this->lexNamedType(); |
||||
788 | } |
||||
789 | |||||
790 | if ($this->skip(TokenKindEnum::BANG)) { |
||||
791 | return new NonNullTypeNode($type, $this->createLocation($start)); |
||||
792 | } |
||||
793 | |||||
794 | return $type; |
||||
795 | } |
||||
796 | |||||
797 | /** |
||||
798 | * NamedType : Name |
||||
799 | * |
||||
800 | * @return NamedTypeNode |
||||
801 | * @throws SyntaxErrorException |
||||
802 | */ |
||||
803 | protected function lexNamedType(): NamedTypeNode |
||||
804 | { |
||||
805 | $start = $this->lexer->getToken(); |
||||
806 | |||||
807 | return new NamedTypeNode($this->lexName(), $this->createLocation($start)); |
||||
808 | } |
||||
809 | |||||
810 | // Implements the parsing rules in the Type Definition section. |
||||
811 | |||||
812 | /** |
||||
813 | * TypeSystemDefinition : |
||||
814 | * - SchemaDefinition |
||||
815 | * - TypeDefinition |
||||
816 | * - TypeExtension |
||||
817 | * - DirectiveDefinition |
||||
818 | * |
||||
819 | * TypeDefinition : |
||||
820 | * - ScalarTypeDefinition |
||||
821 | * - ObjectTypeDefinition |
||||
822 | * - InterfaceTypeDefinition |
||||
823 | * - UnionTypeDefinition |
||||
824 | * - EnumTypeDefinition |
||||
825 | * - InputObjectTypeDefinition |
||||
826 | * |
||||
827 | * @return TypeSystemDefinitionNodeInterface |
||||
828 | * @throws SyntaxErrorException |
||||
829 | * @throws \ReflectionException |
||||
830 | */ |
||||
831 | protected function lexTypeSystemDefinition(): TypeSystemDefinitionNodeInterface |
||||
832 | { |
||||
833 | // Many definitions begin with a description and require a lookahead. |
||||
834 | $token = $this->peekDescription() |
||||
835 | ? $this->lexer->lookahead() |
||||
836 | : $this->lexer->getToken(); |
||||
837 | |||||
838 | if (TokenKindEnum::NAME === $token->getKind()) { |
||||
839 | switch ($token->getValue()) { |
||||
840 | case KeywordEnum::SCHEMA: |
||||
841 | return $this->lexSchemaDefinition(); |
||||
842 | case KeywordEnum::SCALAR: |
||||
843 | return $this->lexScalarTypeDefinition(); |
||||
844 | case KeywordEnum::TYPE: |
||||
845 | return $this->lexObjectTypeDefinition(); |
||||
846 | case KeywordEnum::INTERFACE: |
||||
847 | return $this->lexInterfaceTypeDefinition(); |
||||
848 | case KeywordEnum::UNION: |
||||
849 | return $this->lexUnionTypeDefinition(); |
||||
850 | case KeywordEnum::ENUM: |
||||
851 | return $this->lexEnumTypeDefinition(); |
||||
852 | case KeywordEnum::INPUT: |
||||
853 | return $this->lexInputObjectTypeDefinition(); |
||||
854 | case KeywordEnum::DIRECTIVE: |
||||
855 | return $this->lexDirectiveDefinition(); |
||||
856 | } |
||||
857 | } |
||||
858 | |||||
859 | throw $this->unexpected($token); |
||||
860 | } |
||||
861 | |||||
862 | /** |
||||
863 | * @return bool |
||||
864 | */ |
||||
865 | protected function peekDescription(): bool |
||||
866 | { |
||||
867 | return $this->peek(TokenKindEnum::STRING) || $this->peek(TokenKindEnum::BLOCK_STRING); |
||||
868 | } |
||||
869 | |||||
870 | /** |
||||
871 | * Description : StringValue |
||||
872 | * |
||||
873 | * @return StringValueNode|null |
||||
874 | */ |
||||
875 | public function lexDescription(): ?StringValueNode |
||||
876 | { |
||||
877 | return $this->peekDescription() |
||||
878 | ? $this->lexStringLiteral() |
||||
879 | : null; |
||||
880 | } |
||||
881 | |||||
882 | /** |
||||
883 | * SchemaDefinition : schema Directives[Const]? { OperationTypeDefinition+ } |
||||
884 | * |
||||
885 | * @return SchemaDefinitionNode |
||||
886 | * @throws SyntaxErrorException |
||||
887 | */ |
||||
888 | protected function lexSchemaDefinition(): SchemaDefinitionNode |
||||
889 | { |
||||
890 | $start = $this->lexer->getToken(); |
||||
891 | |||||
892 | $this->expectKeyword(KeywordEnum::SCHEMA); |
||||
893 | |||||
894 | return new SchemaDefinitionNode( |
||||
895 | $this->lexDirectives(), |
||||
896 | $this->many( |
||||
897 | TokenKindEnum::BRACE_L, |
||||
898 | [$this, 'lexOperationTypeDefinition'], |
||||
899 | TokenKindEnum::BRACE_R |
||||
900 | ), |
||||
901 | $this->createLocation($start) |
||||
902 | ); |
||||
903 | } |
||||
904 | |||||
905 | /** |
||||
906 | * OperationTypeDefinition : OperationType : NamedType |
||||
907 | * |
||||
908 | * @return OperationTypeDefinitionNode |
||||
909 | * @throws SyntaxErrorException |
||||
910 | */ |
||||
911 | protected function lexOperationTypeDefinition(): OperationTypeDefinitionNode |
||||
912 | { |
||||
913 | $start = $this->lexer->getToken(); |
||||
914 | |||||
915 | $operation = $this->lexOperationType(); |
||||
916 | |||||
917 | $this->expect(TokenKindEnum::COLON); |
||||
918 | |||||
919 | return new OperationTypeDefinitionNode( |
||||
920 | $operation, |
||||
0 ignored issues
–
show
It seems like
$operation can also be of type null ; however, parameter $operation of Digia\GraphQL\Language\N...tionNode::__construct() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
921 | $this->lexNamedType(), |
||||
922 | $this->createLocation($start) |
||||
923 | ); |
||||
924 | } |
||||
925 | |||||
926 | /** |
||||
927 | * ScalarTypeDefinition : Description? scalar Name Directives[Const]? |
||||
928 | * |
||||
929 | * @return ScalarTypeDefinitionNode |
||||
930 | * @throws SyntaxErrorException |
||||
931 | */ |
||||
932 | protected function lexScalarTypeDefinition(): ScalarTypeDefinitionNode |
||||
933 | { |
||||
934 | $start = $this->lexer->getToken(); |
||||
935 | |||||
936 | $description = $this->lexDescription(); |
||||
937 | |||||
938 | $this->expectKeyword(KeywordEnum::SCALAR); |
||||
939 | |||||
940 | return new ScalarTypeDefinitionNode( |
||||
941 | $description, |
||||
942 | $this->lexName(), |
||||
943 | $this->lexDirectives(), |
||||
944 | $this->createLocation($start) |
||||
945 | ); |
||||
946 | } |
||||
947 | |||||
948 | /** |
||||
949 | * ObjectTypeDefinition : |
||||
950 | * Description? |
||||
951 | * type Name ImplementsInterfaces? Directives[Const]? FieldsDefinition? |
||||
952 | * |
||||
953 | * @return ObjectTypeDefinitionNode |
||||
954 | * @throws SyntaxErrorException |
||||
955 | */ |
||||
956 | protected function lexObjectTypeDefinition(): ObjectTypeDefinitionNode |
||||
957 | { |
||||
958 | $start = $this->lexer->getToken(); |
||||
959 | |||||
960 | $description = $this->lexDescription(); |
||||
961 | |||||
962 | $this->expectKeyword(KeywordEnum::TYPE); |
||||
963 | |||||
964 | return new ObjectTypeDefinitionNode( |
||||
965 | $description, |
||||
966 | $this->lexName(), |
||||
967 | $this->lexImplementsInterfaces(), |
||||
968 | $this->lexDirectives(), |
||||
969 | $this->lexFieldsDefinition(), |
||||
970 | $this->createLocation($start) |
||||
971 | ); |
||||
972 | } |
||||
973 | |||||
974 | /** |
||||
975 | * ImplementsInterfaces : |
||||
976 | * - implements `&`? NamedType |
||||
977 | * - ImplementsInterfaces & NamedType |
||||
978 | * |
||||
979 | * @return array |
||||
980 | * @throws SyntaxErrorException |
||||
981 | */ |
||||
982 | protected function lexImplementsInterfaces(): array |
||||
983 | { |
||||
984 | $types = []; |
||||
985 | |||||
986 | $token = $this->lexer->getToken(); |
||||
987 | |||||
988 | if ('implements' === $token->getValue()) { |
||||
989 | $this->lexer->advance(); |
||||
990 | |||||
991 | // Optional leading ampersand |
||||
992 | $this->skip(TokenKindEnum::AMP); |
||||
993 | |||||
994 | do { |
||||
995 | $types[] = $this->lexNamedType(); |
||||
996 | } while ($this->skip(TokenKindEnum::AMP)); |
||||
997 | } |
||||
998 | |||||
999 | return $types; |
||||
1000 | } |
||||
1001 | |||||
1002 | /** |
||||
1003 | * FieldsDefinition : { FieldDefinition+ } |
||||
1004 | * |
||||
1005 | * @return array |
||||
1006 | * @throws SyntaxErrorException |
||||
1007 | */ |
||||
1008 | protected function lexFieldsDefinition(): array |
||||
1009 | { |
||||
1010 | return $this->peek(TokenKindEnum::BRACE_L) |
||||
1011 | ? $this->many( |
||||
1012 | TokenKindEnum::BRACE_L, |
||||
1013 | [$this, 'lexFieldDefinition'], |
||||
1014 | TokenKindEnum::BRACE_R |
||||
1015 | ) |
||||
1016 | : []; |
||||
1017 | } |
||||
1018 | |||||
1019 | /** |
||||
1020 | * FieldDefinition : |
||||
1021 | * - Description? Name ArgumentsDefinition? : Type Directives[Const]? |
||||
1022 | * |
||||
1023 | * @return FieldDefinitionNode |
||||
1024 | * @throws SyntaxErrorException |
||||
1025 | */ |
||||
1026 | protected function lexFieldDefinition(): FieldDefinitionNode |
||||
1027 | { |
||||
1028 | $start = $this->lexer->getToken(); |
||||
1029 | |||||
1030 | $description = $this->lexDescription(); |
||||
1031 | $name = $this->lexName(); |
||||
1032 | $arguments = $this->lexArgumentsDefinition(); |
||||
1033 | |||||
1034 | $this->expect(TokenKindEnum::COLON); |
||||
1035 | |||||
1036 | return new FieldDefinitionNode( |
||||
1037 | $description, |
||||
1038 | $name, |
||||
1039 | $arguments, |
||||
1040 | $this->lexType(), |
||||
1041 | $this->lexDirectives(), |
||||
1042 | $this->createLocation($start) |
||||
1043 | ); |
||||
1044 | } |
||||
1045 | |||||
1046 | /** |
||||
1047 | * ArgumentsDefinition : ( InputValueDefinition+ ) |
||||
1048 | * |
||||
1049 | * @return InputValueDefinitionNode[] |
||||
1050 | * @throws SyntaxErrorException |
||||
1051 | */ |
||||
1052 | protected function lexArgumentsDefinition(): array |
||||
1053 | { |
||||
1054 | $parseFunction = function (): InputValueDefinitionNode { |
||||
1055 | return $this->lexInputValueDefinition(); |
||||
1056 | }; |
||||
1057 | |||||
1058 | return $this->peek(TokenKindEnum::PAREN_L) |
||||
1059 | ? $this->many( |
||||
1060 | TokenKindEnum::PAREN_L, |
||||
1061 | $parseFunction, |
||||
1062 | TokenKindEnum::PAREN_R |
||||
1063 | ) |
||||
1064 | : []; |
||||
1065 | } |
||||
1066 | |||||
1067 | /** |
||||
1068 | * InputValueDefinition : |
||||
1069 | * - Description? Name : Type DefaultValue? Directives[Const]? |
||||
1070 | * |
||||
1071 | * @return InputValueDefinitionNode |
||||
1072 | * @throws SyntaxErrorException |
||||
1073 | */ |
||||
1074 | protected function lexInputValueDefinition(): InputValueDefinitionNode |
||||
1075 | { |
||||
1076 | $start = $this->lexer->getToken(); |
||||
1077 | |||||
1078 | $description = $this->lexDescription(); |
||||
1079 | $name = $this->lexName(); |
||||
1080 | |||||
1081 | $this->expect(TokenKindEnum::COLON); |
||||
1082 | |||||
1083 | return new InputValueDefinitionNode( |
||||
1084 | $description, |
||||
1085 | $name, |
||||
1086 | $this->lexType(), |
||||
1087 | $this->skip(TokenKindEnum::EQUALS) |
||||
1088 | ? $this->lexValue(true) |
||||
1089 | : null, |
||||
1090 | $this->lexDirectives(true), |
||||
1091 | $this->createLocation($start) |
||||
1092 | ); |
||||
1093 | } |
||||
1094 | |||||
1095 | /** |
||||
1096 | * InterfaceTypeDefinition : |
||||
1097 | * - Description? interface Name Directives[Const]? FieldsDefinition? |
||||
1098 | * |
||||
1099 | * @return InterfaceTypeDefinitionNode |
||||
1100 | * @throws SyntaxErrorException |
||||
1101 | */ |
||||
1102 | protected function lexInterfaceTypeDefinition(): InterfaceTypeDefinitionNode |
||||
1103 | { |
||||
1104 | $start = $this->lexer->getToken(); |
||||
1105 | |||||
1106 | $description = $this->lexDescription(); |
||||
1107 | |||||
1108 | $this->expectKeyword(KeywordEnum::INTERFACE); |
||||
1109 | |||||
1110 | return new InterfaceTypeDefinitionNode( |
||||
1111 | $description, |
||||
1112 | $this->lexName(), |
||||
1113 | $this->lexDirectives(), |
||||
1114 | $this->lexFieldsDefinition(), |
||||
1115 | $this->createLocation($start) |
||||
1116 | ); |
||||
1117 | } |
||||
1118 | |||||
1119 | /** |
||||
1120 | * UnionTypeDefinition : |
||||
1121 | * - Description? union Name Directives[Const]? UnionMemberTypes? |
||||
1122 | * |
||||
1123 | * @return UnionTypeDefinitionNode |
||||
1124 | * @throws SyntaxErrorException |
||||
1125 | */ |
||||
1126 | protected function lexUnionTypeDefinition(): UnionTypeDefinitionNode |
||||
1127 | { |
||||
1128 | $start = $this->lexer->getToken(); |
||||
1129 | |||||
1130 | $description = $this->lexDescription(); |
||||
1131 | |||||
1132 | $this->expectKeyword(KeywordEnum::UNION); |
||||
1133 | |||||
1134 | return new UnionTypeDefinitionNode( |
||||
1135 | $description, |
||||
1136 | $this->lexName(), |
||||
1137 | $this->lexDirectives(), |
||||
1138 | $this->lexUnionMemberTypes(), |
||||
1139 | $this->createLocation($start) |
||||
1140 | ); |
||||
1141 | } |
||||
1142 | |||||
1143 | /** |
||||
1144 | * UnionMemberTypes : |
||||
1145 | * - = `|`? NamedType |
||||
1146 | * - UnionMemberTypes | NamedType |
||||
1147 | * |
||||
1148 | * @return array |
||||
1149 | * @throws SyntaxErrorException |
||||
1150 | */ |
||||
1151 | protected function lexUnionMemberTypes(): array |
||||
1152 | { |
||||
1153 | $types = []; |
||||
1154 | |||||
1155 | if ($this->skip(TokenKindEnum::EQUALS)) { |
||||
1156 | // Optional leading pipe |
||||
1157 | $this->skip(TokenKindEnum::PIPE); |
||||
1158 | |||||
1159 | do { |
||||
1160 | $types[] = $this->lexNamedType(); |
||||
1161 | } while ($this->skip(TokenKindEnum::PIPE)); |
||||
1162 | } |
||||
1163 | |||||
1164 | return $types; |
||||
1165 | } |
||||
1166 | |||||
1167 | /** |
||||
1168 | * EnumTypeDefinition : |
||||
1169 | * - Description? enum Name Directives[Const]? EnumValuesDefinition? |
||||
1170 | * |
||||
1171 | * @return EnumTypeDefinitionNode |
||||
1172 | * @throws SyntaxErrorException |
||||
1173 | */ |
||||
1174 | protected function lexEnumTypeDefinition(): EnumTypeDefinitionNode |
||||
1175 | { |
||||
1176 | $start = $this->lexer->getToken(); |
||||
1177 | |||||
1178 | $description = $this->lexDescription(); |
||||
1179 | |||||
1180 | $this->expectKeyword(KeywordEnum::ENUM); |
||||
1181 | |||||
1182 | return new EnumTypeDefinitionNode( |
||||
1183 | $description, |
||||
1184 | $this->lexName(), |
||||
1185 | $this->lexDirectives(), |
||||
1186 | $this->lexEnumValuesDefinition(), |
||||
1187 | $this->createLocation($start) |
||||
1188 | ); |
||||
1189 | } |
||||
1190 | |||||
1191 | /** |
||||
1192 | * EnumValuesDefinition : { EnumValueDefinition+ } |
||||
1193 | * |
||||
1194 | * @return array |
||||
1195 | * @throws SyntaxErrorException |
||||
1196 | */ |
||||
1197 | protected function lexEnumValuesDefinition(): array |
||||
1198 | { |
||||
1199 | return $this->peek(TokenKindEnum::BRACE_L) |
||||
1200 | ? $this->many( |
||||
1201 | TokenKindEnum::BRACE_L, |
||||
1202 | [$this, 'lexEnumValueDefinition'], |
||||
1203 | TokenKindEnum::BRACE_R |
||||
1204 | ) |
||||
1205 | : []; |
||||
1206 | } |
||||
1207 | |||||
1208 | /** |
||||
1209 | * EnumValueDefinition : Description? EnumValue Directives[Const]? |
||||
1210 | * |
||||
1211 | * EnumValue : Name |
||||
1212 | * |
||||
1213 | * @return EnumValueDefinitionNode |
||||
1214 | * @throws SyntaxErrorException |
||||
1215 | */ |
||||
1216 | protected function lexEnumValueDefinition(): EnumValueDefinitionNode |
||||
1217 | { |
||||
1218 | $start = $this->lexer->getToken(); |
||||
1219 | |||||
1220 | return new EnumValueDefinitionNode( |
||||
1221 | $this->lexDescription(), |
||||
1222 | $this->lexName(), |
||||
1223 | $this->lexDirectives(), |
||||
1224 | $this->createLocation($start) |
||||
1225 | ); |
||||
1226 | } |
||||
1227 | |||||
1228 | /** |
||||
1229 | * InputObjectTypeDefinition : |
||||
1230 | * - Description? input Name Directives[Const]? InputFieldsDefinition? |
||||
1231 | * |
||||
1232 | * @return InputObjectTypeDefinitionNode |
||||
1233 | * @throws SyntaxErrorException |
||||
1234 | */ |
||||
1235 | protected function lexInputObjectTypeDefinition(): InputObjectTypeDefinitionNode |
||||
1236 | { |
||||
1237 | $start = $this->lexer->getToken(); |
||||
1238 | |||||
1239 | $description = $this->lexDescription(); |
||||
1240 | |||||
1241 | $this->expectKeyword(KeywordEnum::INPUT); |
||||
1242 | |||||
1243 | return new InputObjectTypeDefinitionNode( |
||||
1244 | $description, |
||||
1245 | $this->lexName(), |
||||
1246 | $this->lexDirectives(true), |
||||
1247 | $this->lexInputFieldsDefinition(), |
||||
1248 | $this->createLocation($start) |
||||
1249 | ); |
||||
1250 | } |
||||
1251 | |||||
1252 | /** |
||||
1253 | * InputFieldsDefinition : { InputValueDefinition+ } |
||||
1254 | * |
||||
1255 | * @return array |
||||
1256 | * @throws SyntaxErrorException |
||||
1257 | */ |
||||
1258 | protected function lexInputFieldsDefinition(): array |
||||
1259 | { |
||||
1260 | $parseFunction = function (): InputValueDefinitionNode { |
||||
1261 | return $this->lexInputValueDefinition(); |
||||
1262 | }; |
||||
1263 | |||||
1264 | return $this->peek(TokenKindEnum::BRACE_L) |
||||
1265 | ? $this->many( |
||||
1266 | TokenKindEnum::BRACE_L, |
||||
1267 | $parseFunction, |
||||
1268 | TokenKindEnum::BRACE_R |
||||
1269 | ) |
||||
1270 | : []; |
||||
1271 | } |
||||
1272 | |||||
1273 | /** |
||||
1274 | * TypeExtension : |
||||
1275 | * - ScalarTypeExtension |
||||
1276 | * - ObjectTypeExtension |
||||
1277 | * - InterfaceTypeExtension |
||||
1278 | * - UnionTypeExtension |
||||
1279 | * - EnumTypeExtension |
||||
1280 | * - InputObjectTypeDefinition |
||||
1281 | * |
||||
1282 | * @return TypeSystemExtensionNodeInterface |
||||
1283 | * @throws SyntaxErrorException |
||||
1284 | */ |
||||
1285 | protected function lexTypeSystemExtension(): TypeSystemExtensionNodeInterface |
||||
1286 | { |
||||
1287 | $token = $this->lexer->lookahead(); |
||||
1288 | |||||
1289 | if (TokenKindEnum::NAME === $token->getKind()) { |
||||
1290 | switch ($token->getValue()) { |
||||
1291 | case KeywordEnum::SCHEMA: |
||||
1292 | return $this->lexSchemaExtension(); |
||||
1293 | case KeywordEnum::SCALAR: |
||||
1294 | return $this->lexScalarTypeExtension(false); |
||||
1295 | case KeywordEnum::TYPE: |
||||
1296 | return $this->lexObjectTypeExtension(); |
||||
1297 | case KeywordEnum::INTERFACE: |
||||
1298 | return $this->lexInterfaceTypeExtension(); |
||||
1299 | case KeywordEnum::UNION: |
||||
1300 | return $this->lexUnionTypeExtension(); |
||||
1301 | case KeywordEnum::ENUM: |
||||
1302 | return $this->lexEnumTypeExtension(); |
||||
1303 | case KeywordEnum::INPUT: |
||||
1304 | return $this->lexInputObjectTypeExtension(); |
||||
1305 | } |
||||
1306 | } |
||||
1307 | |||||
1308 | throw $this->unexpected($token); |
||||
1309 | } |
||||
1310 | |||||
1311 | /** |
||||
1312 | * SchemaExtension : |
||||
1313 | * - extend schema Directives[Const]? { OperationTypeDefinition+ } |
||||
1314 | * - extend schema Directives[Const] |
||||
1315 | * |
||||
1316 | * @return SchemaExtensionNode |
||||
1317 | * @throws SyntaxErrorException |
||||
1318 | */ |
||||
1319 | protected function lexSchemaExtension(): SchemaExtensionNode |
||||
1320 | { |
||||
1321 | $start = $this->lexer->getToken(); |
||||
1322 | |||||
1323 | $this->expectKeyword(KeywordEnum::EXTEND); |
||||
1324 | $this->expectKeyword(KeywordEnum::SCHEMA); |
||||
1325 | |||||
1326 | $directives = $this->lexDirectives(true); |
||||
1327 | |||||
1328 | $parseFunction = function (): OperationTypeDefinitionNode { |
||||
1329 | return $this->lexOperationTypeDefinition(); |
||||
1330 | }; |
||||
1331 | |||||
1332 | $operationTypes = $this->peek(TokenKindEnum::BRACE_L) |
||||
1333 | ? $this->many( |
||||
1334 | TokenKindEnum::BRACE_L, |
||||
1335 | $parseFunction, |
||||
1336 | TokenKindEnum::BRACE_R |
||||
1337 | ) |
||||
1338 | : []; |
||||
1339 | |||||
1340 | if (empty($directives) && empty($operationTypes)) { |
||||
1341 | $this->unexpected(); |
||||
1342 | } |
||||
1343 | |||||
1344 | return new SchemaExtensionNode($directives, $operationTypes, $this->createLocation($start)); |
||||
1345 | } |
||||
1346 | |||||
1347 | /** |
||||
1348 | * ScalarTypeExtension : |
||||
1349 | * - extend scalar Name Directives[Const] |
||||
1350 | * |
||||
1351 | * @param bool $isConst |
||||
1352 | * @return ScalarTypeExtensionNode |
||||
1353 | * @throws SyntaxErrorException |
||||
1354 | */ |
||||
1355 | protected function lexScalarTypeExtension(bool $isConst = false): ScalarTypeExtensionNode |
||||
1356 | { |
||||
1357 | $start = $this->lexer->getToken(); |
||||
1358 | |||||
1359 | $this->expectKeyword(KeywordEnum::EXTEND); |
||||
1360 | $this->expectKeyword(KeywordEnum::SCALAR); |
||||
1361 | |||||
1362 | $name = $this->lexName(); |
||||
1363 | $directives = $this->lexDirectives($isConst); |
||||
1364 | |||||
1365 | if (empty($directives)) { |
||||
1366 | throw $this->unexpected(); |
||||
1367 | } |
||||
1368 | |||||
1369 | return new ScalarTypeExtensionNode($name, $directives, $this->createLocation($start)); |
||||
1370 | } |
||||
1371 | |||||
1372 | /** |
||||
1373 | * ObjectTypeExtension : |
||||
1374 | * - extend type Name ImplementsInterfaces? Directives[Const]? FieldsDefinition |
||||
1375 | * - extend type Name ImplementsInterfaces? Directives[Const] |
||||
1376 | * - extend type Name ImplementsInterfaces |
||||
1377 | * |
||||
1378 | * @return ObjectTypeExtensionNode |
||||
1379 | * @throws SyntaxErrorException |
||||
1380 | */ |
||||
1381 | protected function lexObjectTypeExtension(): ObjectTypeExtensionNode |
||||
1382 | { |
||||
1383 | $start = $this->lexer->getToken(); |
||||
1384 | |||||
1385 | $this->expectKeyword(KeywordEnum::EXTEND); |
||||
1386 | $this->expectKeyword(KeywordEnum::TYPE); |
||||
1387 | |||||
1388 | $name = $this->lexName(); |
||||
1389 | $interfaces = $this->lexImplementsInterfaces(); |
||||
1390 | $directives = $this->lexDirectives(); |
||||
1391 | $fields = $this->lexFieldsDefinition(); |
||||
1392 | |||||
1393 | if (empty($interfaces) && empty($directives) && empty($fields)) { |
||||
1394 | throw $this->unexpected(); |
||||
1395 | } |
||||
1396 | |||||
1397 | return new ObjectTypeExtensionNode( |
||||
1398 | $name, |
||||
1399 | $interfaces, |
||||
1400 | $directives, |
||||
1401 | $fields, |
||||
1402 | $this->createLocation($start) |
||||
1403 | ); |
||||
1404 | } |
||||
1405 | |||||
1406 | /** |
||||
1407 | * InterfaceTypeExtension : |
||||
1408 | * - extend interface Name Directives[Const]? FieldsDefinition |
||||
1409 | * - extend interface Name Directives[Const] |
||||
1410 | * |
||||
1411 | * @return InterfaceTypeExtensionNode |
||||
1412 | * @throws SyntaxErrorException |
||||
1413 | */ |
||||
1414 | protected function lexInterfaceTypeExtension(): InterfaceTypeExtensionNode |
||||
1415 | { |
||||
1416 | $start = $this->lexer->getToken(); |
||||
1417 | |||||
1418 | $this->expectKeyword(KeywordEnum::EXTEND); |
||||
1419 | $this->expectKeyword(KeywordEnum::INTERFACE); |
||||
1420 | |||||
1421 | $name = $this->lexName(); |
||||
1422 | $directives = $this->lexDirectives(); |
||||
1423 | $fields = $this->lexFieldsDefinition(); |
||||
1424 | |||||
1425 | if (empty($directives) && empty($fields)) { |
||||
1426 | throw $this->unexpected(); |
||||
1427 | } |
||||
1428 | |||||
1429 | return new InterfaceTypeExtensionNode($name, $directives, $fields, $this->createLocation($start)); |
||||
1430 | } |
||||
1431 | |||||
1432 | /** |
||||
1433 | * UnionTypeExtension : |
||||
1434 | * - extend union Name Directives[Const]? UnionMemberTypes |
||||
1435 | * - extend union Name Directives[Const] |
||||
1436 | * |
||||
1437 | * @return UnionTypeExtensionNode |
||||
1438 | * @throws SyntaxErrorException |
||||
1439 | */ |
||||
1440 | protected function lexUnionTypeExtension(): UnionTypeExtensionNode |
||||
1441 | { |
||||
1442 | $start = $this->lexer->getToken(); |
||||
1443 | |||||
1444 | $this->expectKeyword(KeywordEnum::EXTEND); |
||||
1445 | $this->expectKeyword(KeywordEnum::UNION); |
||||
1446 | |||||
1447 | $name = $this->lexName(); |
||||
1448 | $directives = $this->lexDirectives(); |
||||
1449 | $types = $this->lexUnionMemberTypes(); |
||||
1450 | |||||
1451 | if (empty($directives) && empty($types)) { |
||||
1452 | throw $this->unexpected(); |
||||
1453 | } |
||||
1454 | |||||
1455 | return new UnionTypeExtensionNode($name, $directives, $types, $this->createLocation($start)); |
||||
1456 | } |
||||
1457 | |||||
1458 | /** |
||||
1459 | * EnumTypeExtension : |
||||
1460 | * - extend enum Name Directives[Const]? EnumValuesDefinition |
||||
1461 | * - extend enum Name Directives[Const] |
||||
1462 | * |
||||
1463 | * @return EnumTypeExtensionNode |
||||
1464 | * @throws SyntaxErrorException |
||||
1465 | */ |
||||
1466 | protected function lexEnumTypeExtension(): EnumTypeExtensionNode |
||||
1467 | { |
||||
1468 | $start = $this->lexer->getToken(); |
||||
1469 | |||||
1470 | $this->expectKeyword(KeywordEnum::EXTEND); |
||||
1471 | $this->expectKeyword(KeywordEnum::ENUM); |
||||
1472 | |||||
1473 | $name = $this->lexName(); |
||||
1474 | $directives = $this->lexDirectives(); |
||||
1475 | $values = $this->lexEnumValuesDefinition(); |
||||
1476 | |||||
1477 | if (empty($directives) && empty($values)) { |
||||
1478 | throw $this->unexpected(); |
||||
1479 | } |
||||
1480 | |||||
1481 | return new EnumTypeExtensionNode($name, $directives, $values, $this->createLocation($start)); |
||||
1482 | } |
||||
1483 | |||||
1484 | /** |
||||
1485 | * InputObjectTypeExtension : |
||||
1486 | * - extend input Name Directives[Const]? InputFieldsDefinition |
||||
1487 | * - extend input Name Directives[Const] |
||||
1488 | * |
||||
1489 | * @return InputObjectTypeExtensionNode |
||||
1490 | * @throws SyntaxErrorException |
||||
1491 | */ |
||||
1492 | protected function lexInputObjectTypeExtension(): InputObjectTypeExtensionNode |
||||
1493 | { |
||||
1494 | $start = $this->lexer->getToken(); |
||||
1495 | |||||
1496 | $this->expectKeyword(KeywordEnum::EXTEND); |
||||
1497 | $this->expectKeyword(KeywordEnum::INPUT); |
||||
1498 | |||||
1499 | $name = $this->lexName(); |
||||
1500 | $directives = $this->lexDirectives(true); |
||||
1501 | $fields = $this->lexInputFieldsDefinition(); |
||||
1502 | |||||
1503 | if (empty($directives) && empty($fields)) { |
||||
1504 | throw $this->unexpected(); |
||||
1505 | } |
||||
1506 | |||||
1507 | return new InputObjectTypeExtensionNode($name, $directives, $fields, $this->createLocation($start)); |
||||
1508 | } |
||||
1509 | |||||
1510 | /** |
||||
1511 | * DirectiveDefinition : |
||||
1512 | * - Description? directive @ Name ArgumentsDefinition? on DirectiveLocations |
||||
1513 | * |
||||
1514 | * @return DirectiveDefinitionNode |
||||
1515 | * @throws SyntaxErrorException |
||||
1516 | * @throws \ReflectionException |
||||
1517 | */ |
||||
1518 | protected function lexDirectiveDefinition(): DirectiveDefinitionNode |
||||
1519 | { |
||||
1520 | $start = $this->lexer->getToken(); |
||||
1521 | |||||
1522 | $description = $this->lexDescription(); |
||||
1523 | |||||
1524 | $this->expectKeyword(KeywordEnum::DIRECTIVE); |
||||
1525 | $this->expect(TokenKindEnum::AT); |
||||
1526 | |||||
1527 | $name = $this->lexName(); |
||||
1528 | $arguments = $this->lexArgumentsDefinition(); |
||||
1529 | |||||
1530 | $this->expectKeyword(KeywordEnum::ON); |
||||
1531 | |||||
1532 | $locations = $this->lexDirectiveLocations(); |
||||
1533 | |||||
1534 | return new DirectiveDefinitionNode( |
||||
1535 | $description, |
||||
1536 | $name, |
||||
1537 | $arguments, |
||||
1538 | $locations, |
||||
1539 | $this->createLocation($start) |
||||
1540 | ); |
||||
1541 | } |
||||
1542 | |||||
1543 | /** |
||||
1544 | * DirectiveLocations : |
||||
1545 | * - `|`? DirectiveLocation |
||||
1546 | * - DirectiveLocations | DirectiveLocation |
||||
1547 | * |
||||
1548 | * @return array |
||||
1549 | * @throws SyntaxErrorException |
||||
1550 | * @throws \ReflectionException |
||||
1551 | */ |
||||
1552 | protected function lexDirectiveLocations(): array |
||||
1553 | { |
||||
1554 | $this->skip(TokenKindEnum::PIPE); |
||||
1555 | |||||
1556 | $locations = []; |
||||
1557 | |||||
1558 | do { |
||||
1559 | $locations[] = $this->lexDirectiveLocation(); |
||||
1560 | } while ($this->skip(TokenKindEnum::PIPE)); |
||||
1561 | |||||
1562 | return $locations; |
||||
1563 | } |
||||
1564 | |||||
1565 | /** |
||||
1566 | * DirectiveLocation : |
||||
1567 | * - ExecutableDirectiveLocation |
||||
1568 | * - TypeSystemDirectiveLocation |
||||
1569 | * |
||||
1570 | * ExecutableDirectiveLocation : one of |
||||
1571 | * `QUERY` |
||||
1572 | * `MUTATION` |
||||
1573 | * `SUBSCRIPTION` |
||||
1574 | * `FIELD` |
||||
1575 | * `FRAGMENT_DEFINITION` |
||||
1576 | * `FRAGMENT_SPREAD` |
||||
1577 | * `INLINE_FRAGMENT` |
||||
1578 | * |
||||
1579 | * TypeSystemDirectiveLocation : one of |
||||
1580 | * `SCHEMA` |
||||
1581 | * `SCALAR` |
||||
1582 | * `OBJECT` |
||||
1583 | * `FIELD_DEFINITION` |
||||
1584 | * `ARGUMENT_DEFINITION` |
||||
1585 | * `INTERFACE` |
||||
1586 | * `UNION` |
||||
1587 | * `ENUM` |
||||
1588 | * `ENUM_VALUE` |
||||
1589 | * `INPUT_OBJECT` |
||||
1590 | * `INPUT_FIELD_DEFINITION` |
||||
1591 | * |
||||
1592 | * @return NameNode |
||||
1593 | * @throws SyntaxErrorException |
||||
1594 | * @throws \ReflectionException |
||||
1595 | */ |
||||
1596 | protected function lexDirectiveLocation(): NameNode |
||||
1597 | { |
||||
1598 | $start = $this->lexer->getToken(); |
||||
1599 | |||||
1600 | $name = $this->lexName(); |
||||
1601 | |||||
1602 | if (arraySome(DirectiveLocationEnum::values(), function ($value) use ($name) { |
||||
1603 | return $name->getValue() === $value; |
||||
1604 | })) { |
||||
1605 | return $name; |
||||
1606 | } |
||||
1607 | |||||
1608 | throw $this->unexpected($start); |
||||
1609 | } |
||||
1610 | |||||
1611 | /** |
||||
1612 | * Returns a location object, used to identify the place in |
||||
1613 | * the source that created a given parsed object. |
||||
1614 | * |
||||
1615 | * @param Token $start |
||||
1616 | * @return Location|null |
||||
1617 | */ |
||||
1618 | protected function createLocation(Token $start): ?Location |
||||
1619 | { |
||||
1620 | return !$this->lexer->getOption('noLocation', false) |
||||
1621 | ? new Location( |
||||
1622 | $start->getStart(), |
||||
1623 | $this->lexer->getLastToken()->getEnd(), |
||||
1624 | $this->lexer->getSource() |
||||
1625 | ) |
||||
1626 | : null; |
||||
1627 | } |
||||
1628 | |||||
1629 | /** |
||||
1630 | * @param Source $source |
||||
1631 | * @param array $options |
||||
1632 | * @return LexerInterface |
||||
1633 | */ |
||||
1634 | protected function createLexer($source, array $options): LexerInterface |
||||
1635 | { |
||||
1636 | return new Lexer($source, $options); |
||||
1637 | } |
||||
1638 | |||||
1639 | /** |
||||
1640 | * Determines if the next token is of a given kind. |
||||
1641 | * |
||||
1642 | * @param string $kind |
||||
1643 | * @return bool |
||||
1644 | */ |
||||
1645 | protected function peek(string $kind): bool |
||||
1646 | { |
||||
1647 | return $kind === $this->lexer->getToken()->getKind(); |
||||
1648 | } |
||||
1649 | |||||
1650 | /** |
||||
1651 | * If the next token is of the given kind, return true after advancing |
||||
1652 | * the lexer. Otherwise, do not change the parser state and return false. |
||||
1653 | * |
||||
1654 | * @param string $kind |
||||
1655 | * @return bool |
||||
1656 | */ |
||||
1657 | protected function skip(string $kind): bool |
||||
1658 | { |
||||
1659 | if ($match = $this->peek($kind)) { |
||||
1660 | $this->lexer->advance(); |
||||
1661 | } |
||||
1662 | |||||
1663 | return $match; |
||||
1664 | } |
||||
1665 | |||||
1666 | /** |
||||
1667 | * If the next token is of the given kind, return that token after advancing |
||||
1668 | * the lexer. Otherwise, do not change the parser state and throw an error. |
||||
1669 | * |
||||
1670 | * @param string $kind |
||||
1671 | * @return Token |
||||
1672 | * @throws SyntaxErrorException |
||||
1673 | */ |
||||
1674 | protected function expect(string $kind): Token |
||||
1675 | { |
||||
1676 | $token = $this->lexer->getToken(); |
||||
1677 | |||||
1678 | if ($kind === $token->getKind()) { |
||||
1679 | $this->lexer->advance(); |
||||
1680 | return $token; |
||||
1681 | } |
||||
1682 | |||||
1683 | throw $this->lexer->createSyntaxErrorException(\sprintf('Expected %s, found %s.', $kind, $token)); |
||||
1684 | } |
||||
1685 | |||||
1686 | /** |
||||
1687 | * @param string $value |
||||
1688 | * @return Token |
||||
1689 | * @throws SyntaxErrorException |
||||
1690 | */ |
||||
1691 | protected function expectKeyword(string $value): Token |
||||
1692 | { |
||||
1693 | $token = $this->lexer->getToken(); |
||||
1694 | |||||
1695 | if (TokenKindEnum::NAME === $token->getKind() && $value === $token->getValue()) { |
||||
1696 | $this->lexer->advance(); |
||||
1697 | return $token; |
||||
1698 | } |
||||
1699 | |||||
1700 | throw $this->lexer->createSyntaxErrorException(\sprintf('Expected %s, found %s', $value, $token)); |
||||
1701 | } |
||||
1702 | |||||
1703 | /** |
||||
1704 | * Helper function for creating an error when an unexpected lexed token |
||||
1705 | * is encountered. |
||||
1706 | * |
||||
1707 | * @param Token|null $atToken |
||||
1708 | * @return SyntaxErrorException |
||||
1709 | */ |
||||
1710 | protected function unexpected(?Token $atToken = null): SyntaxErrorException |
||||
1711 | { |
||||
1712 | $token = $atToken ?? $this->lexer->getToken(); |
||||
1713 | |||||
1714 | return $this->lexer->createSyntaxErrorException(\sprintf('Unexpected %s', $token)); |
||||
1715 | } |
||||
1716 | |||||
1717 | /** |
||||
1718 | * Returns a possibly empty list of parse nodes, determined by |
||||
1719 | * the parseFn. This list begins with a lex token of openKind |
||||
1720 | * and ends with a lex token of closeKind. Advances the parser |
||||
1721 | * to the next lex token after the closing token. |
||||
1722 | * |
||||
1723 | * @param string $openKind |
||||
1724 | * @param callable $parseFunction |
||||
1725 | * @param string $closeKind |
||||
1726 | * @return array |
||||
1727 | * @throws SyntaxErrorException |
||||
1728 | */ |
||||
1729 | protected function any(string $openKind, callable $parseFunction, string $closeKind): array |
||||
1730 | { |
||||
1731 | $this->expect($openKind); |
||||
1732 | |||||
1733 | $nodes = []; |
||||
1734 | |||||
1735 | while (!$this->skip($closeKind)) { |
||||
1736 | $nodes[] = $parseFunction(); |
||||
1737 | } |
||||
1738 | |||||
1739 | return $nodes; |
||||
1740 | } |
||||
1741 | |||||
1742 | /** |
||||
1743 | * Returns a non-empty list of parse nodes, determined by |
||||
1744 | * the parseFn. This list begins with a lex token of openKind |
||||
1745 | * and ends with a lex token of closeKind. Advances the parser |
||||
1746 | * to the next lex token after the closing token. |
||||
1747 | * |
||||
1748 | * @param string $openKind |
||||
1749 | * @param callable $parseFunction |
||||
1750 | * @param string $closeKind |
||||
1751 | * @return array |
||||
1752 | * @throws SyntaxErrorException |
||||
1753 | */ |
||||
1754 | protected function many(string $openKind, callable $parseFunction, string $closeKind): array |
||||
1755 | { |
||||
1756 | $this->expect($openKind); |
||||
1757 | |||||
1758 | $nodes = [$parseFunction()]; |
||||
1759 | |||||
1760 | while (!$this->skip($closeKind)) { |
||||
1761 | $nodes[] = $parseFunction(); |
||||
1762 | } |
||||
1763 | |||||
1764 | return $nodes; |
||||
1765 | } |
||||
1766 | } |
||||
1767 |