Passed
Pull Request — master (#184)
by Christoffer
02:44
created

TokenReader   F

Complexity

Total Complexity 82

Size/Duplication

Total Lines 519
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 82
dl 0
loc 519
rs 1.5789
c 0
b 0
f 0

20 Methods

Rating   Name   Duplication   Size   Complexity  
A getLexer() 0 3 1
A setLexer() 0 4 1
A readBracket() 0 5 2
A readAt() 0 3 1
A readDollar() 0 3 1
C readBlockString() 0 40 7
C read() 0 57 23
A readParenthesis() 0 5 2
A readAmp() 0 3 1
C readNumber() 0 55 10
A readBrace() 0 5 2
D readString() 0 87 17
A readPipe() 0 3 1
A readName() 0 10 4
A readDigits() 0 16 3
A readBang() 0 3 1
A readEquals() 0 3 1
A readSpread() 0 3 1
A readComment() 0 17 2
A readColon() 0 3 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
     * @throws SyntaxErrorException
37
     */
38
    public function read(string $body, int $bodyLength, int $code, int $pos, int $line, int $col, Token $prev): ?Token
39
    {
40
        switch ($code) {
41
            case 33: // !
42
                return $this->readBang($pos, $line, $col, $prev);
43
            case 35: // #
44
                return $this->readComment($pos, $line, $col, $prev);
45
            case 36: // $
46
                return $this->readDollar($pos, $line, $col, $prev);
47
            case 38: // &
48
                return $this->readAmp($pos, $line, $col, $prev);
49
            case 58: // :
50
                return $this->readColon($pos, $line, $col, $prev);
51
            case 61: // =
52
                return $this->readEquals($pos, $line, $col, $prev);
53
            case 64: // @
54
                return $this->readAt($pos, $line, $col, $prev);
55
            case 124: // |
56
                return $this->readPipe($pos, $line, $col, $prev);
57
            case 40:
58
            case 41: // ( or )~
59
                return $this->readParenthesis($code, $pos, $line, $col, $prev);
60
            case 91:
61
            case 93: // [ or ]
62
                return $this->readBracket($code, $pos, $line, $col, $prev);
63
            case 123:
64
            case 125: // { or }
65
                return $this->readBrace($code, $pos, $line, $col, $prev);
66
        }
67
68
        // Int:   -?(0|[1-9][0-9]*)
69
        // Float: -?(0|[1-9][0-9]*)(\.[0-9]+)?((E|e)(+|-)?[0-9]+)?
70
        if ($code === 45 || isNumber($code)) {
71
            return $this->readNumber($code, $pos, $line, $col, $prev);
72
        }
73
74
        // Name: [_A-Za-z][_0-9A-Za-z]*
75
        if (isAlphaNumeric($code)) {
76
            return $this->readName($body, $bodyLength, $pos, $line, $col, $prev);
77
        }
78
79
        // Spread: ...
80
        if ($bodyLength >= 3 && isSpread($body, $code, $pos)) {
81
            return $this->readSpread($pos, $line, $col, $prev);
82
        }
83
84
        // String: "([^"\\\u000A\u000D]|(\\(u[0-9a-fA-F]{4}|["\\/bfnrt])))*"
85
        if (isString($body, $code, $pos)) {
86
            return $this->readString($body, $bodyLength, $pos, $line, $col, $prev);
87
        }
88
89
        // Block String: """("?"?(\\"""|\\(?!=""")|[^"\\]))*"""
90
        if ($bodyLength >= 3 && isTripleQuote($body, $code, $pos)) {
91
            return $this->readBlockString($body, $bodyLength, $pos, $line, $col, $prev);
92
        }
93
94
        return null;
95
    }
96
97
    /**
98
     * @param string $body
99
     * @param int    $bodyLength
100
     * @param int    $pos
101
     * @param int    $line
102
     * @param int    $col
103
     * @param Token  $prev
104
     * @return Token
105
     */
106
    protected function readName(string $body, int $bodyLength, int $pos, int $line, int $col, Token $prev): Token
107
    {
108
        $start = $pos;
109
        $pos   = $start + 1;
110
111
        while ($pos !== $bodyLength && ($code = charCodeAt($body, $pos)) !== null && isAlphaNumeric($code)) {
112
            ++$pos;
113
        }
114
115
        return new Token(TokenKindEnum::NAME, $start, $pos, $line, $col, $prev, sliceString($body, $start, $pos));
116
    }
117
118
    /**
119
     * @param string $body
120
     * @param int    $bodyLength
121
     * @param int    $pos
122
     * @param int    $line
123
     * @param int    $col
124
     * @param Token  $prev
125
     * @return Token
126
     * @throws SyntaxErrorException
127
     */
128
    protected function readBlockString(string $body, int $bodyLength, int $pos, int $line, int $col, Token $prev): Token
129
    {
130
        $start      = $pos;
131
        $pos        = $start + 3;
132
        $chunkStart = $pos;
133
        $rawValue   = '';
134
135
        while ($pos < $bodyLength && ($code = charCodeAt($body, $pos)) !== null) {
136
            // Closing Triple-Quote (""")
137
            if (isTripleQuote($body, $code, $pos)) {
138
                $rawValue .= sliceString($body, $chunkStart, $pos);
139
                return new Token(
140
                    TokenKindEnum::BLOCK_STRING,
141
                    $start,
142
                    $pos + 3,
143
                    $line,
144
                    $col,
145
                    $prev,
146
                    blockStringValue($rawValue)
147
                );
148
            }
149
150
            if (isSourceCharacter($code) && !isLineTerminator($code)) {
151
                throw new SyntaxErrorException(
152
                    $this->lexer->getSource(),
153
                    $pos,
154
                    \sprintf('Invalid character within String: %s', printCharCode($code))
155
                );
156
            }
157
158
            if (isEscapedTripleQuote($body, $code, $pos)) {
159
                $rawValue   .= sliceString($body, $chunkStart, $pos) . '"""';
160
                $pos        += 4;
161
                $chunkStart = $pos;
162
            } else {
163
                ++$pos;
164
            }
165
        }
166
167
        throw new SyntaxErrorException($this->lexer->getSource(), $pos, 'Unterminated string.');
168
    }
169
170
    /**
171
     * @param int   $code
172
     * @param int   $pos
173
     * @param int   $line
174
     * @param int   $col
175
     * @param Token $prev
176
     * @return Token
177
     * @throws SyntaxErrorException
178
     */
179
    protected function readNumber(int $code, int $pos, int $line, int $col, Token $prev): Token
180
    {
181
        $body    = $this->lexer->getBody();
182
        $start   = $pos;
183
        $isFloat = false;
184
185
        if ($code === 45) {
186
            // -
187
            $code = charCodeAt($body, ++$pos);
188
        }
189
190
        if ($code === 48) {
191
            // 0
192
            $code = charCodeAt($body, ++$pos);
193
            if (isNumber($code)) {
194
                throw new SyntaxErrorException(
195
                    $this->lexer->getSource(),
196
                    $pos,
197
                    \sprintf('Invalid number, unexpected digit after 0: %s.', printCharCode($code))
198
                );
199
            }
200
        } else {
201
            $pos  = $this->readDigits($code, $pos);
202
            $code = charCodeAt($body, $pos);
203
        }
204
205
        if ($code === 46) {
206
            // .
207
            $isFloat = true;
208
            $code    = charCodeAt($body, ++$pos);
209
            $pos     = $this->readDigits($code, $pos);
210
            $code    = charCodeAt($body, $pos);
211
        }
212
213
        if ($code === 69 || $code === 101) {
214
            // e or E
215
            $isFloat = true;
216
            $code    = charCodeAt($body, ++$pos);
217
218
            if ($code === 43 || $code === 45) {
219
                // + or -
220
                $code = charCodeAt($body, ++$pos);
221
            }
222
223
            $pos = $this->readDigits($code, $pos);
224
        }
225
226
        return new Token(
227
            $isFloat ? TokenKindEnum::FLOAT : TokenKindEnum::INT,
228
            $start,
229
            $pos,
230
            $line,
231
            $col,
232
            $prev,
233
            sliceString($body, $start, $pos)
234
        );
235
    }
236
237
    /**
238
     * @param string $body
239
     * @param int    $bodyLength
240
     * @param int    $pos
241
     * @param int    $line
242
     * @param int    $col
243
     * @param Token  $prev
244
     * @return Token
245
     * @throws SyntaxErrorException
246
     */
247
    protected function readString(string $body, int $bodyLength, int $pos, int $line, int $col, Token $prev): Token
248
    {
249
        $start      = $pos;
250
        $pos        = $start + 1;
251
        $chunkStart = $pos;
252
        $value      = '';
253
        while ($pos < $bodyLength && ($code = charCodeAt($body, $pos)) !== null && !isLineTerminator($code)) {
254
            // Closing Quote (")
255
            if ($code === 34) {
256
                $value .= sliceString($body, $chunkStart, $pos);
257
                return new Token(TokenKindEnum::STRING, $start, $pos + 1, $line, $col, $prev, $value);
258
            }
259
260
            if (isSourceCharacter($code)) {
261
                throw new SyntaxErrorException(
262
                    $this->lexer->getSource(),
263
                    $pos,
264
                    \sprintf('Invalid character within String: %s', printCharCode($code))
265
                );
266
            }
267
268
            ++$pos;
269
270
            if ($code === 92) {
271
                // \
272
                $value .= sliceString($body, $chunkStart, $pos - 1);
273
                $code  = charCodeAt($body, $pos);
274
275
                switch ($code) {
276
                    case 34:
277
                        $value .= '"';
278
                        break;
279
                    case 47:
280
                        $value .= '/';
281
                        break;
282
                    case 92:
283
                        $value .= '\\';
284
                        break;
285
                    case 98:
286
                        $value .= '\b';
287
                        break;
288
                    case 102:
289
                        $value .= '\f';
290
                        break;
291
                    case 110:
292
                        $value .= '\n';
293
                        break;
294
                    case 114:
295
                        $value .= '\r';
296
                        break;
297
                    case 116:
298
                        $value .= '\t';
299
                        break;
300
                    case 117:
301
                        // u
302
                        $unicodeString = sliceString($body, $pos - 1, $pos + 5);
303
                        $charCode      = uniCharCode(
304
                            charCodeAt($body, $pos + 1),
305
                            charCodeAt($body, $pos + 2),
306
                            charCodeAt($body, $pos + 3),
307
                            charCodeAt($body, $pos + 4)
308
                        );
309
                        if ($charCode < 0) {
310
                            throw new SyntaxErrorException(
311
                                $this->lexer->getSource(),
312
                                $pos,
313
                                \sprintf('Invalid character escape sequence: %s.', $unicodeString)
314
                            );
315
                        }
316
                        $value .= $unicodeString;
317
                        $pos   += 4;
318
                        break;
319
                    default:
320
                        throw new SyntaxErrorException(
321
                            $this->lexer->getSource(),
322
                            $pos,
323
                            \sprintf('Invalid character escape sequence: \\%s.', \chr($code))
324
                        );
325
                }
326
327
                ++$pos;
328
329
                $chunkStart = $pos;
330
            }
331
        }
332
333
        throw new SyntaxErrorException($this->lexer->getSource(), $pos, 'Unterminated string.');
334
    }
335
336
    /**
337
     * @param int   $pos
338
     * @param int   $line
339
     * @param int   $col
340
     * @param Token $prev
341
     * @return Token
342
     */
343
    protected function readSpread(int $pos, int $line, int $col, Token $prev): Token
344
    {
345
        return new Token(TokenKindEnum::SPREAD, $pos, $pos + 3, $line, $col, $prev);
346
    }
347
348
    /**
349
     * @param int   $pos
350
     * @param int   $line
351
     * @param int   $col
352
     * @param Token $prev
353
     * @return Token
354
     */
355
    protected function readDollar(int $pos, int $line, int $col, Token $prev): Token
356
    {
357
        return new Token(TokenKindEnum::DOLLAR, $pos, $pos + 1, $line, $col, $prev);
358
    }
359
360
    /**
361
     * @param int   $pos
362
     * @param int   $line
363
     * @param int   $col
364
     * @param Token $prev
365
     * @return Token
366
     */
367
    protected function readPipe(int $pos, int $line, int $col, Token $prev): Token
368
    {
369
        return new Token(TokenKindEnum::PIPE, $pos, $pos + 1, $line, $col, $prev);
370
    }
371
372
    /**
373
     * @param int   $code
374
     * @param int   $pos
375
     * @param int   $line
376
     * @param int   $col
377
     * @param Token $prev
378
     * @return Token
379
     */
380
    protected function readParenthesis(int $code, int $pos, int $line, int $col, Token $prev): Token
381
    {
382
        return $code === 40
383
            ? new Token(TokenKindEnum::PAREN_L, $pos, $pos + 1, $line, $col, $prev)
384
            : new Token(TokenKindEnum::PAREN_R, $pos, $pos + 1, $line, $col, $prev);
385
    }
386
387
    /**
388
     * @param int   $pos
389
     * @param int   $line
390
     * @param int   $col
391
     * @param Token $prev
392
     * @return Token
393
     */
394
    protected function readEquals(int $pos, int $line, int $col, Token $prev): Token
395
    {
396
        return new Token(TokenKindEnum::EQUALS, $pos, $pos + 1, $line, $col, $prev);
397
    }
398
399
    /**
400
     * @param int   $pos
401
     * @param int   $line
402
     * @param int   $col
403
     * @param Token $prev
404
     * @return Token
405
     */
406
    protected function readAt(int $pos, int $line, int $col, Token $prev): Token
407
    {
408
        return new Token(TokenKindEnum::AT, $pos, $pos + 1, $line, $col, $prev);
409
    }
410
411
    /**
412
     * @param int   $pos
413
     * @param int   $line
414
     * @param int   $col
415
     * @param Token $prev
416
     * @return Token
417
     */
418
    protected function readComment(int $pos, int $line, int $col, Token $prev): Token
419
    {
420
        $body  = $this->lexer->getBody();
421
        $start = $pos;
422
423
        do {
424
            $code = charCodeAt($body, ++$pos);
425
        } while ($code !== null && ($code > 0x001f || $code === 0x0009)); // SourceCharacter but not LineTerminator
426
427
        return new Token(
428
            TokenKindEnum::COMMENT,
429
            $start,
430
            $pos,
431
            $line,
432
            $col,
433
            $prev,
434
            sliceString($body, $start + 1, $pos)
435
        );
436
    }
437
438
    /**
439
     * @param int   $pos
440
     * @param int   $line
441
     * @param int   $col
442
     * @param Token $prev
443
     * @return Token
444
     */
445
    protected function readColon(int $pos, int $line, int $col, Token $prev): Token
446
    {
447
        return new Token(TokenKindEnum::COLON, $pos, $pos + 1, $line, $col, $prev);
448
    }
449
450
    /**
451
     * @param int   $pos
452
     * @param int   $line
453
     * @param int   $col
454
     * @param Token $prev
455
     * @return Token
456
     */
457
    protected function readAmp(int $pos, int $line, int $col, Token $prev): Token
458
    {
459
        return new Token(TokenKindEnum::AMP, $pos, $pos + 1, $line, $col, $prev);
460
    }
461
462
    /**
463
     * @param int   $pos
464
     * @param int   $line
465
     * @param int   $col
466
     * @param Token $prev
467
     * @return Token
468
     */
469
    protected function readBang(int $pos, int $line, int $col, Token $prev): Token
470
    {
471
        return new Token(TokenKindEnum::BANG, $pos, $pos + 1, $line, $col, $prev);
472
    }
473
474
    /**
475
     * @param int   $code
476
     * @param int   $pos
477
     * @param int   $line
478
     * @param int   $col
479
     * @param Token $prev
480
     * @return Token
481
     */
482
    protected function readBrace(int $code, int $pos, int $line, int $col, Token $prev): Token
483
    {
484
        return $code === 123
485
            ? new Token(TokenKindEnum::BRACE_L, $pos, $pos + 1, $line, $col, $prev)
486
            : new Token(TokenKindEnum::BRACE_R, $pos, $pos + 1, $line, $col, $prev);
487
    }
488
489
    /**
490
     * @param int   $code
491
     * @param int   $pos
492
     * @param int   $line
493
     * @param int   $col
494
     * @param Token $prev
495
     * @return Token
496
     */
497
    protected function readBracket(int $code, int $pos, int $line, int $col, Token $prev): Token
498
    {
499
        return $code === 91
500
            ? new Token(TokenKindEnum::BRACKET_L, $pos, $pos + 1, $line, $col, $prev)
501
            : new Token(TokenKindEnum::BRACKET_R, $pos, $pos + 1, $line, $col, $prev);
502
    }
503
504
    /**
505
     * @param int $code
506
     * @param int $pos
507
     * @return int
508
     * @throws SyntaxErrorException
509
     */
510
    protected function readDigits(int $code, int $pos): int
511
    {
512
        $body = $this->lexer->getBody();
513
514
        if (isNumber($code)) {
515
            do {
516
                $code = charCodeAt($body, ++$pos);
517
            } while (isNumber($code));
518
519
            return $pos;
520
        }
521
522
        throw new SyntaxErrorException(
523
            $this->lexer->getSource(),
524
            $pos,
525
            sprintf('Invalid number, expected digit but got: %s.', printCharCode($code))
526
        );
527
    }
528
}
529