Completed
Push — master ( 37d445...49be16 )
by Alexandr
9s
created

Tokenizer::scanString()   C

Complexity

Conditions 14
Paths 13

Size

Total Lines 56
Code Lines 43

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 38
CRAP Score 14.0245

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 56
ccs 38
cts 40
cp 0.95
rs 6.6598
cc 14
eloc 43
nc 13
nop 0
crap 14.0245

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
 * Date: 23.11.15
4
 *
5
 * @author Portey Vasil <[email protected]>
6
 */
7
8
namespace Youshido\GraphQL\Parser;
9
10
use Youshido\GraphQL\Exception\Parser\SyntaxErrorException;
11
12
class Tokenizer
13
{
14
    protected $source;
15
    protected $pos = 0;
16
    protected $line = 1;
17
    protected $lineStart = 0;
18
19
    /** @var  Token */
20
    protected $lookAhead;
21
22 107
    protected function initTokenizer($source)
23
    {
24 107
        $this->source    = $source;
25 107
        $this->lookAhead = $this->next();
26 107
    }
27
28
    /**
29
     * @return Token
30
     */
31 107
    protected function next()
32
    {
33 107
        $this->skipWhitespace();
34
35 107
        return $this->scan();
36
    }
37
38 107
    protected function skipWhitespace()
39
    {
40 107
        while ($this->pos < strlen($this->source)) {
41 106
            $ch = $this->source[$this->pos];
42 106
            if ($ch === ' ' || $ch === "\t" || $ch === ',') {
43 103
                $this->pos++;
44 106
            } elseif ($ch === '#') {
45 1
                $this->pos++;
46
                while (
47 1
                    $this->pos < strlen($this->source) &&
48 1
                    ($code = ord($this->source[$this->pos])) &&
49 1
                    $code !== 10 && $code !== 13 && $code !== 0x2028 && $code !== 0x2029
50
                ) {
51 1
                    $this->pos++;
52
                }
53 106
            } elseif ($ch === "\r") {
54 1
                $this->pos++;
55 1
                if ($this->source[$this->pos] === "\n") {
56 1
                    $this->pos++;
57
                }
58 1
                $this->line++;
59 1
                $this->lineStart = $this->pos;
60 106
            } elseif ($ch === "\n") {
61 40
                $this->pos++;
62 40
                $this->line++;
63 40
                $this->lineStart = $this->pos;
64
            } else {
65 106
                break;
66
            }
67
        }
68 107
    }
69
70
    /**
71
     * @return Token
72
     *
73
     * @throws SyntaxErrorException
74
     */
75 107
    protected function scan()
76
    {
77 107
        if ($this->pos >= strlen($this->source)) {
78 97
            return new Token(Token::TYPE_END, $this->getLine(), $this->getColumn());
79
        }
80
81 106
        $ch = $this->source[$this->pos];
82
        switch ($ch) {
83 106
            case Token::TYPE_LPAREN:
84 61
                ++$this->pos;
85
86 61
                return new Token(Token::TYPE_LPAREN, $this->getLine(), $this->getColumn());
87 106
            case Token::TYPE_RPAREN:
88 54
                ++$this->pos;
89
90 54
                return new Token(Token::TYPE_RPAREN, $this->getLine(), $this->getColumn());
91 106
            case Token::TYPE_LBRACE:
92 104
                ++$this->pos;
93
94 104
                return new Token(Token::TYPE_LBRACE, $this->getLine(), $this->getColumn());
95 106
            case Token::TYPE_RBRACE:
96 97
                ++$this->pos;
97
98 97
                return new Token(Token::TYPE_RBRACE, $this->getLine(), $this->getColumn());
99 105
            case Token::TYPE_COMMA:
100
                ++$this->pos;
101
102
                return new Token(Token::TYPE_COMMA, $this->getLine(), $this->getColumn());
103 105
            case Token::TYPE_LSQUARE_BRACE:
104 16
                ++$this->pos;
105
106 16
                return new Token(Token::TYPE_LSQUARE_BRACE, $this->getLine(), $this->getColumn());
107 105
            case Token::TYPE_RSQUARE_BRACE:
108 15
                ++$this->pos;
109
110 15
                return new Token(Token::TYPE_RSQUARE_BRACE, $this->getLine(), $this->getColumn());
111 105
            case Token::TYPE_REQUIRED:
112 3
                ++$this->pos;
113
114 3
                return new Token(Token::TYPE_REQUIRED, $this->getLine(), $this->getColumn());
115 105
            case Token::TYPE_AT:
116
                ++$this->pos;
117
118
                return new Token(Token::TYPE_AT, $this->getLine(), $this->getColumn());
119 105
            case Token::TYPE_COLON:
120 65
                ++$this->pos;
121
122 65
                return new Token(Token::TYPE_COLON, $this->getLine(), $this->getColumn());
123
124 105
            case Token::TYPE_EQUAL:
125 1
                ++$this->pos;
126
127 1
                return new Token(Token::TYPE_EQUAL, $this->getLine(), $this->getColumn());
128
129 105
            case Token::TYPE_POINT:
130 16
                if ($this->checkFragment()) {
131 15
                    return new Token(Token::TYPE_FRAGMENT_REFERENCE, $this->getLine(), $this->getColumn());
132
                }
133
134 1
                return new Token(Token::TYPE_POINT, $this->getLine(), $this->getColumn());
135
136
137 105
            case Token::TYPE_VARIABLE:
138 13
                ++$this->pos;
139
140 13
                return new Token(Token::TYPE_VARIABLE, $this->getLine(), $this->getColumn());
141
        }
142
143 105
        if ($ch === '_' || ('a' <= $ch && $ch <= 'z') || ('A' <= $ch && $ch <= 'Z')) {
144 104
            return $this->scanWord();
145
        }
146
147 43
        if ($ch === '-' || ('0' <= $ch && $ch <= '9')) {
148 20
            return $this->scanNumber();
149
        }
150
151 29
        if ($ch === '"') {
152 29
            return $this->scanString();
153
        }
154
155 1
        throw $this->createException('Can\t recognize token type');
156
    }
157
158 16
    protected function checkFragment()
159
    {
160 16
        $this->pos++;
161 16
        $ch = $this->source[$this->pos];
162
163 16
        $this->pos++;
164 16
        $nextCh = $this->source[$this->pos];
165
166 16
        $isset = $ch == Token::TYPE_POINT && $nextCh == Token::TYPE_POINT;
167
168 16
        if ($isset) {
169 15
            $this->pos++;
170
171 15
            return true;
172
        }
173
174 1
        return false;
175
    }
176
177 104
    protected function scanWord()
178
    {
179 104
        $start = $this->pos;
180 104
        $this->pos++;
181
182 104
        while ($this->pos < strlen($this->source)) {
183 104
            $ch = $this->source[$this->pos];
184
185 104
            if ($ch === '_' || $ch === '$' || ('a' <= $ch && $ch <= 'z') || ('A' <= $ch && $ch <= 'Z') || ('0' <= $ch && $ch <= '9')) {
186 104
                $this->pos++;
187
            } else {
188 103
                break;
189
            }
190
        }
191
192 104
        $value = substr($this->source, $start, $this->pos - $start);
193
194 104
        return new Token($this->getKeyword($value), $this->getLine(), $this->getColumn(), $value);
195
    }
196
197 104
    protected function getKeyword($name)
198
    {
199
        switch ($name) {
200 104
            case 'null':
201 9
                return Token::TYPE_NULL;
202
203 104
            case 'true':
204 5
                return Token::TYPE_TRUE;
205
206 104
            case 'false':
207 1
                return Token::TYPE_FALSE;
208
209 104
            case 'query':
210 32
                return Token::TYPE_QUERY;
211
212 103
            case 'fragment':
213 11
                return Token::TYPE_FRAGMENT;
214
215 103
            case 'mutation':
216 17
                return Token::TYPE_MUTATION;
217
218 103
            case 'on':
219 13
                return Token::TYPE_ON;
220
        }
221
222 103
        return Token::TYPE_IDENTIFIER;
223
    }
224
225 103
    protected function expect($type)
226
    {
227 103
        if ($this->match($type)) {
228 103
            return $this->lex();
229
        }
230
231 2
        throw $this->createUnexpectedException($this->peek());
232
    }
233
234 104
    protected function match($type)
235
    {
236 104
        return $this->peek()->getType() === $type;
237
    }
238
239 20
    protected function scanNumber()
240
    {
241 20
        $start = $this->pos;
242 20
        if ($this->source[$this->pos] === '-') {
243 2
            ++$this->pos;
244
        }
245
246 20
        $this->skipInteger();
247
248 20
        if (isset($this->source[$this->pos]) && $this->source[$this->pos] === '.') {
249 1
            $this->pos++;
250 1
            $this->skipInteger();
251
        }
252
253 20
        $value = substr($this->source, $start, $this->pos - $start);
254
255 20
        if (strpos($value, '.') === false) {
256 20
            $value = (int) $value;
257
        } else {
258 1
            $value = (float) $value;
259
        }
260
261 20
        return new Token(Token::TYPE_NUMBER, $this->getLine(), $this->getColumn(), $value);
262
    }
263
264 20
    protected function skipInteger()
265
    {
266 20
        while ($this->pos < strlen($this->source)) {
267 20
            $ch = $this->source[$this->pos];
268 20
            if ('0' <= $ch && $ch <= '9') {
269 20
                $this->pos++;
270
            } else {
271 19
                break;
272
            }
273
        }
274 20
    }
275
276 10
    protected function createException($message)
277
    {
278 10
        return new SyntaxErrorException(sprintf('%s', $message), $this->getLocation());
279
    }
280
281 12
    protected function getLocation()
282
    {
283 12
        return new Location($this->getLine(), $this->getColumn());
284
    }
285
286 107
    protected function getColumn()
287
    {
288 107
        return $this->pos - $this->lineStart;
289
    }
290
291 107
    protected function getLine()
292
    {
293 107
        return $this->line;
294
    }
295
296
    /*
297
  		http://facebook.github.io/graphql/October2016/#sec-String-Value
298
    */
299 29
    protected function scanString()
300
    {
301 29
        $len = strlen($this->source);
302 29
        $this->pos++;
303
304 29
        $value = '';
305 29
        while ($this->pos < $len) {
306 29
            $ch = $this->source[$this->pos];
307 29
            if ($ch === '"') {
308 28
                $token = new Token(Token::TYPE_STRING, $this->getLine(), $this->getColumn(), $value);
309 28
                $this->pos++;
310
311 28
                return $token;
312
            }
313
            
314 29
            if($ch === '\\' && ($this->pos < ($len - 1))) {
315 1
                $this->pos++;
316 1
                $ch = $this->source[$this->pos];
317
                switch($ch) {
318 1
                    case '"':
319 1
                    case '\\':
320 1
                    case '/':
321 1
                        break;
322 1
                    case 'b':
323 1
                        $ch = sprintf("%c", 8);
324 1
                        break;
325 1
                    case 'f':
326 1
                        $ch = "\f";
327 1
                        break;
328 1
                    case 'n':
329 1
                        $ch = "\n";
330 1
                        break;
331 1
                    case 'r':
332 1
                        $ch = "\r";
333 1
                        break;
334 1
                    case 'u':
335 1
                        $codepoint = substr($this->source, $this->pos + 1, 4);
336 1
                        if( !preg_match('/[0-9A-Fa-f]{4}/', $codepoint)) {
337
                            throw $this->createException(sprintf('Invalid string unicode escape sequece "%s"', $codepoint));
338
                        }
339 1
                        $ch = html_entity_decode("&#x{$codepoint};", ENT_QUOTES, 'UTF-8');
340 1
                        $this->pos += 4;
341 1
                        break;
342
                    default:
343
                        throw $this->createException(sprintf('Unexpected string escaped character "%s"', $ch));
344
                        break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
345
346
                }
347
            } 
348
            
349 29
            $value .= $ch;
350 29
            $this->pos++;
351
        }
352
353 1
        throw $this->createUnexpectedTokenTypeException(Token::TYPE_END);
354
    }
355
356 106
    protected function end()
357
    {
358 106
        return $this->lookAhead->getType() === Token::TYPE_END;
359
    }
360
361 105
    protected function peek()
362
    {
363 105
        return $this->lookAhead;
364
    }
365
366 104
    protected function lex()
367
    {
368 104
        $prev            = $this->lookAhead;
369 104
        $this->lookAhead = $this->next();
370
371 104
        return $prev;
372
    }
373
374 5
    protected function createUnexpectedException(Token $token)
375
    {
376 5
        return $this->createUnexpectedTokenTypeException($token->getType());
377
    }
378
379 9
    protected function createUnexpectedTokenTypeException($tokenType)
380
    {
381 9
        return $this->createException(sprintf('Unexpected token "%s"', Token::tokenName($tokenType)));
382
    }
383
}
384