Completed
Push — master ( 331d29...e40fed )
by Portey
02:57
created

Tokenizer   D

Complexity

Total Complexity 82

Size/Duplication

Total Lines 326
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 70.47%

Importance

Changes 8
Bugs 0 Features 2
Metric Value
wmc 82
c 8
b 0
f 2
lcom 1
cbo 2
dl 0
loc 326
ccs 136
cts 193
cp 0.7047
rs 4.8718

17 Methods

Rating   Name   Duplication   Size   Complexity  
A setSource() 0 5 1
A next() 0 13 1
C skipWhitespace() 0 22 7
D scan() 0 80 25
A checkFragment() 0 18 3
B scanWord() 0 19 10
D getKeyword() 0 30 9
B scanNumber() 0 25 4
B skipInteger() 0 17 5
A createIllegal() 0 6 2
A createError() 0 4 1
A getColumn() 0 4 1
B scanString() 0 23 5
A end() 0 4 1
A peek() 0 4 1
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 16
    public function setSource($source)
21
    {
22 16
        $this->source    = $source;
23 16
        $this->lookAhead = $this->next();
24 16
    }
25
26 16
    protected function next()
27
    {
28 16
        $this->skipWhitespace();
29
30 16
        $line      = $this->line;
31 16
        $lineStart = $this->lineStart;
32 16
        $token     = $this->scan();
33
34 16
        $token->line   = $line;
0 ignored issues
show
Bug introduced by
The property line does not seem to exist in Youshido\GraphQL\Parser\Token.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
35 16
        $token->column = $this->pos - $lineStart;
0 ignored issues
show
Bug introduced by
The property column does not seem to exist in Youshido\GraphQL\Parser\Token.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
36
37 16
        return $token;
38
    }
39
40 16
    protected function skipWhitespace()
41
    {
42 16
        while ($this->pos < strlen($this->source)) {
43 16
            $ch = $this->source[$this->pos];
44 16
            if ($ch === ' ' || $ch === "\t") {
45 15
                $this->pos++;
46 16
            } elseif ($ch === "\r") {
47
                $this->pos++;
48
                if ($this->source[$this->pos] === "\n") {
49
                    $this->pos++;
50
                }
51
                $this->line++;
52
                $this->lineStart = $this->pos;
53 16
            } elseif ($ch === "\n") {
54 3
                $this->pos++;
55 3
                $this->line++;
56 3
                $this->lineStart = $this->pos;
57 3
            } else {
58 16
                break;
59
            }
60 15
        }
61 16
    }
62
63
    /**
64
     * @return Token
65
     */
66 16
    protected function scan()
67
    {
68 16
        if ($this->pos >= strlen($this->source)) {
69 16
            return new Token(Token::TYPE_END);
70
        }
71
72 16
        $ch = $this->source[$this->pos];
73
        switch ($ch) {
74 16
            case Token::TYPE_LPAREN:
75 9
                ++$this->pos;
76
77 9
                return new Token(Token::TYPE_LPAREN);
78 16
            case Token::TYPE_RPAREN:
79 9
                ++$this->pos;
80
81 9
                return new Token(Token::TYPE_RPAREN);
82 16
            case Token::TYPE_LBRACE:
83 16
                ++$this->pos;
84
85 16
                return new Token(Token::TYPE_LBRACE);
86 16
            case Token::TYPE_RBRACE:
87 16
                ++$this->pos;
88
89 16
                return new Token(Token::TYPE_RBRACE);
90 15
            case Token::TYPE_LT:
91
                ++$this->pos;
92
93
                return new Token(Token::TYPE_LT);
94 15
            case Token::TYPE_GT:
95
                ++$this->pos;
96
97
                return new Token(Token::TYPE_GT);
98 15
            case Token::TYPE_AMP:
99
                ++$this->pos;
100
101
                return new Token(Token::TYPE_AMP);
102 15
            case Token::TYPE_COMMA:
103 9
                ++$this->pos;
104
105 9
                return new Token(Token::TYPE_COMMA);
106 15
            case Token::TYPE_LSQUARE_BRACE:
107 3
                ++$this->pos;
108
109 3
                return new Token(Token::TYPE_LSQUARE_BRACE);
110 15
            case Token::TYPE_RSQUARE_BRACE:
111 3
                ++$this->pos;
112
113 3
                return new Token(Token::TYPE_RSQUARE_BRACE);
114 15
            case Token::TYPE_COLON:
115 11
                ++$this->pos;
116
117 11
                return new Token(Token::TYPE_COLON);
118
119 15
            case Token::TYPE_POINT:
120
                if ($this->checkFragment()) {
121
                    return new Token(Token::TYPE_FRAGMENT_REFERENCE);
122
                }
123
124
                break;
125
126 15
            case Token::TYPE_VARIABLE:
127 1
                ++$this->pos;
128
129 1
                return new Token(Token::TYPE_VARIABLE);
130
        }
131
132 15
        if ($ch === '_' || 'a' <= $ch && $ch <= 'z' || 'A' <= $ch && $ch <= 'Z') {
133 15
            return $this->scanWord();
134
        }
135
136 8
        if ($ch === '-' || '0' <= $ch && $ch <= '9') {
137 6
            return $this->scanNumber();
138
        }
139
140 5
        if ($ch === '"') {
141 5
            return $this->scanString();
142
        }
143
144
        throw $this->createIllegal();
145
    }
146
147
    protected function checkFragment()
148
    {
149
        $this->pos++;
150
        $ch = $this->source[$this->pos];
151
152
        $this->pos++;
153
        $nextCh = $this->source[$this->pos];
154
155
        $isset = $ch == Token::TYPE_POINT && $nextCh == Token::TYPE_POINT;
156
157
        if ($isset) {
158
            $this->pos++;
159
160
            return true;
161
        }
162
163
        return false;
164
    }
165
166 15
    protected function scanWord()
167
    {
168 15
        $start = $this->pos;
169 15
        $this->pos++;
170
171 15
        while ($this->pos < strlen($this->source)) {
172 15
            $ch = $this->source[$this->pos];
173
174 15
            if ($ch === '_' || $ch === '$' || 'a' <= $ch && $ch <= ('z') || 'A' <= $ch && $ch <= 'Z' || '0' <= $ch && $ch <= '9') {
175 15
                $this->pos++;
176 15
            } else {
177 15
                break;
178
            }
179 15
        }
180
181 15
        $value = substr($this->source, $start, $this->pos - $start);
182
183 15
        return new Token($this->getKeyword($value), $value);
184
    }
185
186 15
    protected function getKeyword($name)
187
    {
188
        switch ($name) {
189 15
            case 'null':
190 1
                return Token::TYPE_NULL;
191
192 15
            case 'true':
193 2
                return Token::TYPE_TRUE;
194
195 15
            case 'false':
196
                return Token::TYPE_FALSE;
197
198 15
            case 'query':
199 2
                return Token::TYPE_QUERY;
200
201 15
            case 'fragment':
202
                return Token::TYPE_FRAGMENT;
203
204 15
            case 'mutation':
205 2
                return Token::TYPE_MUTATION;
206
207 15
            case 'on':
208
                return Token::TYPE_ON;
209
210 15
            case 'as':
211
                return Token::TYPE_AS;
0 ignored issues
show
Deprecated Code introduced by
The constant Youshido\GraphQL\Parser\Token::TYPE_AS has been deprecated.

This class constant has been deprecated.

Loading history...
212
        }
213
214 15
        return Token::TYPE_IDENTIFIER;
215
    }
216
217 6
    protected function scanNumber()
218
    {
219 6
        $start = $this->pos;
220
221 6
        if ($this->source[$this->pos] === '-') {
222
            $this->pos++;
223
        }
224
225 6
        $this->skipInteger();
226
227 6
        if ($this->source[$this->pos] === '.') {
228 1
            $this->pos++;
229 1
            $this->skipInteger();
230 1
        }
231
232 6
        $value = substr($this->source, $start, $this->pos - $start);
233
234 6
        if(strpos($value, '.') === false){
235 6
            $value = (int) $value;
236 6
        } else {
237 1
            $value = (float) $value;
238
        }
239
240 6
        return new Token(Token::TYPE_NUMBER, $value);
241
    }
242
243 6
    protected function skipInteger()
244
    {
245 6
        $start = $this->pos;
246
247 6
        while ($this->pos < strlen($this->source)) {
248 6
            $ch = $this->source[$this->pos];
249 6
            if ('0' <= $ch && $ch <= '9') {
250 6
                $this->pos++;
251 6
            } else {
252 6
                break;
253
            }
254 6
        }
255
256 6
        if ($this->pos - $start === 0) {
257
            throw $this->createIllegal();
258
        }
259 6
    }
260
261
    protected function createIllegal()
262
    {
263
        return $this->pos < strlen($this->source)
264
            ? $this->createError("Unexpected {$this->source[$this->pos]}")
265
            : $this->createError('Unexpected end of input');
266
    }
267
268
    protected function createError($message)
269
    {
270
        return new SyntaxErrorException($message . " ({$this->line}:{$this->getColumn()})");
271
    }
272
273
    protected function getColumn()
274
    {
275
        return $this->pos - $this->lineStart;
276
    }
277
278 5
    protected function scanString()
279
    {
280 5
        $this->pos++;
281
282 5
        $value = '';
283 5
        while ($this->pos < strlen($this->source)) {
284 5
            $ch = $this->source[$this->pos];
285 5
            if ($ch === '"') {
286 5
                $this->pos++;
287
288 5
                return new Token(Token::TYPE_STRING, $value);
289
            }
290
291 5
            if ($ch === "\r" || $ch === "\n") {
292
                break;
293
            }
294
295 5
            $value .= $ch;
296 5
            $this->pos++;
297 5
        }
298
299
        throw $this->createIllegal();
300
    }
301
302 16
    protected function end()
303
    {
304 16
        return $this->lookAhead->getType() === Token::TYPE_END;
305
    }
306
307
    protected function peek()
308
    {
309
        return $this->lookAhead;
310
    }
311
312 16
    protected function lex()
313
    {
314 16
        $prev            = $this->lookAhead;
315 16
        $this->lookAhead = $this->next();
316
317 16
        return $prev;
318
    }
319
320
    protected function createUnexpected(Token $token)
321
    {
322
        switch ($token->getType()) {
323
            case Token::TYPE_END:
324
                return $this->createError('Unexpected end of input');
325
            case Token::TYPE_NUMBER:
326
                return $this->createError('Unexpected number');
327
            case Token::TYPE_STRING:
328
                return $this->createError('Unexpected string');
329
            case Token::TYPE_IDENTIFIER:
330
                return $this->createError('Unexpected identifier');
331
        }
332
333
        return new \Exception('Unexpected token');
334
    }
335
}