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

Parser::parseFragment()   B

Complexity

Conditions 4
Paths 3

Size

Total Lines 27
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 17
nc 3
nop 0
dl 0
loc 27
rs 8.5806
c 0
b 0
f 0

1 Method

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