Passed
Pull Request — master (#237)
by Christoffer
02:28
created

Parser::lexEnumValueDefinition()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 6
nc 1
nop 0
dl 0
loc 9
rs 9.6666
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\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
     * @param string $name
66
     * @param array  $arguments
67
     * @return mixed
68
     *
69
     * @throws InvariantException
70
     * @throws SyntaxErrorException
71
     */
72
    public function __call(string $name, array $arguments)
73
    {
74
        $lexCallback = \str_replace('parse', 'lex', $name);
75
76
        if (\method_exists($this, $lexCallback)) {
77
            return $this->parsePartial([$this, $lexCallback], ...$arguments);
0 ignored issues
show
Bug introduced by
$arguments is expanded, but the parameter $source of Digia\GraphQL\Language\Parser::parsePartial() does not expect variable arguments. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

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