Passed
Pull Request — master (#188)
by Sebastian
02:49
created

TokenReader   F

Complexity

Total Complexity 103

Size/Duplication

Total Lines 769
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 103
dl 0
loc 769
rs 1.263
c 0
b 0
f 0

28 Methods

Rating   Name   Duplication   Size   Complexity  
A isSourceCharacter() 0 3 2
A readBracket() 0 12 2
A getLexer() 0 3 1
A readAt() 0 8 1
A readDollar() 0 9 1
B readBlockString() 0 51 6
A isTripleQuote() 0 4 3
C read() 0 61 24
A isLineTerminator() 0 3 2
A readParenthesis() 0 12 2
A readAmp() 0 9 1
A isEscapedTripleQuote() 0 9 4
A isUnderscore() 0 3 1
C readNumber() 0 61 10
A isAlphaNumeric() 0 3 3
A readBrace() 0 12 2
D readString() 0 102 17
A readName() 0 19 4
A readPipe() 0 9 1
A readBang() 0 9 1
A readDigits() 0 17 3
A isNumber() 0 3 2
A readEquals() 0 9 1
A setLexer() 0 4 1
A readComment() 0 22 2
A isLetter() 0 3 4
A readSpread() 0 9 1
A readColon() 0 9 1

How to fix   Complexity   

Complex Class

Complex classes like TokenReader often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use TokenReader, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Digia\GraphQL\Language;
4
5
use Digia\GraphQL\Error\SyntaxErrorException;
6
7
class TokenReader implements TokenReaderInterface
8
{
9
10
    /**
11
     * The lexer owning this token reader.
12
     *
13
     * @var LexerInterface
14
     */
15
    protected $lexer;
16
17
    /**
18
     * {@inheritdoc}
19
     */
20
    public function setLexer(LexerInterface $lexer)
21
    {
22
        $this->lexer = $lexer;
23
        return $this;
24
    }
25
26
    /**
27
     * {@inheritdoc}
28
     */
29
    public function getLexer(): LexerInterface
30
    {
31
        return $this->lexer;
32
    }
33
34
    /**
35
     * {@inheritdoc}
36
     */
37
    public function read(
38
      int $code,
39
      int $pos,
40
      int $line,
41
      int $col,
42
      Token $prev
43
    ): Token {
44
        switch ($code) {
45
            case 33: // !
46
                return $this->readBang($code, $pos, $line, $col, $prev);
47
            case 35: // #
48
                return $this->readComment($code, $pos, $line, $col, $prev);
49
            case 36: // $
50
                return $this->readDollar($code, $pos, $line, $col, $prev);
51
            case 38: // &
52
                return $this->readAmp($code, $pos, $line, $col, $prev);
53
            case 58: // :
54
                return $this->readColon($code, $pos, $line, $col, $prev);
55
            case 61: // =
56
                return $this->readEquals($code, $pos, $line, $col, $prev);
57
            case 64: // @
58
                return $this->readAt($code, $pos, $line, $col, $prev);
59
            case 124: // |
60
                return $this->readPipe($code, $pos, $line, $col, $prev);
61
            case 40:
62
            case 41: // ( or )~
63
                return $this->readParenthesis($code, $pos, $line, $col, $prev);
64
            case 91:
65
            case 93: // [ or ]
66
                return $this->readBracket($code, $pos, $line, $col, $prev);
67
            case 123:
68
            case 125: // { or }
69
                return $this->readBrace($code, $pos, $line, $col, $prev);
70
        }
71
72
        // Int:   -?(0|[1-9][0-9]*)
73
        // Float: -?(0|[1-9][0-9]*)(\.[0-9]+)?((E|e)(+|-)?[0-9]+)?
74
        if (isNumber($code)) {
75
            return $this->readNumber($code, $pos, $line, $col, $prev);
76
        }
77
78
        if ($this->isLetter($code) || $this->isUnderscore($code)) {
79
            return $this->readName($code, $pos, $line, $col, $prev);
80
        }
81
82
        $body = $this->lexer->getBody();
83
84
        // Spread: ...
85
        if ($code === 46 && charCodeAt($body,
86
            $pos + 1) === 46 && charCodeAt($body, $pos + 2) === 46) {
87
            return $this->readSpread($code, $pos, $line, $col, $prev);
88
        }
89
90
        // String: "([^"\\\u000A\u000D]|(\\(u[0-9a-fA-F]{4}|["\\/bfnrt])))*"
91
        if ($code === 34 && charCodeAt($body, $pos + 1) !== 34) {
92
            return $this->readString($code, $pos, $line, $col, $prev);
93
        }
94
95
        // Block String: """("?"?(\\"""|\\(?!=""")|[^"\\]))*"""
96
        if ($this->isTripleQuote($body, $code, $pos)) {
97
            return $this->readBlockString($code, $pos, $line, $col, $prev);
98
        }
0 ignored issues
show
Bug Best Practice introduced by
The function implicitly returns null when the if condition on line 96 is false. This is incompatible with the type-hinted return Digia\GraphQL\Language\Token. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
99
    }
100
101
    /**
102
     * @param int $code
103
     * @param int $pos
104
     * @param int $line
105
     * @param int $col
106
     * @param \Digia\GraphQL\Language\Token $prev
107
     *
108
     * @return \Digia\GraphQL\Language\Token
109
     */
110
    protected function readName(
111
      int $code,
0 ignored issues
show
Unused Code introduced by
The parameter $code is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

111
      /** @scrutinizer ignore-unused */ int $code,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
112
      int $pos,
113
      int $line,
114
      int $col,
115
      Token $prev
116
    ): Token {
117
        $body = $this->lexer->getBody();
118
        $bodyLength = mb_strlen($body);
119
        $start = $pos;
120
        $pos = $start + 1;
121
122
        while ($pos !== $bodyLength && ($code = charCodeAt($body,
123
            $pos)) !== null && $this->isAlphaNumeric($code)) {
124
            ++$pos;
125
        }
126
127
        return new Token(TokenKindEnum::NAME, $start, $pos, $line, $col, $prev,
128
          sliceString($body, $start, $pos));
129
    }
130
131
    /**
132
     * @param int $code
133
     * @param int $pos
134
     * @param int $line
135
     * @param int $col
136
     * @param \Digia\GraphQL\Language\Token $prev
137
     *
138
     * @return \Digia\GraphQL\Language\Token
139
     * @throws \Digia\GraphQL\Error\SyntaxErrorException
140
     */
141
    protected function readBlockString(
142
      int $code,
0 ignored issues
show
Unused Code introduced by
The parameter $code is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

142
      /** @scrutinizer ignore-unused */ int $code,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
143
      int $pos,
144
      int $line,
145
      int $col,
146
      Token $prev
147
    ): Token {
148
        $body = $this->lexer->getBody();
149
        $bodyLength = mb_strlen($body);
150
        $start = $pos;
151
        $pos = $start + 3;
152
        $chunkStart = $pos;
153
        $rawValue = '';
154
155
        while ($pos < $bodyLength && ($code = charCodeAt($body,
156
            $pos)) !== null) {
157
            // Closing Triple-Quote (""")
158
            if ($this->isTripleQuote($body, $code, $pos)) {
159
                $rawValue .= sliceString($body, $chunkStart, $pos);
160
161
                return new Token(
162
                  TokenKindEnum::BLOCK_STRING,
163
                  $start,
164
                  $pos + 3,
165
                  $line,
166
                  $col,
167
                  $prev,
168
                  blockStringValue($rawValue)
169
                );
170
            }
171
172
            if (isSourceCharacter($code)) {
173
                throw new SyntaxErrorException(
174
                  $this->lexer->getSource(),
175
                  $pos,
176
                  sprintf('Invalid character within String: %s',
177
                    printCharCode($code))
178
                );
179
            }
180
181
            if ($this->isEscapedTripleQuote($body, $code, $pos)) {
182
                $rawValue .= sliceString($body, $chunkStart, $pos) . '"""';
183
                $pos += 4;
184
                $chunkStart = $pos;
185
            } else {
186
                ++$pos;
187
            }
188
        }
189
190
        throw new SyntaxErrorException($this->lexer->getSource(), $pos,
191
          'Unterminated string.');
192
    }
193
194
    /**
195
     * @param int $code
196
     * @param int $pos
197
     * @param int $line
198
     * @param int $col
199
     * @param \Digia\GraphQL\Language\Token $prev
200
     *
201
     * @return \Digia\GraphQL\Language\Token
202
     * @throws \Digia\GraphQL\Error\SyntaxErrorException
203
     */
204
    protected function readNumber(
205
      int $code,
206
      int $pos,
207
      int $line,
208
      int $col,
209
      Token $prev
210
    ): Token {
211
        $body = $this->lexer->getBody();
212
        $start = $pos;
213
        $isFloat = false;
214
215
        if ($code === 45) {
216
            // -
217
            $code = charCodeAt($body, ++$pos);
218
        }
219
220
        if ($code === 48) {
221
            // 0
222
            $code = charCodeAt($body, ++$pos);
223
            if (isNumber($code)) {
224
                throw new SyntaxErrorException(
225
                  $this->lexer->getSource(),
226
                  $pos,
227
                  sprintf('Invalid number, unexpected digit after 0: %s.',
228
                    printCharCode($code))
229
                );
230
            }
231
        } else {
232
            $pos = $this->readDigits($code, $pos);
233
            $code = charCodeAt($body, $pos);
234
        }
235
236
        if ($code === 46) {
237
            // .
238
            $isFloat = true;
239
            $code = charCodeAt($body, ++$pos);
240
            $pos = $this->readDigits($code, $pos);
241
            $code = charCodeAt($body, $pos);
242
        }
243
244
        if ($code === 69 || $code === 101) {
245
            // e or E
246
            $isFloat = true;
247
            $code = charCodeAt($body, ++$pos);
248
249
            if ($code === 43 || $code === 45) {
250
                // + or -
251
                $code = charCodeAt($body, ++$pos);
252
            }
253
254
            $pos = $this->readDigits($code, $pos);
255
        }
256
257
        return new Token(
258
          $isFloat ? TokenKindEnum::FLOAT : TokenKindEnum::INT,
259
          $start,
260
          $pos,
261
          $line,
262
          $col,
263
          $prev,
264
          sliceString($body, $start, $pos)
265
        );
266
    }
267
268
    /**
269
     * @param int $code
270
     * @param int $pos
271
     * @param int $line
272
     * @param int $col
273
     * @param \Digia\GraphQL\Language\Token $prev
274
     *
275
     * @return \Digia\GraphQL\Language\Token
276
     * @throws \Digia\GraphQL\Error\SyntaxErrorException
277
     */
278
    protected function readString(
279
      int $code,
0 ignored issues
show
Unused Code introduced by
The parameter $code is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

279
      /** @scrutinizer ignore-unused */ int $code,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
280
      int $pos,
281
      int $line,
282
      int $col,
283
      Token $prev
284
    ): Token {
285
        $body = $this->lexer->getBody();
286
        $bodyLength = mb_strlen($body);
287
        $start = $pos;
288
        $pos = $start + 1;
289
        $chunkStart = $pos;
290
        $value = '';
291
292
        while ($pos < $bodyLength && ($code = charCodeAt($body,
293
            $pos)) !== null && !$this->isLineTerminator($code)) {
294
            // Closing Quote (")
295
            if ($code === 34) {
296
                $value .= sliceString($body, $chunkStart, $pos);
297
298
                return new Token(TokenKindEnum::STRING, $start, $pos + 1, $line,
299
                  $col, $prev, $value);
300
            }
301
302
            if ($this->isSourceCharacter($code)) {
303
                throw new SyntaxErrorException(
304
                  $this->lexer->getSource(),
305
                  $pos,
306
                  sprintf('Invalid character within String: %s',
307
                    printCharCode($code))
308
                );
309
            }
310
311
            ++$pos;
312
313
            if ($code === 92) {
314
                // \
315
                $value .= sliceString($body, $chunkStart, $pos + 1);
316
                $code = charCodeAt($body, $pos);
317
318
                switch ($code) {
319
                    case 34:
320
                        $value .= '"';
321
                        break;
322
                    case 47:
323
                        $value .= '/';
324
                        break;
325
                    case 92:
326
                        $value .= '\\';
327
                        break;
328
                    case 98:
329
                        $value .= '\b';
330
                        break;
331
                    case 102:
332
                        $value .= '\f';
333
                        break;
334
                    case 110:
335
                        $value .= '\n';
336
                        break;
337
                    case 114:
338
                        $value .= '\r';
339
                        break;
340
                    case 116:
341
                        $value .= '\t';
342
                        break;
343
                    case 117:
344
                        // u
345
                        $charCode = uniCharCode(
346
                          charCodeAt($body, $pos + 1),
347
                          charCodeAt($body, $pos + 2),
348
                          charCodeAt($body, $pos + 3),
349
                          charCodeAt($body, $pos + 4)
350
                        );
351
                        if ($charCode < 0) {
352
                            throw new SyntaxErrorException(
353
                              $this->lexer->getSource(),
354
                              $pos,
355
                              sprintf(
356
                                'Invalid character escape sequence: \\u%s',
357
                                sliceString($body, $pos + 1, $pos + 5)
358
                              )
359
                            );
360
                        }
361
                        $value .= chr($charCode);
0 ignored issues
show
Bug introduced by
$charCode of type string is incompatible with the type integer expected by parameter $ascii of chr(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

361
                        $value .= chr(/** @scrutinizer ignore-type */ $charCode);
Loading history...
362
                        $pos += 4;
363
                        break;
364
                    default:
365
                        throw new SyntaxErrorException(
366
                          $this->lexer->getSource(),
367
                          $pos,
368
                          sprintf('Invalid character escape sequence: \\%s',
369
                            chr($code))
370
                        );
371
                }
372
373
                ++$pos;
374
                $chunkStart = $pos;
375
            }
376
        }
377
378
        throw new SyntaxErrorException($this->lexer->getSource(), $pos,
379
          'Unterminated string.');
380
    }
381
382
    /**
383
     * @param int $code
384
     * @param int $pos
385
     * @param int $line
386
     * @param int $col
387
     * @param \Digia\GraphQL\Language\Token $prev
388
     *
389
     * @return \Digia\GraphQL\Language\Token
390
     */
391
    protected function readSpread(
392
      int $code,
0 ignored issues
show
Unused Code introduced by
The parameter $code is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

392
      /** @scrutinizer ignore-unused */ int $code,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
393
      int $pos,
394
      int $line,
395
      int $col,
396
      Token $prev
397
    ): Token {
398
        return new Token(TokenKindEnum::SPREAD, $pos, $pos + 3, $line, $col,
399
          $prev);
400
    }
401
402
    /**
403
     * @param int $code
404
     * @param int $pos
405
     * @param int $line
406
     * @param int $col
407
     * @param \Digia\GraphQL\Language\Token $prev
408
     *
409
     * @return \Digia\GraphQL\Language\Token
410
     */
411
    protected function readDollar(
412
      int $code,
0 ignored issues
show
Unused Code introduced by
The parameter $code is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

412
      /** @scrutinizer ignore-unused */ int $code,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
413
      int $pos,
414
      int $line,
415
      int $col,
416
      Token $prev
417
    ): Token {
418
        return new Token(TokenKindEnum::DOLLAR, $pos, $pos + 1, $line, $col,
419
          $prev);
420
    }
421
422
    /**
423
     * @param int $code
424
     * @param int $pos
425
     * @param int $line
426
     * @param int $col
427
     * @param \Digia\GraphQL\Language\Token $prev
428
     *
429
     * @return \Digia\GraphQL\Language\Token
430
     */
431
    protected function readPipe(
432
      int $code,
0 ignored issues
show
Unused Code introduced by
The parameter $code is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

432
      /** @scrutinizer ignore-unused */ int $code,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
433
      int $pos,
434
      int $line,
435
      int $col,
436
      Token $prev
437
    ): Token {
438
        return new Token(TokenKindEnum::PIPE, $pos, $pos + 1, $line, $col,
439
          $prev);
440
    }
441
442
    /**
443
     * @param int $code
444
     * @param int $pos
445
     * @param int $line
446
     * @param int $col
447
     * @param \Digia\GraphQL\Language\Token $prev
448
     *
449
     * @return \Digia\GraphQL\Language\Token
450
     */
451
    protected function readParenthesis(
452
      int $code,
453
      int $pos,
454
      int $line,
455
      int $col,
456
      Token $prev
457
    ): Token {
458
        return $code === 40
459
          ? new Token(TokenKindEnum::PAREN_L, $pos, $pos + 1, $line, $col,
460
            $prev)
461
          : new Token(TokenKindEnum::PAREN_R, $pos, $pos + 1, $line, $col,
462
            $prev);
463
    }
464
465
    /**
466
     * @param int $code
467
     * @param int $pos
468
     * @param int $line
469
     * @param int $col
470
     * @param \Digia\GraphQL\Language\Token $prev
471
     *
472
     * @return \Digia\GraphQL\Language\Token
473
     */
474
    protected function readEquals(
475
      int $code,
0 ignored issues
show
Unused Code introduced by
The parameter $code is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

475
      /** @scrutinizer ignore-unused */ int $code,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
476
      int $pos,
477
      int $line,
478
      int $col,
479
      Token $prev
480
    ): Token {
481
        return new Token(TokenKindEnum::EQUALS, $pos, $pos + 1, $line, $col,
482
          $prev);
483
    }
484
485
    /**
486
     * @param int $code
487
     * @param int $pos
488
     * @param int $line
489
     * @param int $col
490
     * @param \Digia\GraphQL\Language\Token $prev
491
     *
492
     * @return \Digia\GraphQL\Language\Token
493
     */
494
    protected function readAt(
495
      int $code,
0 ignored issues
show
Unused Code introduced by
The parameter $code is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

495
      /** @scrutinizer ignore-unused */ int $code,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
496
      int $pos,
497
      int $line,
498
      int $col,
499
      Token $prev
500
    ): Token {
501
        return new Token(TokenKindEnum::AT, $pos, $pos + 1, $line, $col, $prev);
502
    }
503
504
    /**
505
     * @param int $code
506
     * @param int $pos
507
     * @param int $line
508
     * @param int $col
509
     * @param \Digia\GraphQL\Language\Token $prev
510
     *
511
     * @return \Digia\GraphQL\Language\Token
512
     */
513
    protected function readComment(
514
      int $code,
0 ignored issues
show
Unused Code introduced by
The parameter $code is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

514
      /** @scrutinizer ignore-unused */ int $code,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
515
      int $pos,
516
      int $line,
517
      int $col,
518
      Token $prev
519
    ): Token {
520
        $body = $this->lexer->getBody();
521
        $start = $pos;
522
523
        do {
524
            $code = charCodeAt($body, ++$pos);
525
        } while ($code !== null && ($code > 0x001f || $code === 0x0009)); // SourceCharacter but not LineTerminator
526
527
        return new Token(
528
          TokenKindEnum::COMMENT,
529
          $start,
530
          $pos,
531
          $line,
532
          $col,
533
          $prev,
534
          sliceString($body, $start + 1, $pos)
535
        );
536
    }
537
538
    /**
539
     * @param int $code
540
     * @param int $pos
541
     * @param int $line
542
     * @param int $col
543
     * @param \Digia\GraphQL\Language\Token $prev
544
     *
545
     * @return \Digia\GraphQL\Language\Token
546
     */
547
    protected function readColon(
548
      int $code,
0 ignored issues
show
Unused Code introduced by
The parameter $code is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

548
      /** @scrutinizer ignore-unused */ int $code,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
549
      int $pos,
550
      int $line,
551
      int $col,
552
      Token $prev
553
    ): Token {
554
        return new Token(TokenKindEnum::COLON, $pos, $pos + 1, $line, $col,
555
          $prev);
556
    }
557
558
    /**
559
     * @param int $code
560
     * @param int $pos
561
     * @param int $line
562
     * @param int $col
563
     * @param \Digia\GraphQL\Language\Token $prev
564
     *
565
     * @return \Digia\GraphQL\Language\Token
566
     */
567
    protected function readAmp(
568
      int $code,
0 ignored issues
show
Unused Code introduced by
The parameter $code is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

568
      /** @scrutinizer ignore-unused */ int $code,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
569
      int $pos,
570
      int $line,
571
      int $col,
572
      Token $prev
573
    ): Token {
574
        return new Token(TokenKindEnum::AMP, $pos, $pos + 1, $line, $col,
575
          $prev);
576
    }
577
578
    /**
579
     * @param int $code
580
     * @param int $pos
581
     * @param int $line
582
     * @param int $col
583
     * @param \Digia\GraphQL\Language\Token $prev
584
     *
585
     * @return \Digia\GraphQL\Language\Token
586
     */
587
    protected function readBang(
588
      int $code,
0 ignored issues
show
Unused Code introduced by
The parameter $code is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

588
      /** @scrutinizer ignore-unused */ int $code,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
589
      int $pos,
590
      int $line,
591
      int $col,
592
      Token $prev
593
    ): Token {
594
        return new Token(TokenKindEnum::BANG, $pos, $pos + 1, $line, $col,
595
          $prev);
596
    }
597
598
    /**
599
     * @param int $code
600
     * @param int $pos
601
     * @param int $line
602
     * @param int $col
603
     * @param \Digia\GraphQL\Language\Token $prev
604
     *
605
     * @return \Digia\GraphQL\Language\Token
606
     */
607
    protected function readBrace(
608
      int $code,
609
      int $pos,
610
      int $line,
611
      int $col,
612
      Token $prev
613
    ): Token {
614
        return $code === 123
615
          ? new Token(TokenKindEnum::BRACE_L, $pos, $pos + 1, $line, $col,
616
            $prev)
617
          : new Token(TokenKindEnum::BRACE_R, $pos, $pos + 1, $line, $col,
618
            $prev);
619
    }
620
621
    /**
622
     * @param int $code
623
     * @param int $pos
624
     * @param int $line
625
     * @param int $col
626
     * @param \Digia\GraphQL\Language\Token $prev
627
     *
628
     * @return \Digia\GraphQL\Language\Token
629
     */
630
    protected function readBracket(
631
      int $code,
632
      int $pos,
633
      int $line,
634
      int $col,
635
      Token $prev
636
    ): Token {
637
        return $code === 91
638
          ? new Token(TokenKindEnum::BRACKET_L, $pos, $pos + 1, $line, $col,
639
            $prev)
640
          : new Token(TokenKindEnum::BRACKET_R, $pos, $pos + 1, $line, $col,
641
            $prev);
642
    }
643
644
    /**
645
     * @param int $code
646
     * @param int $pos
647
     *
648
     * @return int
649
     * @throws SyntaxErrorException
650
     */
651
    protected function readDigits(int $code, int $pos): int
652
    {
653
        $body = $this->lexer->getBody();
654
655
        if (isNumber($code)) {
656
            do {
657
                $code = charCodeAt($body, ++$pos);
658
            } while (isNumber($code));
659
660
            return $pos;
661
        }
662
663
        throw new SyntaxErrorException(
664
          $this->lexer->getSource(),
665
          $pos,
666
          sprintf('Invalid number, expected digit but got: %s',
667
            printCharCode($code))
668
        );
669
    }
670
671
    /**
672
     * @TODO Move to utils.
673
     *
674
     * @param int $code
675
     *
676
     * @return bool
677
     */
678
    protected function isLineTerminator(int $code): bool
679
    {
680
        return $code === 0x000a || $code === 0x000d;
681
    }
682
683
    /**
684
     * @TODO Move to utils.
685
     *
686
     * @param int $code
687
     *
688
     * @return bool
689
     */
690
    protected function isSourceCharacter(int $code): bool
691
    {
692
        return $code < 0x0020 && $code !== 0x0009;
693
    }
694
695
    /**
696
     * @TODO Move to utils.
697
     *
698
     * @param string $body
699
     * @param int $code
700
     * @param int $pos
701
     *
702
     * @return bool
703
     */
704
    protected function isTripleQuote(string $body, int $code, int $pos): bool
705
    {
706
        return $code === 34 && charCodeAt($body,
707
            $pos + 1) === 34 && charCodeAt($body, $pos + 2) === 34; // """
708
    }
709
710
    /**
711
     * @TODO Move to utils.
712
     *
713
     * @param string $body
714
     * @param int $code
715
     * @param int $pos
716
     *
717
     * @return bool
718
     */
719
    protected function isEscapedTripleQuote(
720
      string $body,
721
      int $code,
722
      int $pos
723
    ): bool {
724
        return $code === 92 &&
725
          charCodeAt($body, $pos + 1) === 34 &&
726
          charCodeAt($body, $pos + 2) === 34 &&
727
          charCodeAt($body, $pos + 3) === 34;
728
    }
729
730
    /**
731
     * @TODO Move to utils.
732
     *
733
     * @param int $code
734
     *
735
     * @return bool
736
     */
737
    protected function isLetter(int $code): bool
738
    {
739
        return ($code >= 65 && $code <= 90) || ($code >= 97 && $code <= 122); // a-z or A-Z
740
    }
741
742
    /**
743
     * @TODO Move to utils.
744
     *
745
     * @param int $code
746
     *
747
     * @return bool
748
     */
749
    protected function isNumber(int $code): bool
750
    {
751
        return $code >= 48 && $code <= 57; // 0-9
752
    }
753
754
    /**
755
     * @TODO Move to utils.
756
     *
757
     * @param int $code
758
     *
759
     * @return bool
760
     */
761
    protected function isUnderscore(int $code): bool
762
    {
763
        return $code === 95; // _
764
    }
765
766
    /**
767
     * @TODO Move to utils.
768
     *
769
     * @param int $code
770
     *
771
     * @return bool
772
     */
773
    protected function isAlphaNumeric(int $code): bool
774
    {
775
        return $this->isLetter($code) || $this->isNumber($code) || $this->isUnderscore($code);
776
    }
777
}
778