Passed
Push — master ( 16c004...87d406 )
by Christoffer
02:33
created

Parser::parseValueLiteral()   C

Complexity

Conditions 13
Paths 12

Size

Total Lines 40
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 13
eloc 30
nc 12
nop 1
dl 0
loc 40
rs 5.1234
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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