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

Tokenizer::scanNumber()   B

Complexity

Conditions 4
Paths 8

Size

Total Lines 25
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 4.0313

Importance

Changes 3
Bugs 0 Features 0
Metric Value
c 3
b 0
f 0
dl 0
loc 25
ccs 14
cts 16
cp 0.875
rs 8.5806
cc 4
eloc 14
nc 8
nop 0
crap 4.0313
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
}