Completed
Push — master ( 757834...ce0ced )
by Alexandr
02:36
created

Tokenizer::skipInteger()   B

Complexity

Conditions 5
Paths 6

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 5.0145

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 17
ccs 11
cts 12
cp 0.9167
rs 8.8571
cc 5
eloc 10
nc 6
nop 0
crap 5.0145
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 9
    public function setSource($source)
21
    {
22 9
        $this->source    = $source;
23 9
        $this->lookAhead = $this->next();
24 9
    }
25
26 9
    protected function next()
27
    {
28 9
        $this->skipWhitespace();
29
30 9
        $line      = $this->line;
31 9
        $lineStart = $this->lineStart;
32 9
        $token     = $this->scan();
33
34 9
        $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 9
        $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 9
        return $token;
38
    }
39
40 9
    protected function skipWhitespace()
41
    {
42 9
        while ($this->pos < strlen($this->source)) {
43 9
            $ch = $this->source[$this->pos];
44 9
            if ($ch === ' ' || $ch === "\t") {
45 8
                $this->pos++;
46 9
            } 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 9
            } elseif ($ch === "\n") {
54
                $this->pos++;
55
                $this->line++;
56
                $this->lineStart = $this->pos;
57
            } else {
58 9
                break;
59
            }
60 8
        }
61 9
    }
62
63
    /**
64
     * @return Token
65
     */
66 9
    protected function scan()
67
    {
68 9
        if ($this->pos >= strlen($this->source)) {
69 9
            return new Token(Token::TYPE_END);
70
        }
71
72 9
        $ch = $this->source[$this->pos];
73
        switch ($ch) {
74 9
            case Token::TYPE_LPAREN:
75 7
                ++$this->pos;
76
77 7
                return new Token(Token::TYPE_LPAREN);
78 9
            case Token::TYPE_RPAREN:
79 7
                ++$this->pos;
80
81 7
                return new Token(Token::TYPE_RPAREN);
82 9
            case Token::TYPE_LBRACE:
83 9
                ++$this->pos;
84
85 9
                return new Token(Token::TYPE_LBRACE);
86 9
            case Token::TYPE_RBRACE:
87 9
                ++$this->pos;
88
89 9
                return new Token(Token::TYPE_RBRACE);
90 8
            case Token::TYPE_LT:
91
                ++$this->pos;
92
93
                return new Token(Token::TYPE_LT);
94 8
            case Token::TYPE_GT:
95
                ++$this->pos;
96
97
                return new Token(Token::TYPE_GT);
98 8
            case Token::TYPE_AMP:
99
                ++$this->pos;
100
101
                return new Token(Token::TYPE_AMP);
102 8
            case Token::TYPE_COMMA:
103 6
                ++$this->pos;
104
105 6
                return new Token(Token::TYPE_COMMA);
106 8
            case Token::TYPE_LSQUARE_BRACE:
107 3
                ++$this->pos;
108
109 3
                return new Token(Token::TYPE_LSQUARE_BRACE);
110 8
            case Token::TYPE_RSQUARE_BRACE:
111 3
                ++$this->pos;
112
113 3
                return new Token(Token::TYPE_RSQUARE_BRACE);
114 8
            case Token::TYPE_COLON:
115 7
                ++$this->pos;
116
117 7
                return new Token(Token::TYPE_COLON);
118
119 8
            case Token::TYPE_POINT:
120
                if ($this->checkFragment()) {
121
                    return new Token(Token::TYPE_FRAGMENT_REFERENCE);
122
                }
123
124
                break;
125
        }
126
127 8
        if ($ch === '_' || $ch === '$' || 'a' <= $ch && $ch <= 'z' || 'A' <= $ch && $ch <= 'Z') {
128 8
            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 8
    protected function scanWord()
162
    {
163 8
        $start = $this->pos;
164 8
        $this->pos++;
165
166 8
        while ($this->pos < strlen($this->source)) {
167 8
            $ch = $this->source[$this->pos];
168
169 8
            if ($ch === '_' || $ch === '$' || 'a' <= $ch && $ch <= ('z') || 'A' <= $ch && $ch <= 'Z' || '0' <= $ch && $ch <= '9') {
170 8
                $this->pos++;
171 8
            } else {
172 8
                break;
173
            }
174 8
        }
175
176 8
        $value = substr($this->source, $start, $this->pos - $start);
177
178 8
        return new Token($this->getKeyword($value), $value);
179
    }
180
181 8
    protected function getKeyword($name)
182
    {
183
        switch ($name) {
184 8
            case 'null':
185 1
                return Token::TYPE_NULL;
186
187 8
            case 'true':
188 2
                return Token::TYPE_TRUE;
189
190 8
            case 'false':
191
                return Token::TYPE_FALSE;
192
193 8
            case 'query':
194
                return Token::TYPE_QUERY;
195
196 8
            case 'fragment':
197
                return Token::TYPE_FRAGMENT;
198
199 8
            case 'mutation':
200 2
                return Token::TYPE_MUTATION;
201
202 8
            case 'on':
203
                return Token::TYPE_ON;
204
205 8
            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 8
        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] === '->' || $this->source[$this->pos] === '.') {
223 1
            $this->pos++;
224 1
            $this->skipInteger();
225 1
        }
226
227 6
        $ch = $this->source[$this->pos];
228 6
        if ($ch === 'e' || $ch === 'E') {
229
            $this->pos++;
230
231
            $ch = $this->source[$this->pos];
232
            if ($ch === '+' || $ch === '-') {
233
                $this->pos++;
234
            }
235
236
            $this->skipInteger();
237
        }
238
239 6
        $value = (float)substr($this->source, $start, $this->pos);
240
241 6
        return new Token(Token::TYPE_NUMBER, $value);
242
    }
243
244 6
    protected function skipInteger()
245
    {
246 6
        $start = $this->pos;
247
248 6
        while ($this->pos < strlen($this->source)) {
249 6
            $ch = $this->source[$this->pos];
250 6
            if ('0' <= $ch && $ch <= '9') {
251 6
                $this->pos++;
252 6
            } else {
253 6
                break;
254
            }
255 6
        }
256
257 6
        if ($this->pos - $start === 0) {
258
            throw $this->createIllegal();
259
        }
260 6
    }
261
262
    protected function createIllegal()
263
    {
264
        return $this->pos < strlen($this->source)
265
            ? $this->createError("Unexpected {$this->source[$this->pos]}")
266
            : $this->createError('Unexpected end of input');
267
    }
268
269
    protected function createError($message)
270
    {
271
        return new SyntaxErrorException($message . " ({$this->line}:{$this->getColumn()})");
272
    }
273
274
    protected function getColumn()
275
    {
276
        return $this->pos - $this->lineStart;
277
    }
278
279 4
    protected function scanString()
280
    {
281 4
        $this->pos++;
282
283 4
        $value = '';
284 4
        while ($this->pos < strlen($this->source)) {
285 4
            $ch = $this->source[$this->pos];
286 4
            if ($ch === '"') {
287 4
                $this->pos++;
288
289 4
                return new Token(Token::TYPE_STRING, $value);
290
            }
291
292 4
            if ($ch === "\r" || $ch === "\n") {
293
                break;
294
            }
295
296 4
            $value .= $ch;
297 4
            $this->pos++;
298 4
        }
299
300
        throw $this->createIllegal();
301
    }
302
303 9
    protected function end()
304
    {
305 9
        return $this->lookAhead->getType() === Token::TYPE_END;
306
    }
307
308
    protected function peek()
309
    {
310
        return $this->lookAhead;
311
    }
312
313 9
    protected function lex()
314
    {
315 9
        $prev            = $this->lookAhead;
316 9
        $this->lookAhead = $this->next();
317
318 9
        return $prev;
319
    }
320
321
    protected function createUnexpected(Token $token)
322
    {
323
        switch ($token->getType()) {
324
            case Token::TYPE_END:
325
                return $this->createError('Unexpected end of input');
326
            case Token::TYPE_NUMBER:
327
                return $this->createError('Unexpected number');
328
            case Token::TYPE_STRING:
329
                return $this->createError('Unexpected string');
330
            case Token::TYPE_IDENTIFIER:
331
                return $this->createError('Unexpected identifier');
332
        }
333
334
        return new \Exception('Unexpected token');
335
    }
336
}