Completed
Push — master ( 5bf303...8cd23b )
by Portey
03:14
created

Tokenizer   D

Complexity

Total Complexity 86

Size/Duplication

Total Lines 329
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 88.83%

Importance

Changes 12
Bugs 2 Features 3
Metric Value
wmc 86
c 12
b 2
f 3
lcom 1
cbo 2
dl 0
loc 329
ccs 175
cts 197
cp 0.8883
rs 4.8717

17 Methods

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