Completed
Push — master ( bd0485...10acd3 )
by Portey
03:02
created

Tokenizer::createUnexpected()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 15
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 5.0145

Importance

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