Completed
Push — master ( 6bfa1f...af419f )
by Portey
05:37
created

Parser::eatMulti()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 8
ccs 4
cts 4
cp 1
rs 9.4286
cc 2
eloc 4
nc 2
nop 1
crap 2
1
<?php
2
/*
3
* This file is a part of graphql-youshido project.
4
*
5
* @author Portey Vasil <[email protected]>
6
* created: 11/23/15 1:22 AM
7
*/
8
9
namespace Youshido\GraphQL\Parser;
10
11
use Youshido\GraphQL\Parser\Ast\TypedFragment;
12
use Youshido\GraphQL\Parser\Ast\TypedFragmentReference;
13
use Youshido\GraphQL\Parser\Value\InputList;
14
use Youshido\GraphQL\Parser\Ast\Argument;
15
use Youshido\GraphQL\Parser\Ast\Field;
16
use Youshido\GraphQL\Parser\Ast\Fragment;
17
use Youshido\GraphQL\Parser\Ast\FragmentReference;
18
use Youshido\GraphQL\Parser\Value\InputObject;
19
use Youshido\GraphQL\Parser\Value\Literal;
20
use Youshido\GraphQL\Parser\Ast\Mutation;
21
use Youshido\GraphQL\Parser\Ast\Query;
22
use Youshido\GraphQL\Parser\Value\Variable;
23
24
class Parser extends Tokenizer
25
{
26
27 43
    public function parse()
28
    {
29 43
        $data = ['queries' => [], 'mutations' => [], 'fragments' => []];
30
31 43
        while (!$this->end()) {
32 43
            $tokenType = $this->peek()->getType();
33
34
            switch ($tokenType) {
35 43
                case Token::TYPE_LBRACE:
36 43
                case Token::TYPE_QUERY:
37 37
                    $data = array_merge($data, ['queries' => $this->parseBody()]);
38 31
                    break;
39
40 8
                case Token::TYPE_MUTATION:
41 5
                    $data = array_merge($data, ['mutations' => $this->parseBody(Token::TYPE_MUTATION)]);
42 3
                    break;
43
44 3
                case Token::TYPE_FRAGMENT:
45 2
                    $data['fragments'][] = $this->parseFragment();
46 2
                    break;
47
48 1
                default:
49 1
                    throw new SyntaxErrorException();
50 1
            }
51 34
        }
52
53 34
        return $data;
54
    }
55
56 42
    protected function parseBody($token = Token::TYPE_QUERY, $highLevel = true)
57
    {
58 42
        $fields = [];
59 42
        $first  = true;
60
61 42
        if ($this->peek()->getType() == $token) {
62 12
            $this->lex();
63 12
            $this->eat(Token::TYPE_IDENTIFIER);
64 12
        }
65
66 42
        $this->lex();
67
68 42
        while (!$this->match(Token::TYPE_RBRACE) && !$this->end()) {
69 39
            if ($first) {
70 39
                $first = false;
71 39
            } else {
72 17
                $this->eatMulti([Token::TYPE_COMMA]);
73
            }
74
75 39
            if ($this->match(Token::TYPE_FRAGMENT_REFERENCE)) {
76 5
                $this->lex();
77
78 5
                if ($this->eat(Token::TYPE_ON)) {
79 3
                    $fields[] = $this->parseBodyItem(Token::TYPE_TYPED_FRAGMENT, $highLevel);
80 3
                } else {
81 2
                    $fields[] = $this->parseFragmentReference();
82
                }
83 5
            } else {
84 39
                $fields[] = $this->parseBodyItem($token, $highLevel);
85
            }
86 34
        }
87
88 35
        $this->expect(Token::TYPE_RBRACE);
89
90 35
        return $fields;
91
    }
92
93 40
    protected function expect($type)
94
    {
95 40
        if ($this->match($type)) {
96 40
            return $this->lex();
97
        }
98
99 3
        throw $this->createUnexpected($this->peek());
100
    }
101
102 39
    protected function expectMulti($types)
103
    {
104 39
        if ($this->matchMulti($types)) {
105 39
            return $this->lex();
106
        }
107
108 2
        throw $this->createUnexpected($this->peek());
109
    }
110
111 4
    protected function parseReference()
112
    {
113 4
        $this->expectMulti([Token::TYPE_AMP, Token::TYPE_VARIABLE]);
114
115 4
        if ($this->match(Token::TYPE_NUMBER) || $this->match(Token::TYPE_IDENTIFIER)) {
116 3
            return new Variable($this->lex()->getData());
117
        }
118
119 1
        throw $this->createUnexpected($this->peek());
120
    }
121
122 2
    protected function parseFragmentReference()
123
    {
124 2
        $name = $this->parseIdentifier();
125
126 2
        return new FragmentReference($name);
127
    }
128
129 39
    protected function parseIdentifier()
130
    {
131 39
        return $this->expectMulti([
132 39
            Token::TYPE_IDENTIFIER,
133 39
            Token::TYPE_MUTATION,
134 39
            Token::TYPE_QUERY,
135 39
            Token::TYPE_FRAGMENT,
136 39
        ])->getData();
137
    }
138
139 39
    protected function parseBodyItem($type = Token::TYPE_QUERY, $highLevel = true)
140
    {
141 39
        $name  = $this->parseIdentifier();
142 39
        $alias = null;
143
144 39
        if ($this->eat(Token::TYPE_COLON)) {
145 10
            $alias = $name;
146 10
            $name  = $this->parseIdentifier();
147 10
        }
148
149 39
        $arguments = $this->match(Token::TYPE_LPAREN) ? $this->parseArgumentList() : [];
150
151 34
        if ($this->match(Token::TYPE_LBRACE)) {
152 32
            $fields = $this->parseBody($type, false);
153
154 30
            if ($type == Token::TYPE_QUERY) {
155 29
                return new Query($name, $alias, $arguments, $fields);
156 4
            } elseif ($type == Token::TYPE_TYPED_FRAGMENT) {
157 3
                return new TypedFragmentReference($name, $fields);
158
            } else {
159 1
                return new Mutation($name, $alias, $arguments, $fields);
160
            }
161
        } else {
162 34
            if ($highLevel && $type == Token::TYPE_MUTATION) {
163 2
                return new Mutation($name, $alias, $arguments);
164
            }
165
166 32
            return new Field($name, $alias);
167
        }
168
    }
169
170 19
    protected function parseArgumentList()
171
    {
172 19
        $args  = [];
173 19
        $first = true;
174
175 19
        $this->expect(Token::TYPE_LPAREN);
176
177 19
        while (!$this->match(Token::TYPE_RPAREN) && !$this->end()) {
178 19
            if ($first) {
179 19
                $first = false;
180 19
            } else {
181 3
                $this->expect(Token::TYPE_COMMA);
182
            }
183
184 19
            $args[] = $this->parseArgument();
185 14
        }
186
187 14
        $this->expect(Token::TYPE_RPAREN);
188
189 14
        return $args;
190
    }
191
192 19
    protected function parseArgument()
193
    {
194 19
        $name = $this->parseIdentifier();
195 19
        $this->expect(Token::TYPE_COLON);
196 19
        $value = $this->parseValue();
197
198 14
        return new Argument($name, $value);
199
    }
200
201 19
    protected function parseValue()
202
    {
203 19
        switch ($this->lookAhead->getType()) {
204 19
            case Token::TYPE_LSQUARE_BRACE:
205 3
                return $this->parseList();
206
207 16
            case Token::TYPE_LBRACE:
208 3
                return $this->parseObject();
209
210 13
            case Token::TYPE_AMP:
211 13
            case Token::TYPE_VARIABLE:
212 4
                return $this->parseReference();
213
214 10
            case Token::TYPE_LT:
215 1
                return $this->parseVariable();
216
217 9
            case Token::TYPE_NUMBER:
218 9
            case Token::TYPE_STRING:
219 9
            case Token::TYPE_IDENTIFIER:
220 8
                return new Literal($this->lex()->getData());
221
222 2
            case Token::TYPE_NULL:
223 2
            case Token::TYPE_TRUE:
224 2
            case Token::TYPE_FALSE:
225 1
                return new Literal($this->lex()->getData());
226 1
        }
227
228 1
        throw $this->createUnexpected($this->lookAhead);
229
    }
230
231 4
    protected function parseList($createType = true)
232
    {
233 4
        $this->eat(Token::TYPE_LSQUARE_BRACE);
234
235 4
        $list = [];
236 4
        while (!$this->match(Token::TYPE_RSQUARE_BRACE) && !$this->end()) {
237 4
            $list[] = $this->parseListValue();
238
239 3
            if ($this->lookAhead->getType() != Token::TYPE_RSQUARE_BRACE) {
240 3
                $this->expect(Token::TYPE_COMMA);
241 3
            }
242 3
        }
243
244 3
        $this->expect(Token::TYPE_RSQUARE_BRACE);
245
246 3
        return $createType ? new InputList($list) : $list;
247
    }
248
249 6
    protected function parseListValue()
250
    {
251 6
        switch ($this->lookAhead->getType()) {
252 6
            case Token::TYPE_NUMBER:
253 5
                return $this->expect(Token::TYPE_NUMBER)->getData();
254
255 4
            case Token::TYPE_STRING:
256 2
                return $this->expect(Token::TYPE_STRING)->getData();
257
258 4
            case Token::TYPE_LBRACE:
259 1
                return $this->parseObject(false);
260
261 4
            case Token::TYPE_LSQUARE_BRACE:
262 1
                return $this->parseList(false);
263
264 3
            case Token::TYPE_TRUE:
265 1
                return $this->expect(Token::TYPE_TRUE)->getData();
266
267 3
            case Token::TYPE_FALSE:
268 1
                return $this->expect(Token::TYPE_FALSE)->getData();
269
270 3
            case Token::TYPE_NULL:
271 2
                return $this->expect(Token::TYPE_NULL)->getData();
272 1
        }
273
274 1
        throw new SyntaxErrorException('Can\'t parse argument');
275
    }
276
277 3
    protected function parseObject($createType = true)
278
    {
279 3
        $this->eat(Token::TYPE_LBRACE);
280
281 3
        $object = [];
282 3
        while (!$this->match(Token::TYPE_RBRACE) && !$this->end()) {
283 3
            $key = $this->expect(Token::TYPE_STRING)->getData();
284 3
            $this->expect(Token::TYPE_COLON);
285 3
            $value = $this->parseListValue();
286
287 3
            if ($this->peek()->getType() != Token::TYPE_RBRACE) {
288 3
                $this->expect(Token::TYPE_COMMA);
289 3
            }
290
291 3
            $object[$key] = $value;
292 3
        }
293
294 1
        $this->eat(Token::TYPE_RBRACE);
295
296 1
        return $createType ? new InputObject($object) : $object;
297
    }
298
299 1
    protected function parseVariable()
300
    {
301 1
        $this->expect(Token::TYPE_LT);
302 1
        $name = $this->expect(Token::TYPE_IDENTIFIER)->getData();
303 1
        $this->expect(Token::TYPE_GT);
304
305 1
        return new Variable($name);
306
    }
307
308 2
    protected function parseFragment()
309
    {
310 2
        $this->lex();
311 2
        $name = $this->parseIdentifier();
312
313 2
        $this->eat(Token::TYPE_ON);
314
315 2
        $model  = $this->parseIdentifier();
316 2
        $fields = $this->parseBody();
317
318 2
        return new Fragment($name, $model, $fields);
319
    }
320
321 41
    protected function eat($type)
322
    {
323 41
        if ($this->match($type)) {
324 20
            return $this->lex();
325
        }
326
327 40
        return null;
328
    }
329
330 17
    protected function eatMulti($types)
331
    {
332 17
        if ($this->matchMulti($types)) {
333 16
            return $this->lex();
334
        }
335
336 1
        return null;
337
    }
338
339 39
    protected function matchMulti($types)
340
    {
341 39
        foreach ($types as $type) {
342 39
            if ($this->peek()->getType() == $type) {
343 39
                return true;
344
            }
345 8
        }
346
347 3
        return false;
348
    }
349
350 42
    protected function match($type)
351
    {
352 42
        return $this->peek()->getType() === $type;
353
    }
354
}