Passed
Pull Request — master (#237)
by Christoffer
02:32 queued 17s
created

Parser::lexDefinition()   D

Complexity

Conditions 17
Paths 17

Size

Total Lines 31
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 17
eloc 23
nc 17
nop 0
dl 0
loc 31
rs 4.9807
c 0
b 0
f 0

How to fix   Complexity   

Long Method

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

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

Commonly applied refactorings include:

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