Completed
Pull Request — master (#150)
by
unknown
06:33
created

Tokenizer::skipInteger()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4

Importance

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