Passed
Push — master ( da4ce9...54c779 )
by Christoffer
02:24
created

Parser::many()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 3
dl 0
loc 11
rs 10
c 0
b 0
f 0
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\ExecutableDefinitionNodeInterface;
17
use Digia\GraphQL\Language\Node\FieldDefinitionNode;
18
use Digia\GraphQL\Language\Node\FieldNode;
19
use Digia\GraphQL\Language\Node\FloatValueNode;
20
use Digia\GraphQL\Language\Node\FragmentDefinitionNode;
21
use Digia\GraphQL\Language\Node\FragmentNodeInterface;
22
use Digia\GraphQL\Language\Node\FragmentSpreadNode;
23
use Digia\GraphQL\Language\Node\InlineFragmentNode;
24
use Digia\GraphQL\Language\Node\InputObjectTypeDefinitionNode;
25
use Digia\GraphQL\Language\Node\InputObjectTypeExtensionNode;
26
use Digia\GraphQL\Language\Node\InputValueDefinitionNode;
27
use Digia\GraphQL\Language\Node\InterfaceTypeDefinitionNode;
28
use Digia\GraphQL\Language\Node\InterfaceTypeExtensionNode;
29
use Digia\GraphQL\Language\Node\IntValueNode;
30
use Digia\GraphQL\Language\Node\ListTypeNode;
31
use Digia\GraphQL\Language\Node\ListValueNode;
32
use Digia\GraphQL\Language\Node\NamedTypeNode;
33
use Digia\GraphQL\Language\Node\NameNode;
34
use Digia\GraphQL\Language\Node\NodeInterface;
35
use Digia\GraphQL\Language\Node\NonNullTypeNode;
36
use Digia\GraphQL\Language\Node\NullValueNode;
37
use Digia\GraphQL\Language\Node\ObjectFieldNode;
38
use Digia\GraphQL\Language\Node\ObjectTypeDefinitionNode;
39
use Digia\GraphQL\Language\Node\ObjectTypeExtensionNode;
40
use Digia\GraphQL\Language\Node\ObjectValueNode;
41
use Digia\GraphQL\Language\Node\OperationDefinitionNode;
42
use Digia\GraphQL\Language\Node\OperationTypeDefinitionNode;
43
use Digia\GraphQL\Language\Node\ScalarTypeDefinitionNode;
44
use Digia\GraphQL\Language\Node\ScalarTypeExtensionNode;
45
use Digia\GraphQL\Language\Node\SchemaDefinitionNode;
46
use Digia\GraphQL\Language\Node\SchemaExtensionNode;
47
use Digia\GraphQL\Language\Node\SelectionSetNode;
48
use Digia\GraphQL\Language\Node\StringValueNode;
49
use Digia\GraphQL\Language\Node\TypeSystemDefinitionNodeInterface;
50
use Digia\GraphQL\Language\Node\TypeSystemExtensionNodeInterface;
51
use Digia\GraphQL\Language\Node\TypeNodeInterface;
52
use Digia\GraphQL\Language\Node\UnionTypeDefinitionNode;
53
use Digia\GraphQL\Language\Node\UnionTypeExtensionNode;
54
use Digia\GraphQL\Language\Node\ValueNodeInterface;
55
use Digia\GraphQL\Language\Node\VariableDefinitionNode;
56
use Digia\GraphQL\Language\Node\VariableNode;
57
use function Digia\GraphQL\Util\arraySome;
58
59
/** @noinspection PhpHierarchyChecksInspection */
60
61
/**
62
 * Class Parser
63
 * @package Digia\GraphQL\Language
64
 */
65
class Parser implements ParserInterface
66
{
67
    /**
68
     * @var LexerInterface
69
     */
70
    protected $lexer;
71
72
    /**
73
     * @param string $name
74
     * @param array  $arguments
75
     * @return mixed
76
     *
77
     * @throws InvariantException
78
     * @throws SyntaxErrorException
79
     */
80
    public function __call(string $name, array $arguments)
81
    {
82
        $lexCallback = \str_replace('parse', 'lex', $name);
83
84
        if (\method_exists($this, $lexCallback)) {
85
            return $this->parsePartial([$this, $lexCallback], $arguments[0], $arguments[1] ?? []);
86
        }
87
88
        return $this;
89
    }
90
91
    /**
92
     * Given a GraphQL source, parses it into a Document.
93
     * Throws GraphQLError if a syntax error is encountered.
94
     *
95
     * @inheritdoc
96
     * @throws SyntaxErrorException
97
     * @throws \ReflectionException
98
     * @throws InvariantException
99
     */
100
    public function parse($source, array $options = []): DocumentNode
101
    {
102
        $this->lexer = $this->createLexer($source, $options);
103
104
        return $this->lexDocument();
105
    }
106
107
    /**
108
     * @param callable      $lexCallback
109
     * @param Source|string $source
110
     * @param array         $options
111
     * @return NodeInterface
112
     * @throws InvariantException
113
     * @throws SyntaxErrorException
114
     */
115
    protected function parsePartial(callable $lexCallback, $source, array $options = []): NodeInterface
116
    {
117
        $this->lexer = $this->createLexer($source, $options);
118
119
        $this->expect(TokenKindEnum::SOF);
120
        $node = $lexCallback($source, $options);
121
        $this->expect(TokenKindEnum::EOF);
122
123
        return $node;
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 lexName(): 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
    protected function lexDocument(): DocumentNode
149
    {
150
        $start = $this->lexer->getToken();
151
152
        $this->expect(TokenKindEnum::SOF);
153
154
        $definitions = [];
155
156
        do {
157
            $definitions[] = $this->lexDefinition();
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 lexDefinition(): 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->lexExecutableDefinition();
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::DIRECTIVE:
191
                    return $this->lexTypeSystemDefinition();
192
                case KeywordEnum::EXTEND:
193
                    return $this->lexTypeSystemExtension();
194
195
            }
196
        } elseif ($this->peek(TokenKindEnum::BRACE_L)) {
197
            return $this->lexExecutableDefinition();
198
        } elseif ($this->peekDescription()) {
199
            return $this->lexTypeSystemDefinition();
200
        }
201
202
        throw $this->unexpected();
203
    }
204
205
    /**
206
     * ExecutableDefinition :
207
     *   - OperationDefinition
208
     *   - FragmentDefinition
209
     *
210
     * @return ExecutableDefinitionNodeInterface
211
     * @throws SyntaxErrorException
212
     */
213
    protected function lexExecutableDefinition(): ExecutableDefinitionNodeInterface
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->lexOperationDefinition();
224
                case KeywordEnum::FRAGMENT:
225
                    return $this->lexFragmentDefinition();
226
            }
227
        } elseif ($this->peek(TokenKindEnum::BRACE_L)) {
228
            // Anonymous query
229
            return $this->lexOperationDefinition();
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 lexOperationDefinition(): 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->lexSelectionSet(),
257
                $this->createLocation($start)
258
            );
259
        }
260
261
        $operation = $this->lexOperationType();
262
263
        if ($this->peek(TokenKindEnum::NAME)) {
264
            $name = $this->lexName();
265
        }
266
267
        return new OperationDefinitionNode(
268
            $operation,
269
            $name ?? null,
270
            $this->lexVariableDefinitions(),
271
            $this->lexDirectives(),
272
            $this->lexSelectionSet(),
273
            $this->createLocation($start)
274
        );
275
    }
276
277
    /**
278
     * OperationType : one of query mutation subscription
279
     *
280
     * @return null|string
281
     * @throws SyntaxErrorException
282
     */
283
    protected function lexOperationType(): ?string
284
    {
285
        $token = $this->expect(TokenKindEnum::NAME);
286
        $value = $token->getValue();
287
288
        if (isOperation($value)) {
289
            return $value;
290
        }
291
292
        throw $this->unexpected($token);
293
    }
294
295
    /**
296
     * VariableDefinitions : ( VariableDefinition+ )
297
     *
298
     * @return array
299
     * @throws SyntaxErrorException
300
     */
301
    protected function lexVariableDefinitions(): array
302
    {
303
        return $this->peek(TokenKindEnum::PAREN_L)
304
            ? $this->many(
305
                TokenKindEnum::PAREN_L,
306
                [$this, 'lexVariableDefinition'],
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 lexVariableDefinition(): 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->lexType();
328
        };
329
330
        return new VariableDefinitionNode(
331
            $this->lexVariable(),
332
            $parseType(),
333
            $this->skip(TokenKindEnum::EQUALS)
334
                ? $this->lexValue(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 lexVariable(): VariableNode
347
    {
348
        $start = $this->lexer->getToken();
349
350
        $this->expect(TokenKindEnum::DOLLAR);
351
352
        return new VariableNode($this->lexName(), $this->createLocation($start));
353
    }
354
355
    /**
356
     * SelectionSet : { Selection+ }
357
     *
358
     * @return SelectionSetNode
359
     * @throws SyntaxErrorException
360
     */
361
    protected function lexSelectionSet(): SelectionSetNode
362
    {
363
        $start = $this->lexer->getToken();
364
365
        return new SelectionSetNode(
366
            $this->many(
367
                TokenKindEnum::BRACE_L,
368
                [$this, 'lexSelection'],
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 lexSelection(): NodeInterface
385
    {
386
        return $this->peek(TokenKindEnum::SPREAD)
387
            ? $this->lexFragment()
388
            : $this->lexField();
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 lexField(): FieldNode
400
    {
401
        $start = $this->lexer->getToken();
402
403
        $nameOrAlias = $this->lexName();
404
405
        if ($this->skip(TokenKindEnum::COLON)) {
406
            $alias = $nameOrAlias;
407
            $name  = $this->lexName();
408
        } else {
409
            $name = $nameOrAlias;
410
        }
411
412
        return new FieldNode(
413
            $alias ?? null,
414
            $name,
415
            $this->lexArguments(false),
416
            $this->lexDirectives(),
417
            $this->peek(TokenKindEnum::BRACE_L)
418
                ? $this->lexSelectionSet()
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 lexArguments(bool $isConst = false): ?array
432
    {
433
        /**
434
         * @return ArgumentNode
435
         */
436
        $parseFunction = function () use ($isConst): ArgumentNode {
437
            return $this->lexArgument($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 lexArgument(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->lexValue($isConst);
466
        };
467
468
        return new ArgumentNode(
469
            $this->lexName(),
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 lexFragment(): 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->lexFragmentName($token),
498
                $this->lexDirectives(),
499
                null,
500
                $this->createLocation($start)
501
            );
502
        }
503
504
        if (KeywordEnum::ON === $token->getValue()) {
505
            $this->lexer->advance();
506
            $typeCondition = $this->lexNamedType();
507
        }
508
509
        return new InlineFragmentNode(
510
            $typeCondition ?? null,
511
            $this->lexDirectives(),
512
            $this->lexSelectionSet(),
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 lexFragmentDefinition(): 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->lexNamedType();
535
        };
536
537
        return new FragmentDefinitionNode(
538
            $this->lexFragmentName(),
539
            $this->lexVariableDefinitions(),
540
            $parseTypeCondition(),
541
            $this->lexDirectives(),
542
            $this->lexSelectionSet(),
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 lexFragmentName(?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->lexName();
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 lexValue(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->lexList($isConst);
599
            case TokenKindEnum::BRACE_L:
600
                return $this->lexObject($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->lexStringLiteral();
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->lexVariable();
626
                }
627
                break;
628
        }
629
630
        throw $this->unexpected();
631
    }
632
633
    /**
634
     * @return StringValueNode
635
     */
636
    protected function lexStringLiteral(): 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 lexList(bool $isConst): ListValueNode
659
    {
660
        $start = $this->lexer->getToken();
661
662
        $parseFunction = function () use ($isConst) {
663
            return $this->lexValue($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 lexObject(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->lexObjectField($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 lexObjectField(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->lexValue($isConst);
718
        };
719
720
        return new ObjectFieldNode(
721
            $this->lexName(),
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 lexDirectives(bool $isConst = false): array
737
    {
738
        $directives = [];
739
740
        while ($this->peek(TokenKindEnum::AT)) {
741
            $directives[] = $this->lexDirective($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 lexDirective(bool $isConst): DirectiveNode
755
    {
756
        $start = $this->lexer->getToken();
757
758
        $this->expect(TokenKindEnum::AT);
759
760
        return new DirectiveNode(
761
            $this->lexName(),
762
            $this->lexArguments($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 lexType(): TypeNodeInterface
779
    {
780
        $start = $this->lexer->getToken();
781
782
        if ($this->skip(TokenKindEnum::BRACKET_L)) {
783
            $type = $this->lexType();
784
785
            $this->expect(TokenKindEnum::BRACKET_R);
786
787
            $type = new ListTypeNode($type, $this->createLocation($start));
788
        } else {
789
            $type = $this->lexNamedType();
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 lexNamedType(): NamedTypeNode
806
    {
807
        $start = $this->lexer->getToken();
808
809
        return new NamedTypeNode($this->lexName(), $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 TypeSystemDefinitionNodeInterface
830
     * @throws SyntaxErrorException
831
     * @throws \ReflectionException
832
     */
833
    protected function lexTypeSystemDefinition(): TypeSystemDefinitionNodeInterface
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->lexSchemaDefinition();
844
                case KeywordEnum::SCALAR:
845
                    return $this->lexScalarTypeDefinition();
846
                case KeywordEnum::TYPE:
847
                    return $this->lexObjectTypeDefinition();
848
                case KeywordEnum::INTERFACE:
849
                    return $this->lexInterfaceTypeDefinition();
850
                case KeywordEnum::UNION:
851
                    return $this->lexUnionTypeDefinition();
852
                case KeywordEnum::ENUM:
853
                    return $this->lexEnumTypeDefinition();
854
                case KeywordEnum::INPUT:
855
                    return $this->lexInputObjectTypeDefinition();
856
                case KeywordEnum::DIRECTIVE:
857
                    return $this->lexDirectiveDefinition();
858
            }
859
        }
860
861
        throw $this->unexpected($token);
862
    }
863
864
    /**
865
     * @return bool
866
     */
867
    protected function peekDescription(): bool
868
    {
869
        return $this->peek(TokenKindEnum::STRING) || $this->peek(TokenKindEnum::BLOCK_STRING);
870
    }
871
872
    /**
873
     * Description : StringValue
874
     *
875
     * @return StringValueNode|null
876
     */
877
    public function lexDescription(): ?StringValueNode
878
    {
879
        return $this->peekDescription()
880
            ? $this->lexStringLiteral()
881
            : null;
882
    }
883
884
    /**
885
     * SchemaDefinition : schema Directives[Const]? { OperationTypeDefinition+ }
886
     *
887
     * @return SchemaDefinitionNode
888
     * @throws SyntaxErrorException
889
     */
890
    protected function lexSchemaDefinition(): SchemaDefinitionNode
891
    {
892
        $start = $this->lexer->getToken();
893
894
        $this->expectKeyword(KeywordEnum::SCHEMA);
895
896
        return new SchemaDefinitionNode(
897
            $this->lexDirectives(),
898
            $this->many(
899
                TokenKindEnum::BRACE_L,
900
                [$this, 'lexOperationTypeDefinition'],
901
                TokenKindEnum::BRACE_R
902
            ),
903
            $this->createLocation($start)
904
        );
905
    }
906
907
    /**
908
     * OperationTypeDefinition : OperationType : NamedType
909
     *
910
     * @return OperationTypeDefinitionNode
911
     * @throws SyntaxErrorException
912
     */
913
    protected function lexOperationTypeDefinition(): OperationTypeDefinitionNode
914
    {
915
        $start = $this->lexer->getToken();
916
917
        $operation = $this->lexOperationType();
918
919
        $this->expect(TokenKindEnum::COLON);
920
921
        return new OperationTypeDefinitionNode(
922
            $operation,
923
            $this->lexNamedType(),
924
            $this->createLocation($start)
925
        );
926
    }
927
928
    /**
929
     * ScalarTypeDefinition : Description? scalar Name Directives[Const]?
930
     *
931
     * @return ScalarTypeDefinitionNode
932
     * @throws SyntaxErrorException
933
     */
934
    protected function lexScalarTypeDefinition(): ScalarTypeDefinitionNode
935
    {
936
        $start = $this->lexer->getToken();
937
938
        $description = $this->lexDescription();
939
940
        $this->expectKeyword(KeywordEnum::SCALAR);
941
942
        return new ScalarTypeDefinitionNode(
943
            $description,
944
            $this->lexName(),
945
            $this->lexDirectives(),
946
            $this->createLocation($start)
947
        );
948
    }
949
950
    /**
951
     * ObjectTypeDefinition :
952
     *   Description?
953
     *   type Name ImplementsInterfaces? Directives[Const]? FieldsDefinition?
954
     *
955
     * @return ObjectTypeDefinitionNode
956
     * @throws SyntaxErrorException
957
     */
958
    protected function lexObjectTypeDefinition(): ObjectTypeDefinitionNode
959
    {
960
        $start = $this->lexer->getToken();
961
962
        $description = $this->lexDescription();
963
964
        $this->expectKeyword(KeywordEnum::TYPE);
965
966
        return new ObjectTypeDefinitionNode(
967
            $description,
968
            $this->lexName(),
969
            $this->lexImplementsInterfaces(),
970
            $this->lexDirectives(),
971
            $this->lexFieldsDefinition(),
972
            $this->createLocation($start)
973
        );
974
    }
975
976
    /**
977
     * ImplementsInterfaces :
978
     *   - implements `&`? NamedType
979
     *   - ImplementsInterfaces & NamedType
980
     *
981
     * @return array
982
     * @throws SyntaxErrorException
983
     */
984
    protected function lexImplementsInterfaces(): array
985
    {
986
        $types = [];
987
988
        $token = $this->lexer->getToken();
989
990
        if ('implements' === $token->getValue()) {
991
            $this->lexer->advance();
992
993
            // Optional leading ampersand
994
            $this->skip(TokenKindEnum::AMP);
995
996
            do {
997
                $types[] = $this->lexNamedType();
998
            } while ($this->skip(TokenKindEnum::AMP));
999
        }
1000
1001
        return $types;
1002
    }
1003
1004
    /**
1005
     * FieldsDefinition : { FieldDefinition+ }
1006
     *
1007
     * @return array
1008
     * @throws SyntaxErrorException
1009
     */
1010
    protected function lexFieldsDefinition(): array
1011
    {
1012
        return $this->peek(TokenKindEnum::BRACE_L)
1013
            ? $this->many(
1014
                TokenKindEnum::BRACE_L,
1015
                [$this, 'lexFieldDefinition'],
1016
                TokenKindEnum::BRACE_R
1017
            )
1018
            : [];
1019
    }
1020
1021
    /**
1022
     * FieldDefinition :
1023
     *   - Description? Name ArgumentsDefinition? : Type Directives[Const]?
1024
     *
1025
     * @return FieldDefinitionNode
1026
     * @throws SyntaxErrorException
1027
     */
1028
    protected function lexFieldDefinition(): FieldDefinitionNode
1029
    {
1030
        $start = $this->lexer->getToken();
1031
1032
        $description = $this->lexDescription();
1033
        $name        = $this->lexName();
1034
        $arguments   = $this->lexArgumentsDefinition();
1035
1036
        $this->expect(TokenKindEnum::COLON);
1037
1038
        return new FieldDefinitionNode(
1039
            $description,
1040
            $name,
1041
            $arguments,
1042
            $this->lexType(),
1043
            $this->lexDirectives(),
1044
            $this->createLocation($start)
1045
        );
1046
    }
1047
1048
    /**
1049
     * ArgumentsDefinition : ( InputValueDefinition+ )
1050
     *
1051
     * @return InputValueDefinitionNode[]
1052
     * @throws SyntaxErrorException
1053
     */
1054
    protected function lexArgumentsDefinition(): array
1055
    {
1056
        $parseFunction = function (): InputValueDefinitionNode {
1057
            return $this->lexInputValueDefinition();
1058
        };
1059
1060
        return $this->peek(TokenKindEnum::PAREN_L)
1061
            ? $this->many(
1062
                TokenKindEnum::PAREN_L,
1063
                $parseFunction,
1064
                TokenKindEnum::PAREN_R
1065
            )
1066
            : [];
1067
    }
1068
1069
    /**
1070
     * InputValueDefinition :
1071
     *   - Description? Name : Type DefaultValue? Directives[Const]?
1072
     *
1073
     * @return InputValueDefinitionNode
1074
     * @throws SyntaxErrorException
1075
     */
1076
    protected function lexInputValueDefinition(): InputValueDefinitionNode
1077
    {
1078
        $start = $this->lexer->getToken();
1079
1080
        $description = $this->lexDescription();
1081
        $name        = $this->lexName();
1082
1083
        $this->expect(TokenKindEnum::COLON);
1084
1085
        return new InputValueDefinitionNode(
1086
            $description,
1087
            $name,
1088
            $this->lexType(),
1089
            $this->skip(TokenKindEnum::EQUALS)
1090
                ? $this->lexValue(true)
1091
                : null,
1092
            $this->lexDirectives(true),
1093
            $this->createLocation($start)
1094
        );
1095
    }
1096
1097
    /**
1098
     * InterfaceTypeDefinition :
1099
     *   - Description? interface Name Directives[Const]? FieldsDefinition?
1100
     *
1101
     * @return InterfaceTypeDefinitionNode
1102
     * @throws SyntaxErrorException
1103
     */
1104
    protected function lexInterfaceTypeDefinition(): InterfaceTypeDefinitionNode
1105
    {
1106
        $start = $this->lexer->getToken();
1107
1108
        $description = $this->lexDescription();
1109
1110
        $this->expectKeyword(KeywordEnum::INTERFACE);
1111
1112
        return new InterfaceTypeDefinitionNode(
1113
            $description,
1114
            $this->lexName(),
1115
            $this->lexDirectives(),
1116
            $this->lexFieldsDefinition(),
1117
            $this->createLocation($start)
1118
        );
1119
    }
1120
1121
    /**
1122
     * UnionTypeDefinition :
1123
     *   - Description? union Name Directives[Const]? UnionMemberTypes?
1124
     *
1125
     * @return UnionTypeDefinitionNode
1126
     * @throws SyntaxErrorException
1127
     */
1128
    protected function lexUnionTypeDefinition(): UnionTypeDefinitionNode
1129
    {
1130
        $start = $this->lexer->getToken();
1131
1132
        $description = $this->lexDescription();
1133
1134
        $this->expectKeyword(KeywordEnum::UNION);
1135
1136
        return new UnionTypeDefinitionNode(
1137
            $description,
1138
            $this->lexName(),
1139
            $this->lexDirectives(),
1140
            $this->lexUnionMemberTypes(),
1141
            $this->createLocation($start)
1142
        );
1143
    }
1144
1145
    /**
1146
     * UnionMemberTypes :
1147
     *   - = `|`? NamedType
1148
     *   - UnionMemberTypes | NamedType
1149
     *
1150
     * @return array
1151
     * @throws SyntaxErrorException
1152
     */
1153
    protected function lexUnionMemberTypes(): array
1154
    {
1155
        $types = [];
1156
1157
        if ($this->skip(TokenKindEnum::EQUALS)) {
1158
            // Optional leading pipe
1159
            $this->skip(TokenKindEnum::PIPE);
1160
1161
            do {
1162
                $types[] = $this->lexNamedType();
1163
            } while ($this->skip(TokenKindEnum::PIPE));
1164
        }
1165
1166
        return $types;
1167
    }
1168
1169
    /**
1170
     * EnumTypeDefinition :
1171
     *   - Description? enum Name Directives[Const]? EnumValuesDefinition?
1172
     *
1173
     * @return EnumTypeDefinitionNode
1174
     * @throws SyntaxErrorException
1175
     */
1176
    protected function lexEnumTypeDefinition(): EnumTypeDefinitionNode
1177
    {
1178
        $start = $this->lexer->getToken();
1179
1180
        $description = $this->lexDescription();
1181
1182
        $this->expectKeyword(KeywordEnum::ENUM);
1183
1184
        return new EnumTypeDefinitionNode(
1185
            $description,
1186
            $this->lexName(),
1187
            $this->lexDirectives(),
1188
            $this->lexEnumValuesDefinition(),
1189
            $this->createLocation($start)
1190
        );
1191
    }
1192
1193
    /**
1194
     * EnumValuesDefinition : { EnumValueDefinition+ }
1195
     *
1196
     * @return array
1197
     * @throws SyntaxErrorException
1198
     */
1199
    protected function lexEnumValuesDefinition(): array
1200
    {
1201
        return $this->peek(TokenKindEnum::BRACE_L)
1202
            ? $this->many(
1203
                TokenKindEnum::BRACE_L,
1204
                [$this, 'lexEnumValueDefinition'],
1205
                TokenKindEnum::BRACE_R
1206
            )
1207
            : [];
1208
    }
1209
1210
    /**
1211
     * EnumValueDefinition : Description? EnumValue Directives[Const]?
1212
     *
1213
     * EnumValue : Name
1214
     *
1215
     * @return EnumValueDefinitionNode
1216
     * @throws SyntaxErrorException
1217
     */
1218
    protected function lexEnumValueDefinition(): EnumValueDefinitionNode
1219
    {
1220
        $start = $this->lexer->getToken();
1221
1222
        return new EnumValueDefinitionNode(
1223
            $this->lexDescription(),
1224
            $this->lexName(),
1225
            $this->lexDirectives(),
1226
            $this->createLocation($start)
1227
        );
1228
    }
1229
1230
    /**
1231
     * InputObjectTypeDefinition :
1232
     *   - Description? input Name Directives[Const]? InputFieldsDefinition?
1233
     *
1234
     * @return InputObjectTypeDefinitionNode
1235
     * @throws SyntaxErrorException
1236
     */
1237
    protected function lexInputObjectTypeDefinition(): InputObjectTypeDefinitionNode
1238
    {
1239
        $start = $this->lexer->getToken();
1240
1241
        $description = $this->lexDescription();
1242
1243
        $this->expectKeyword(KeywordEnum::INPUT);
1244
1245
        return new InputObjectTypeDefinitionNode(
1246
            $description,
1247
            $this->lexName(),
1248
            $this->lexDirectives(true),
1249
            $this->lexInputFieldsDefinition(),
1250
            $this->createLocation($start)
1251
        );
1252
    }
1253
1254
    /**
1255
     * InputFieldsDefinition : { InputValueDefinition+ }
1256
     *
1257
     * @return array
1258
     * @throws SyntaxErrorException
1259
     */
1260
    protected function lexInputFieldsDefinition(): array
1261
    {
1262
        $parseFunction = function (): InputValueDefinitionNode {
1263
            return $this->lexInputValueDefinition();
1264
        };
1265
1266
        return $this->peek(TokenKindEnum::BRACE_L)
1267
            ? $this->many(
1268
                TokenKindEnum::BRACE_L,
1269
                $parseFunction,
1270
                TokenKindEnum::BRACE_R
1271
            )
1272
            : [];
1273
    }
1274
1275
    /**
1276
     * TypeExtension :
1277
     *   - ScalarTypeExtension
1278
     *   - ObjectTypeExtension
1279
     *   - InterfaceTypeExtension
1280
     *   - UnionTypeExtension
1281
     *   - EnumTypeExtension
1282
     *   - InputObjectTypeDefinition
1283
     *
1284
     * @return TypeSystemExtensionNodeInterface
1285
     * @throws SyntaxErrorException
1286
     */
1287
    protected function lexTypeSystemExtension(): TypeSystemExtensionNodeInterface
1288
    {
1289
        $token = $this->lexer->lookahead();
1290
1291
        if (TokenKindEnum::NAME === $token->getKind()) {
1292
            switch ($token->getValue()) {
1293
                case KeywordEnum::SCHEMA:
1294
                    return $this->lexSchemaExtension();
1295
                case KeywordEnum::SCALAR:
1296
                    return $this->lexScalarTypeExtension(false);
1297
                case KeywordEnum::TYPE:
1298
                    return $this->lexObjectTypeExtension();
1299
                case KeywordEnum::INTERFACE:
1300
                    return $this->lexInterfaceTypeExtension();
1301
                case KeywordEnum::UNION:
1302
                    return $this->lexUnionTypeExtension();
1303
                case KeywordEnum::ENUM:
1304
                    return $this->lexEnumTypeExtension();
1305
                case KeywordEnum::INPUT:
1306
                    return $this->lexInputObjectTypeExtension();
1307
            }
1308
        }
1309
1310
        throw $this->unexpected($token);
1311
    }
1312
1313
    /**
1314
     * SchemaExtension :
1315
     *  - extend schema Directives[Const]? { OperationTypeDefinition+ }
1316
     *  - extend schema Directives[Const]
1317
     *
1318
     * @return SchemaExtensionNode
1319
     * @throws SyntaxErrorException
1320
     */
1321
    protected function lexSchemaExtension(): SchemaExtensionNode
1322
    {
1323
        $start = $this->lexer->getToken();
1324
1325
        $this->expectKeyword(KeywordEnum::EXTEND);
1326
        $this->expectKeyword(KeywordEnum::SCHEMA);
1327
1328
        $directives = $this->lexDirectives(true);
1329
1330
        $parseFunction = function (): OperationTypeDefinitionNode {
1331
            return $this->lexOperationTypeDefinition();
1332
        };
1333
1334
        $operationTypes = $this->peek(TokenKindEnum::BRACE_L)
1335
            ? $this->many(
1336
                TokenKindEnum::BRACE_L,
1337
                $parseFunction,
1338
                TokenKindEnum::BRACE_R
1339
            )
1340
            : [];
1341
1342
        if (empty($directives) && empty($operationTypes)) {
1343
            $this->unexpected();
1344
        }
1345
1346
        return new SchemaExtensionNode($directives, $operationTypes, $this->createLocation($start));
1347
    }
1348
1349
    /**
1350
     * ScalarTypeExtension :
1351
     *   - extend scalar Name Directives[Const]
1352
     *
1353
     * @param bool $isConst
1354
     * @return ScalarTypeExtensionNode
1355
     * @throws SyntaxErrorException
1356
     */
1357
    protected function lexScalarTypeExtension(bool $isConst = false): ScalarTypeExtensionNode
1358
    {
1359
        $start = $this->lexer->getToken();
1360
1361
        $this->expectKeyword(KeywordEnum::EXTEND);
1362
        $this->expectKeyword(KeywordEnum::SCALAR);
1363
1364
        $name       = $this->lexName();
1365
        $directives = $this->lexDirectives($isConst);
1366
1367
        if (empty($directives)) {
1368
            throw $this->unexpected();
1369
        }
1370
1371
        return new ScalarTypeExtensionNode($name, $directives, $this->createLocation($start));
1372
    }
1373
1374
    /**
1375
     * ObjectTypeExtension :
1376
     *  - extend type Name ImplementsInterfaces? Directives[Const]? FieldsDefinition
1377
     *  - extend type Name ImplementsInterfaces? Directives[Const]
1378
     *  - extend type Name ImplementsInterfaces
1379
     *
1380
     * @return ObjectTypeExtensionNode
1381
     * @throws SyntaxErrorException
1382
     */
1383
    protected function lexObjectTypeExtension(): ObjectTypeExtensionNode
1384
    {
1385
        $start = $this->lexer->getToken();
1386
1387
        $this->expectKeyword(KeywordEnum::EXTEND);
1388
        $this->expectKeyword(KeywordEnum::TYPE);
1389
1390
        $name       = $this->lexName();
1391
        $interfaces = $this->lexImplementsInterfaces();
1392
        $directives = $this->lexDirectives();
1393
        $fields     = $this->lexFieldsDefinition();
1394
1395
        if (empty($interfaces) && empty($directives) && empty($fields)) {
1396
            throw $this->unexpected();
1397
        }
1398
1399
        return new ObjectTypeExtensionNode(
1400
            $name,
1401
            $interfaces,
1402
            $directives,
1403
            $fields,
1404
            $this->createLocation($start)
1405
        );
1406
    }
1407
1408
    /**
1409
     * InterfaceTypeExtension :
1410
     *   - extend interface Name Directives[Const]? FieldsDefinition
1411
     *   - extend interface Name Directives[Const]
1412
     *
1413
     * @return InterfaceTypeExtensionNode
1414
     * @throws SyntaxErrorException
1415
     */
1416
    protected function lexInterfaceTypeExtension(): InterfaceTypeExtensionNode
1417
    {
1418
        $start = $this->lexer->getToken();
1419
1420
        $this->expectKeyword(KeywordEnum::EXTEND);
1421
        $this->expectKeyword(KeywordEnum::INTERFACE);
1422
1423
        $name       = $this->lexName();
1424
        $directives = $this->lexDirectives();
1425
        $fields     = $this->lexFieldsDefinition();
1426
1427
        if (empty($directives) && empty($fields)) {
1428
            throw $this->unexpected();
1429
        }
1430
1431
        return new InterfaceTypeExtensionNode($name, $directives, $fields, $this->createLocation($start));
1432
    }
1433
1434
    /**
1435
     * UnionTypeExtension :
1436
     *   - extend union Name Directives[Const]? UnionMemberTypes
1437
     *   - extend union Name Directives[Const]
1438
     *
1439
     * @return UnionTypeExtensionNode
1440
     * @throws SyntaxErrorException
1441
     */
1442
    protected function lexUnionTypeExtension(): UnionTypeExtensionNode
1443
    {
1444
        $start = $this->lexer->getToken();
1445
1446
        $this->expectKeyword(KeywordEnum::EXTEND);
1447
        $this->expectKeyword(KeywordEnum::UNION);
1448
1449
        $name       = $this->lexName();
1450
        $directives = $this->lexDirectives();
1451
        $types      = $this->lexUnionMemberTypes();
1452
1453
        if (empty($directives) && empty($types)) {
1454
            throw $this->unexpected();
1455
        }
1456
1457
        return new UnionTypeExtensionNode($name, $directives, $types, $this->createLocation($start));
1458
    }
1459
1460
    /**
1461
     * EnumTypeExtension :
1462
     *   - extend enum Name Directives[Const]? EnumValuesDefinition
1463
     *   - extend enum Name Directives[Const]
1464
     *
1465
     * @return EnumTypeExtensionNode
1466
     * @throws SyntaxErrorException
1467
     */
1468
    protected function lexEnumTypeExtension(): EnumTypeExtensionNode
1469
    {
1470
        $start = $this->lexer->getToken();
1471
1472
        $this->expectKeyword(KeywordEnum::EXTEND);
1473
        $this->expectKeyword(KeywordEnum::ENUM);
1474
1475
        $name       = $this->lexName();
1476
        $directives = $this->lexDirectives();
1477
        $values     = $this->lexEnumValuesDefinition();
1478
1479
        if (empty($directives) && empty($values)) {
1480
            throw $this->unexpected();
1481
        }
1482
1483
        return new EnumTypeExtensionNode($name, $directives, $values, $this->createLocation($start));
1484
    }
1485
1486
    /**
1487
     * InputObjectTypeExtension :
1488
     *   - extend input Name Directives[Const]? InputFieldsDefinition
1489
     *   - extend input Name Directives[Const]
1490
     *
1491
     * @return InputObjectTypeExtensionNode
1492
     * @throws SyntaxErrorException
1493
     */
1494
    protected function lexInputObjectTypeExtension(): InputObjectTypeExtensionNode
1495
    {
1496
        $start = $this->lexer->getToken();
1497
1498
        $this->expectKeyword(KeywordEnum::EXTEND);
1499
        $this->expectKeyword(KeywordEnum::INPUT);
1500
1501
        $name       = $this->lexName();
1502
        $directives = $this->lexDirectives(true);
1503
        $fields     = $this->lexInputFieldsDefinition();
1504
1505
        if (empty($directives) && empty($fields)) {
1506
            throw $this->unexpected();
1507
        }
1508
1509
        return new InputObjectTypeExtensionNode($name, $directives, $fields, $this->createLocation($start));
1510
    }
1511
1512
    /**
1513
     * DirectiveDefinition :
1514
     *   - Description? directive @ Name ArgumentsDefinition? on DirectiveLocations
1515
     *
1516
     * @return DirectiveDefinitionNode
1517
     * @throws SyntaxErrorException
1518
     * @throws \ReflectionException
1519
     */
1520
    protected function lexDirectiveDefinition(): DirectiveDefinitionNode
1521
    {
1522
        $start = $this->lexer->getToken();
1523
1524
        $description = $this->lexDescription();
1525
1526
        $this->expectKeyword(KeywordEnum::DIRECTIVE);
1527
        $this->expect(TokenKindEnum::AT);
1528
1529
        $name      = $this->lexName();
1530
        $arguments = $this->lexArgumentsDefinition();
1531
1532
        $this->expectKeyword(KeywordEnum::ON);
1533
1534
        $locations = $this->lexDirectiveLocations();
1535
1536
        return new DirectiveDefinitionNode(
1537
            $description,
1538
            $name,
1539
            $arguments,
1540
            $locations,
1541
            $this->createLocation($start)
1542
        );
1543
    }
1544
1545
    /**
1546
     * DirectiveLocations :
1547
     *   - `|`? DirectiveLocation
1548
     *   - DirectiveLocations | DirectiveLocation
1549
     *
1550
     * @return array
1551
     * @throws SyntaxErrorException
1552
     * @throws \ReflectionException
1553
     */
1554
    protected function lexDirectiveLocations(): array
1555
    {
1556
        $this->skip(TokenKindEnum::PIPE);
1557
1558
        $locations = [];
1559
1560
        do {
1561
            $locations[] = $this->lexDirectiveLocation();
1562
        } while ($this->skip(TokenKindEnum::PIPE));
1563
1564
        return $locations;
1565
    }
1566
1567
    /**
1568
     * DirectiveLocation :
1569
     *   - ExecutableDirectiveLocation
1570
     *   - TypeSystemDirectiveLocation
1571
     *
1572
     * ExecutableDirectiveLocation : one of
1573
     *   `QUERY`
1574
     *   `MUTATION`
1575
     *   `SUBSCRIPTION`
1576
     *   `FIELD`
1577
     *   `FRAGMENT_DEFINITION`
1578
     *   `FRAGMENT_SPREAD`
1579
     *   `INLINE_FRAGMENT`
1580
     *
1581
     * TypeSystemDirectiveLocation : one of
1582
     *   `SCHEMA`
1583
     *   `SCALAR`
1584
     *   `OBJECT`
1585
     *   `FIELD_DEFINITION`
1586
     *   `ARGUMENT_DEFINITION`
1587
     *   `INTERFACE`
1588
     *   `UNION`
1589
     *   `ENUM`
1590
     *   `ENUM_VALUE`
1591
     *   `INPUT_OBJECT`
1592
     *   `INPUT_FIELD_DEFINITION`
1593
     *
1594
     * @return NameNode
1595
     * @throws SyntaxErrorException
1596
     * @throws \ReflectionException
1597
     */
1598
    protected function lexDirectiveLocation(): NameNode
1599
    {
1600
        $start = $this->lexer->getToken();
1601
1602
        $name = $this->lexName();
1603
1604
        if (arraySome(DirectiveLocationEnum::values(), function ($value) use ($name) {
1605
            return $name->getValue() === $value;
1606
        })) {
1607
            return $name;
1608
        }
1609
1610
        throw $this->unexpected($start);
1611
    }
1612
1613
    /**
1614
     * Returns a location object, used to identify the place in
1615
     * the source that created a given parsed object.
1616
     *
1617
     * @param Token $start
1618
     * @return Location|null
1619
     */
1620
    protected function createLocation(Token $start): ?Location
1621
    {
1622
        return !$this->lexer->getOption('noLocation', false)
1623
            ? new Location(
1624
                $start->getStart(),
1625
                $this->lexer->getLastToken()->getEnd(),
1626
                $this->lexer->getSource()
1627
            )
1628
            : null;
1629
    }
1630
1631
    /**
1632
     * @param string|Source $source
1633
     * @param array         $options
1634
     * @return LexerInterface
1635
     * @throws InvariantException
1636
     */
1637
    protected function createLexer($source, array $options): LexerInterface
1638
    {
1639
        return new Lexer($source instanceof Source ? $source : new Source($source), $options);
1640
    }
1641
1642
    /**
1643
     * Determines if the next token is of a given kind.
1644
     *
1645
     * @param string $kind
1646
     * @return bool
1647
     */
1648
    protected function peek(string $kind): bool
1649
    {
1650
        return $kind === $this->lexer->getToken()->getKind();
1651
    }
1652
1653
    /**
1654
     * If the next token is of the given kind, return true after advancing
1655
     * the lexer. Otherwise, do not change the parser state and return false.
1656
     *
1657
     * @param string $kind
1658
     * @return bool
1659
     */
1660
    protected function skip(string $kind): bool
1661
    {
1662
        if ($match = $this->peek($kind)) {
1663
            $this->lexer->advance();
1664
        }
1665
1666
        return $match;
1667
    }
1668
1669
    /**
1670
     * If the next token is of the given kind, return that token after advancing
1671
     * the lexer. Otherwise, do not change the parser state and throw an error.
1672
     *
1673
     * @param string $kind
1674
     * @return Token
1675
     * @throws SyntaxErrorException
1676
     */
1677
    protected function expect(string $kind): Token
1678
    {
1679
        $token = $this->lexer->getToken();
1680
1681
        if ($kind === $token->getKind()) {
1682
            $this->lexer->advance();
1683
            return $token;
1684
        }
1685
1686
        throw $this->lexer->createSyntaxErrorException(\sprintf('Expected %s, found %s.', $kind, $token));
1687
    }
1688
1689
    /**
1690
     * @param string $value
1691
     * @return Token
1692
     * @throws SyntaxErrorException
1693
     */
1694
    protected function expectKeyword(string $value): Token
1695
    {
1696
        $token = $this->lexer->getToken();
1697
1698
        if (TokenKindEnum::NAME === $token->getKind() && $value === $token->getValue()) {
1699
            $this->lexer->advance();
1700
            return $token;
1701
        }
1702
1703
        throw $this->lexer->createSyntaxErrorException(\sprintf('Expected %s, found %s', $value, $token));
1704
    }
1705
1706
    /**
1707
     * Helper function for creating an error when an unexpected lexed token
1708
     * is encountered.
1709
     *
1710
     * @param Token|null $atToken
1711
     * @return SyntaxErrorException
1712
     */
1713
    protected function unexpected(?Token $atToken = null): SyntaxErrorException
1714
    {
1715
        $token = $atToken ?? $this->lexer->getToken();
1716
1717
        return $this->lexer->createSyntaxErrorException(\sprintf('Unexpected %s', $token));
1718
    }
1719
1720
    /**
1721
     * Returns a possibly empty list of parse nodes, determined by
1722
     * the parseFn. This list begins with a lex token of openKind
1723
     * and ends with a lex token of closeKind. Advances the parser
1724
     * to the next lex token after the closing token.
1725
     *
1726
     * @param string   $openKind
1727
     * @param callable $parseFunction
1728
     * @param string   $closeKind
1729
     * @return array
1730
     * @throws SyntaxErrorException
1731
     */
1732
    protected function any(string $openKind, callable $parseFunction, string $closeKind): array
1733
    {
1734
        $this->expect($openKind);
1735
1736
        $nodes = [];
1737
1738
        while (!$this->skip($closeKind)) {
1739
            $nodes[] = $parseFunction();
1740
        }
1741
1742
        return $nodes;
1743
    }
1744
1745
    /**
1746
     * Returns a non-empty list of parse nodes, determined by
1747
     * the parseFn. This list begins with a lex token of openKind
1748
     * and ends with a lex token of closeKind. Advances the parser
1749
     * to the next lex token after the closing token.
1750
     *
1751
     * @param string   $openKind
1752
     * @param callable $parseFunction
1753
     * @param string   $closeKind
1754
     * @return array
1755
     * @throws SyntaxErrorException
1756
     */
1757
    protected function many(string $openKind, callable $parseFunction, string $closeKind): array
1758
    {
1759
        $this->expect($openKind);
1760
1761
        $nodes = [$parseFunction()];
1762
1763
        while (!$this->skip($closeKind)) {
1764
            $nodes[] = $parseFunction();
1765
        }
1766
1767
        return $nodes;
1768
    }
1769
}
1770