Completed
Push — master ( 331d29...e40fed )
by Portey
02:57
created

Parser::expectMulti()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2.0625

Importance

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