Completed
Pull Request — master (#188)
by Sebastian
06:47 queued 27s
created

TokenReader::read()   C

Complexity

Conditions 24
Paths 20

Size

Total Lines 61
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 61
rs 6.0693
c 0
b 0
f 0
cc 24
eloc 38
nc 20
nop 5

How to fix   Long Method    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
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
    }
24
25
    /**
26
     * {@inheritdoc}
27
     */
28
    public function getLexer(): LexerInterface
29
    {
30
        return $this->lexer;
31
    }
32
33
    /**
34
     * {@inheritdoc}
35
     */
36
    public function read(
37
      int $code,
38
      int $pos,
39
      int $line,
40
      int $col,
41
      Token $prev
42
    ): Token {
43
        switch ($code) {
44
            case 33: // !
45
                return $this->readBang($code, $pos, $line, $col, $prev);
46
            case 35: // #
47
                return $this->readComment($code, $pos, $line, $col, $prev);
48
            case 36: // $
49
                return $this->readDollar($code, $pos, $line, $col, $prev);
50
            case 38: // &
51
                return $this->readAmp($code, $pos, $line, $col, $prev);
52
            case 58: // :
53
                return $this->readColon($code, $pos, $line, $col, $prev);
54
            case 61: // =
55
                return $this->readEquals($code, $pos, $line, $col, $prev);
56
            case 64: // @
57
                return $this->readAt($code, $pos, $line, $col, $prev);
58
            case 124: // |
59
                return $this->readPipe($code, $pos, $line, $col, $prev);
60
            case 40:
61
            case 41: // ( or )~
62
                return $this->readParenthesis($code, $pos, $line, $col, $prev);
63
            case 91:
64
            case 93: // [ or ]
65
                return $this->readBracket($code, $pos, $line, $col, $prev);
66
            case 123:
67
            case 125: // { or }
68
                return $this->readBrace($code, $pos, $line, $col, $prev);
69
        }
70
71
        // Int:   -?(0|[1-9][0-9]*)
72
        // Float: -?(0|[1-9][0-9]*)(\.[0-9]+)?((E|e)(+|-)?[0-9]+)?
73
        if (isNumber($code)) {
74
            return $this->readNumber($code, $pos, $line, $col, $prev);
75
        }
76
77
        if ($this->isLetter($code) || $this->isUnderscore($code)) {
78
            return $this->readName($code, $pos, $line, $col, $prev);
79
        }
80
81
        $body = $this->lexer->getBody();
82
83
        // Spread: ...
84
        if ($code === 46 && charCodeAt($body,
85
            $pos + 1) === 46 && charCodeAt($body, $pos + 2) === 46) {
86
            return $this->readSpread($code, $pos, $line, $col, $prev);
87
        }
88
89
        // String: "([^"\\\u000A\u000D]|(\\(u[0-9a-fA-F]{4}|["\\/bfnrt])))*"
90
        if ($code === 34 && charCodeAt($body, $pos + 1) !== 34) {
91
            return $this->readString($code, $pos, $line, $col, $prev);
92
        }
93
94
        // Block String: """("?"?(\\"""|\\(?!=""")|[^"\\]))*"""
95
        if ($this->isTripleQuote($body, $code, $pos)) {
96
            return $this->readBlockString($code, $pos, $line, $col, $prev);
97
        }
0 ignored issues
show
Bug Best Practice introduced by
The function implicitly returns null when the if condition on line 95 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...
98
    }
99
100
    /**
101
     * @param int $code
102
     * @param int $pos
103
     * @param int $line
104
     * @param int $col
105
     * @param \Digia\GraphQL\Language\Token $prev
106
     *
107
     * @return \Digia\GraphQL\Language\Token
108
     */
109
    protected function readName(
110
      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

110
      /** @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...
111
      int $pos,
112
      int $line,
113
      int $col,
114
      Token $prev
115
    ): Token {
116
        $body = $this->lexer->getBody();
117
        $bodyLength = mb_strlen($body);
118
        $start = $pos;
119
        $pos = $start + 1;
120
121
        while ($pos !== $bodyLength && ($code = charCodeAt($body,
122
            $pos)) !== null && $this->isAlphaNumeric($code)) {
123
            ++$pos;
124
        }
125
126
        return new Token(TokenKindEnum::NAME, $start, $pos, $line, $col, $prev,
127
          sliceString($body, $start, $pos));
128
    }
129
130
    /**
131
     * @param int $code
132
     * @param int $pos
133
     * @param int $line
134
     * @param int $col
135
     * @param \Digia\GraphQL\Language\Token $prev
136
     *
137
     * @return \Digia\GraphQL\Language\Token
138
     * @throws \Digia\GraphQL\Error\SyntaxErrorException
139
     */
140
    protected function readBlockString(
141
      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

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

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

360
                        $value .= chr(/** @scrutinizer ignore-type */ $charCode);
Loading history...
361
                        $pos += 4;
362
                        break;
363
                    default:
364
                        throw new SyntaxErrorException(
365
                          $this->lexer->getSource(),
366
                          $pos,
367
                          sprintf('Invalid character escape sequence: \\%s',
368
                            chr($code))
369
                        );
370
                }
371
372
                ++$pos;
373
                $chunkStart = $pos;
374
            }
375
        }
376
377
        throw new SyntaxErrorException($this->lexer->getSource(), $pos,
378
          'Unterminated string.');
379
    }
380
381
    /**
382
     * @param int $code
383
     * @param int $pos
384
     * @param int $line
385
     * @param int $col
386
     * @param \Digia\GraphQL\Language\Token $prev
387
     *
388
     * @return \Digia\GraphQL\Language\Token
389
     */
390
    protected function readSpread(
391
      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

391
      /** @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...
392
      int $pos,
393
      int $line,
394
      int $col,
395
      Token $prev
396
    ): Token {
397
        return new Token(TokenKindEnum::SPREAD, $pos, $pos + 3, $line, $col,
398
          $prev);
399
    }
400
401
    /**
402
     * @param int $code
403
     * @param int $pos
404
     * @param int $line
405
     * @param int $col
406
     * @param \Digia\GraphQL\Language\Token $prev
407
     *
408
     * @return \Digia\GraphQL\Language\Token
409
     */
410
    protected function readDollar(
411
      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

411
      /** @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...
412
      int $pos,
413
      int $line,
414
      int $col,
415
      Token $prev
416
    ): Token {
417
        return new Token(TokenKindEnum::DOLLAR, $pos, $pos + 1, $line, $col,
418
          $prev);
419
    }
420
421
    /**
422
     * @param int $code
423
     * @param int $pos
424
     * @param int $line
425
     * @param int $col
426
     * @param \Digia\GraphQL\Language\Token $prev
427
     *
428
     * @return \Digia\GraphQL\Language\Token
429
     */
430
    protected function readPipe(
431
      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

431
      /** @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...
432
      int $pos,
433
      int $line,
434
      int $col,
435
      Token $prev
436
    ): Token {
437
        return new Token(TokenKindEnum::PIPE, $pos, $pos + 1, $line, $col,
438
          $prev);
439
    }
440
441
    /**
442
     * @param int $code
443
     * @param int $pos
444
     * @param int $line
445
     * @param int $col
446
     * @param \Digia\GraphQL\Language\Token $prev
447
     *
448
     * @return \Digia\GraphQL\Language\Token
449
     */
450
    protected function readParenthesis(
451
      int $code,
452
      int $pos,
453
      int $line,
454
      int $col,
455
      Token $prev
456
    ): Token {
457
        return $code === 40
458
          ? new Token(TokenKindEnum::PAREN_L, $pos, $pos + 1, $line, $col,
459
            $prev)
460
          : new Token(TokenKindEnum::PAREN_R, $pos, $pos + 1, $line, $col,
461
            $prev);
462
    }
463
464
    /**
465
     * @param int $code
466
     * @param int $pos
467
     * @param int $line
468
     * @param int $col
469
     * @param \Digia\GraphQL\Language\Token $prev
470
     *
471
     * @return \Digia\GraphQL\Language\Token
472
     */
473
    protected function readEquals(
474
      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

474
      /** @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...
475
      int $pos,
476
      int $line,
477
      int $col,
478
      Token $prev
479
    ): Token {
480
        return new Token(TokenKindEnum::EQUALS, $pos, $pos + 1, $line, $col,
481
          $prev);
482
    }
483
484
    /**
485
     * @param int $code
486
     * @param int $pos
487
     * @param int $line
488
     * @param int $col
489
     * @param \Digia\GraphQL\Language\Token $prev
490
     *
491
     * @return \Digia\GraphQL\Language\Token
492
     */
493
    protected function readAt(
494
      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

494
      /** @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...
495
      int $pos,
496
      int $line,
497
      int $col,
498
      Token $prev
499
    ): Token {
500
        return new Token(TokenKindEnum::AT, $pos, $pos + 1, $line, $col, $prev);
501
    }
502
503
    /**
504
     * @param int $code
505
     * @param int $pos
506
     * @param int $line
507
     * @param int $col
508
     * @param \Digia\GraphQL\Language\Token $prev
509
     *
510
     * @return \Digia\GraphQL\Language\Token
511
     */
512
    protected function readComment(
513
      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

513
      /** @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...
514
      int $pos,
515
      int $line,
516
      int $col,
517
      Token $prev
518
    ): Token {
519
        $body = $this->lexer->getBody();
520
        $start = $pos;
521
522
        do {
523
            $code = charCodeAt($body, ++$pos);
524
        } while ($code !== null && ($code > 0x001f || $code === 0x0009)); // SourceCharacter but not LineTerminator
525
526
        return new Token(
527
          TokenKindEnum::COMMENT,
528
          $start,
529
          $pos,
530
          $line,
531
          $col,
532
          $prev,
533
          sliceString($body, $start + 1, $pos)
534
        );
535
    }
536
537
    /**
538
     * @param int $code
539
     * @param int $pos
540
     * @param int $line
541
     * @param int $col
542
     * @param \Digia\GraphQL\Language\Token $prev
543
     *
544
     * @return \Digia\GraphQL\Language\Token
545
     */
546
    protected function readColon(
547
      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

547
      /** @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...
548
      int $pos,
549
      int $line,
550
      int $col,
551
      Token $prev
552
    ): Token {
553
        return new Token(TokenKindEnum::COLON, $pos, $pos + 1, $line, $col,
554
          $prev);
555
    }
556
557
    /**
558
     * @param int $code
559
     * @param int $pos
560
     * @param int $line
561
     * @param int $col
562
     * @param \Digia\GraphQL\Language\Token $prev
563
     *
564
     * @return \Digia\GraphQL\Language\Token
565
     */
566
    protected function readAmp(
567
      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

567
      /** @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...
568
      int $pos,
569
      int $line,
570
      int $col,
571
      Token $prev
572
    ): Token {
573
        return new Token(TokenKindEnum::AMP, $pos, $pos + 1, $line, $col,
574
          $prev);
575
    }
576
577
    /**
578
     * @param int $code
579
     * @param int $pos
580
     * @param int $line
581
     * @param int $col
582
     * @param \Digia\GraphQL\Language\Token $prev
583
     *
584
     * @return \Digia\GraphQL\Language\Token
585
     */
586
    protected function readBang(
587
      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

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