Completed
Push — master ( 9e4cdb...1641ea )
by Alexandr
02:44
created

Tokenizer   D

Complexity

Total Complexity 85

Size/Duplication

Total Lines 323
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 1 Features 0
Metric Value
wmc 85
lcom 1
cbo 3
dl 0
loc 323
ccs 183
cts 183
cp 1
rs 4.8717
c 1
b 1
f 0

21 Methods

Rating   Name   Duplication   Size   Complexity  
A initTokenizer() 0 5 1
A next() 0 9 1
C skipWhitespace() 0 31 14
C scan() 0 72 23
A checkFragment() 0 18 3
B scanWord() 0 19 10
C getKeyword() 0 27 8
A expect() 0 8 2
A match() 0 4 1
B scanNumber() 0 24 5
A skipInteger() 0 11 4
A createException() 0 4 1
A getLocation() 0 4 1
A getColumn() 0 4 1
A getLine() 0 4 1
A scanString() 0 20 4
A end() 0 4 1
A peek() 0 4 1
A lex() 0 7 1
A createUnexpectedException() 0 4 1
A createUnexpectedTokenTypeException() 0 4 1

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