Completed
Push — master ( d52ee7...e7842c )
by Alexandr
03:54
created

Tokenizer::createUnexpectedTokenTypeException()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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