Completed
Push — master ( a7480b...987705 )
by Portey
03:46
created

Tokenizer   D

Complexity

Total Complexity 87

Size/Duplication

Total Lines 331
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 89.8%

Importance

Changes 10
Bugs 0 Features 3
Metric Value
wmc 87
c 10
b 0
f 3
lcom 1
cbo 2
dl 0
loc 331
ccs 176
cts 196
cp 0.898
rs 4.8717

17 Methods

Rating   Name   Duplication   Size   Complexity  
A scanNumber() 0 22 3
A createError() 0 4 1
A getColumn() 0 4 1
A end() 0 4 1
A peek() 0 4 1
A setSource() 0 5 1
A next() 0 15 1
C skipWhitespace() 0 31 14
D scan() 0 80 25
A checkFragment() 0 18 3
B scanWord() 0 19 10
C getKeyword() 0 27 8
B skipInteger() 0 17 5
A createIllegal() 0 6 2
B scanString() 0 23 5
A lex() 0 7 1
B createUnexpected() 0 15 5

How to fix   Complexity   

Complex Class

Complex classes like Tokenizer 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 Tokenizer, and based on these observations, apply Extract Interface, too.

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