Completed
Push — master ( b8b70e...552805 )
by Portey
03:44
created

Tokenizer   D

Complexity

Total Complexity 82

Size/Duplication

Total Lines 321
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 69.47%

Importance

Changes 7
Bugs 0 Features 1
Metric Value
wmc 82
c 7
b 0
f 1
lcom 1
cbo 2
dl 0
loc 321
ccs 132
cts 190
cp 0.6947
rs 4.8718

17 Methods

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