Completed
Pull Request — master (#80)
by Christoffer
02:18
created

Parser::parseDefinition()   D

Complexity

Conditions 17
Paths 17

Size

Total Lines 31
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

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

How to fix   Complexity   

Long Method

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

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

Commonly applied refactorings include:

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