Passed
Push — master ( 4bfe95...7dd794 )
by Christoffer
02:37
created

Parser::lexImplementsInterfaces()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 18
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 9
nc 2
nop 0
dl 0
loc 18
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace Digia\GraphQL\Language;
4
5
use Digia\GraphQL\Error\InvariantException;
6
use Digia\GraphQL\Error\SyntaxErrorException;
7
use Digia\GraphQL\Language\Node\ArgumentNode;
8
use Digia\GraphQL\Language\Node\BooleanValueNode;
9
use Digia\GraphQL\Language\Node\DirectiveDefinitionNode;
10
use Digia\GraphQL\Language\Node\DirectiveNode;
11
use Digia\GraphQL\Language\Node\DocumentNode;
12
use Digia\GraphQL\Language\Node\EnumTypeDefinitionNode;
13
use Digia\GraphQL\Language\Node\EnumTypeExtensionNode;
14
use Digia\GraphQL\Language\Node\EnumValueDefinitionNode;
15
use Digia\GraphQL\Language\Node\EnumValueNode;
16
use Digia\GraphQL\Language\Node\ExecutableDefinitionNodeInterface;
17
use Digia\GraphQL\Language\Node\FieldDefinitionNode;
18
use Digia\GraphQL\Language\Node\FieldNode;
19
use Digia\GraphQL\Language\Node\FloatValueNode;
20
use Digia\GraphQL\Language\Node\FragmentDefinitionNode;
21
use Digia\GraphQL\Language\Node\FragmentNodeInterface;
22
use Digia\GraphQL\Language\Node\FragmentSpreadNode;
23
use Digia\GraphQL\Language\Node\InlineFragmentNode;
24
use Digia\GraphQL\Language\Node\InputObjectTypeDefinitionNode;
25
use Digia\GraphQL\Language\Node\InputObjectTypeExtensionNode;
26
use Digia\GraphQL\Language\Node\InputValueDefinitionNode;
27
use Digia\GraphQL\Language\Node\InterfaceTypeDefinitionNode;
28
use Digia\GraphQL\Language\Node\InterfaceTypeExtensionNode;
29
use Digia\GraphQL\Language\Node\IntValueNode;
30
use Digia\GraphQL\Language\Node\ListTypeNode;
31
use Digia\GraphQL\Language\Node\ListValueNode;
32
use Digia\GraphQL\Language\Node\NamedTypeNode;
33
use Digia\GraphQL\Language\Node\NameNode;
34
use Digia\GraphQL\Language\Node\NodeInterface;
35
use Digia\GraphQL\Language\Node\NonNullTypeNode;
36
use Digia\GraphQL\Language\Node\NullValueNode;
37
use Digia\GraphQL\Language\Node\ObjectFieldNode;
38
use Digia\GraphQL\Language\Node\ObjectTypeDefinitionNode;
39
use Digia\GraphQL\Language\Node\ObjectTypeExtensionNode;
40
use Digia\GraphQL\Language\Node\ObjectValueNode;
41
use Digia\GraphQL\Language\Node\OperationDefinitionNode;
42
use Digia\GraphQL\Language\Node\OperationTypeDefinitionNode;
43
use Digia\GraphQL\Language\Node\ScalarTypeDefinitionNode;
44
use Digia\GraphQL\Language\Node\ScalarTypeExtensionNode;
45
use Digia\GraphQL\Language\Node\SchemaDefinitionNode;
46
use Digia\GraphQL\Language\Node\SelectionSetNode;
47
use Digia\GraphQL\Language\Node\StringValueNode;
48
use Digia\GraphQL\Language\Node\TypeDefinitionNodeInterface;
49
use Digia\GraphQL\Language\Node\TypeExtensionNodeInterface;
50
use Digia\GraphQL\Language\Node\TypeNodeInterface;
51
use Digia\GraphQL\Language\Node\UnionTypeDefinitionNode;
52
use Digia\GraphQL\Language\Node\UnionTypeExtensionNode;
53
use Digia\GraphQL\Language\Node\ValueNodeInterface;
54
use Digia\GraphQL\Language\Node\VariableDefinitionNode;
55
use Digia\GraphQL\Language\Node\VariableNode;
56
use function Digia\GraphQL\Util\arraySome;
57
58
/** @noinspection PhpHierarchyChecksInspection */
59
60
/**
61
 * Class Parser
62
 * @package Digia\GraphQL\Language
63
 */
64
class Parser implements ParserInterface
65
{
66
    /**
67
     * @var LexerInterface
68
     */
69
    protected $lexer;
70
71
    /**
72
     * @param string $name
73
     * @param array  $arguments
74
     * @return mixed
75
     *
76
     * @throws InvariantException
77
     * @throws SyntaxErrorException
78
     */
79
    public function __call(string $name, array $arguments)
80
    {
81
        $lexCallback = \str_replace('parse', 'lex', $name);
82
83
        if (\method_exists($this, $lexCallback)) {
84
            return $this->parsePartial([$this, $lexCallback], ...$arguments);
0 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

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