Passed
Pull Request — master (#20)
by Christoffer
02:08
created

Parser::parseDirectiveLocations()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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