Passed
Pull Request — master (#196)
by Christoffer
02:49
created

Parser::parseDefinition()   D

Complexity

Conditions 17
Paths 17

Size

Total Lines 29
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 29
rs 4.9807
c 0
b 0
f 0
cc 17
eloc 22
nc 17
nop 1

How to fix   Complexity   

Long Method

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

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

Commonly applied refactorings include:

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