Completed
Push — master ( 70056a...05fbf4 )
by Alexandr
03:34
created

Tokenizer   D

Complexity

Total Complexity 86

Size/Duplication

Total Lines 323
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 98.91%

Importance

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

21 Methods

Rating   Name   Duplication   Size   Complexity  
A initTokenizer() 0 5 1
A next() 0 9 1
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 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
A getLocation() 0 4 1
C scan() 0 72 23
C skipWhitespace() 0 31 15

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