Completed
Push — master ( e98c11...e5185f )
by Hans
03:36 queued 01:01
created

Lexer::getError()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 7
ccs 4
cts 4
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 1
crap 1
1
<?php
2
3
namespace HansOtt\GraphQL\Query;
4
5
final class Lexer
6
{
7
    /**
8
     * @var ScannerWithLocation
9
     */
10
    private $scanner;
11
    private $tokens;
12
13 63
    private function emit($type, $value)
14
    {
15 63
        $line = $this->scanner->getLine();
16 63
        $column = $this->scanner->getColumn();
17 63
        $this->tokens[] = new Token($type, $value, $line, $column);
18 63
    }
19
20 27
    private function getError($message)
21
    {
22 27
        $line = $this->scanner->getLine();
23 27
        $column = $this->scanner->getColumn();
24
25 27
        return new SyntaxError($message . " (line {$line}, column {$column})");
26
    }
27
28 60
    private function name()
29
    {
30 60
        $name = $this->scanner->next();
31
32 60
        if ($this->scanner->eof()) {
33
            $this->emit(Token::T_NAME, $name);
34
            return;
35
        }
36
37 60
        $next = $this->scanner->peek();
38 60
        while ($next === '_' || ctype_alpha($next) || ctype_digit($next)) {
39 60
            $name .= $this->scanner->next();
40 60
            if ($this->scanner->eof()) {
41 3
                break;
42
            }
43 60
            $next = $this->scanner->peek();
44 60
        }
45
46 60
        $type = Token::T_NAME;
47 60
        if ($name === 'query') {
48 15
            $type = Token::T_QUERY;
49 60
        } elseif ($name === 'mutation') {
50
            $type = Token::T_MUTATION;
51 60
        } elseif ($name === 'subscription') {
52
            $type = Token::T_SUBSCRIPTION;
53 60
        } elseif ($name === 'fragment') {
54 6
            $type = Token::T_FRAGMENT;
55 60
        } elseif ($name === 'true') {
56 3
            $type = Token::T_TRUE;
57 60
        } elseif ($name === 'false') {
58 3
            $type = Token::T_FALSE;
59 60
        } elseif ($name === 'null') {
60 3
            $type = Token::T_NULL;
61 3
        }
62
63 60
        $this->emit($type, $name);
64 60
    }
65
66
    private function comment()
67
    {
68
        $this->scanner->next();
69
        $next = $this->scanner->peek();
70
        while ($this->scanner->eof() === false && $next !== "\n" && $next !== "\r") {
71
            $this->scanner->next();
72
            $next = $this->scanner->peek();
73
        }
74
    }
75
76 6
    private function spread()
77
    {
78 6
        $points = $this->scanner->next();
79 6
        $next = $this->scanner->peek();
80
81 6
        if ($next !== '.') {
82
            throw $this->getError("Expected \".\" but instead found \"{$next}\"");
83
        }
84
85 6
        $points .= $this->scanner->next();
86 6
        $next = $this->scanner->peek();
87
88 6
        if ($next !== '.') {
89
            throw $this->getError("Expected \".\" but instead found \"{$next}\"");
90
        }
91
92 6
        $points .= $this->scanner->next();
93 6
        $this->emit(Token::T_SPREAD, $points);
94 6
    }
95
96 24
    private function str()
97
    {
98 24
        $this->scanner->next();
99 24
        $string = '';
100 24
        $previousChar = false;
101
102 24
        while (true) {
103 24
            if ($this->scanner->eof()) {
104 3
                throw $this->getError('Unclosed quote');
105
            }
106 21
            $next = $this->scanner->peek();
107 21
            if ($previousChar !== '\\' && $next === '"') {
108 21
                $this->scanner->next();
109 21
                break;
110
            }
111 21
            $previousChar = $this->scanner->next();
112 21
            $string .= $previousChar;
113 21
        }
114
115 21
        $string = json_decode('"' . $string . '"');
116 21
        $this->emit(Token::T_STRING, $string);
117 21
    }
118
119 48 View Code Duplication
    private function integerPart()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
120
    {
121 48
        $number = $this->scanner->next();
122 48
        if ($number === '-') {
123 12
            if ($this->scanner->eof()) {
124 3
                throw $this->getError('Expected a digit but instead reached end');
125
            }
126 9
            $next = $this->scanner->peek();
127 9
            if (ctype_digit($next) === false) {
128
                throw $this->getError("Expected a digit but instead found \"{$next}\"");
129
            }
130 9
        }
131
132 45
        $next = $this->scanner->peek();
133 45
        if ($next === '0') {
134 9
            $number .= $this->scanner->next();
135 9
            return $number;
136
        }
137
138 36
        $next = $this->scanner->peek();
139 36
        while ($this->scanner->eof() === false && ctype_digit($next)) {
140
            $number .= $this->scanner->next();
141
            $next = $this->scanner->peek();
142
        }
143
144 36
        return $number;
145
    }
146
147 21
    private function fractionalPart()
148
    {
149 21
        $part = $this->scanner->next();
150
151 21
        if ($this->scanner->eof()) {
152 6
            throw $this->getError('Expected a digit but instead reached end');
153
        }
154
155 15
        $next = $this->scanner->peek();
156 15
        if (ctype_digit($next) === false) {
157
            throw $this->getError("Expected a digit but instead found \"{$next}\"");
158
        }
159
160 15
        $next = $this->scanner->peek();
161 15
        while ($this->scanner->eof() === false && ctype_digit($next)) {
162 15
            $part .= $this->scanner->next();
163 15
            $next = $this->scanner->peek();
164 15
        }
165
166 15
        return $part;
167
    }
168
169 15 View Code Duplication
    private function exponentPart()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
170
    {
171 15
        $part = $this->scanner->next();
172
173 15
        if ($this->scanner->eof()) {
174 9
            throw $this->getError('Expected a digit but instead reached end');
175
        }
176
177 6
        $next = $this->scanner->peek();
178 6
        if ($next === '+' || $next === '-') {
179
            $part .= $this->scanner->next();
180
        }
181
182 6
        $next = $this->scanner->peek();
183 6
        if (ctype_digit($next) === false) {
184 6
            throw $this->getError("Expected a digit but instead found \"{$next}\"");
185
        }
186
187
        $next = $this->scanner->peek();
188
        while ($this->scanner->eof() === false && ctype_digit($next)) {
189
            $part .= $this->scanner->next();
190
            $next = $this->scanner->peek();
191
        }
192
193
        return $part;
194
    }
195
196 48
    private function number()
197
    {
198 48
        $integerPart = $this->integerPart();
199 45
        if ($this->scanner->eof()) {
200
            $this->emit(Token::T_INT, $integerPart);
201
            return;
202
        }
203
204 45
        $next = $this->scanner->peek();
205 45
        if ($next !== '.' && $next !== 'e' && $next !== 'E') {
206 24
            $this->emit(Token::T_INT, $integerPart);
207 24
            return;
208
        }
209
210 24
        $number = $integerPart;
211 24
        if ($next === '.') {
212 21
            $number .= $this->fractionalPart();
213 15
        }
214
215 18
        $next = $this->scanner->peek();
216 18
        if ($next === 'e' || $next === 'E') {
217 15
            $number .= $this->exponentPart();
218
        }
219
220 3
        $this->emit(Token::T_FLOAT, $number);
221 3
    }
222
223
    /**
224
     * @param string $query
225
     *
226
     * @return Token[]
227
     */
228 93
    public function lex($query)
229
    {
230 93
        $flags = PREG_SPLIT_NO_EMPTY;
231 93
        $chars = preg_split('//u', $query, -1, $flags);
232 93
        $scanner = new ScannerGeneric($chars);
233 93
        $this->scanner = new ScannerWithLocation($scanner);
234 93
        $this->tokens = array();
235
        $punctuators = array(
236 93
            '!' => Token::T_EXCLAMATION,
237 93
            '$' => Token::T_DOLLAR,
238 93
            '(' => Token::T_PAREN_LEFT,
239 93
            ')' => Token::T_PAREN_RIGHT,
240 93
            '{' => Token::T_BRACE_LEFT,
241 93
            '}' => Token::T_BRACE_RIGHT,
242 93
            ':' => Token::T_COLON,
243 93
            ',' => Token::T_COMMA,
244 93
            '[' => Token::T_BRACKET_LEFT,
245 93
            ']' => Token::T_BRACKET_RIGHT,
246 93
            '=' => Token::T_EQUAL,
247 93
            '@' => Token::T_AT,
248 93
        );
249
250 93
        while ($this->scanner->eof() === false) {
251 90
            $next = $this->scanner->peek();
252
253 90
            if (ctype_space($next)) {
254 60
                $this->scanner->next();
255 60
                continue;
256
            }
257
258 90
            if ($next === '#') {
259
                $this->comment();
260
                continue;
261
            }
262
263 90
            if ($next === '_' || ctype_alpha($next)) {
264 60
                $this->name();
265 60
                continue;
266
            }
267
268 90
            if ($next === '.') {
269 6
                $this->spread();
270 6
                continue;
271
            }
272
273 90
            if ($next === '"') {
274 24
                $this->str();
275 21
                continue;
276
            }
277
278 87
            if ($next === '-' || ctype_digit($next)) {
279 48
                $this->number();
280 24
                continue;
281
            }
282
283 63
            if (isset($punctuators[$next])) {
284 63
                $this->emit($punctuators[$next], $this->scanner->next());
285 63
                continue;
286
            }
287
288
            throw $this->getError("Unknown character: \"{$next}\"");
289
        }
290
291 66
        return $this->tokens;
292
    }
293
}
294