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