Completed
Push — master ( 325209...de8c12 )
by Portey
06:47
created

Parser::parseIdentifier()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 1

Importance

Changes 3
Bugs 0 Features 1
Metric Value
c 3
b 0
f 1
dl 0
loc 9
ccs 7
cts 7
cp 1
rs 9.6667
cc 1
eloc 7
nc 1
nop 0
crap 1
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 40
    public function parse()
28
    {
29 40
        $data = ['queries' => [], 'mutations' => [], 'fragments' => []];
30
31 40
        while (!$this->end()) {
32 40
            $tokenType = $this->peek()->getType();
33
34
            switch ($tokenType) {
35 40
                case Token::TYPE_LBRACE:
36 40
                case Token::TYPE_QUERY:
37 34
                    $data = array_merge($data, ['queries' => $this->parseBody()]);
38 28
                    break;
39
40 7
                case Token::TYPE_MUTATION:
41 5
                    $data = array_merge($data, ['mutations' => $this->parseBody(Token::TYPE_MUTATION)]);
42 3
                    break;
43
44 2
                case Token::TYPE_FRAGMENT:
45 1
                    $data['fragments'][] = $this->parseFragment();
46 1
                    break;
47
48 1
                default:
49 1
                    throw new SyntaxErrorException();
50 1
            }
51 31
        }
52
53 31
        return $data;
54
    }
55
56 39
    protected function parseBody($token = Token::TYPE_QUERY, $highLevel = true)
57
    {
58 39
        $fields = [];
59 39
        $first  = true;
60
61 39
        if ($this->peek()->getType() == $token) {
62 11
            $this->lex();
63 11
            $this->eat(Token::TYPE_IDENTIFIER);
64 11
        }
65
66 39
        $this->lex();
67
68 39
        while (!$this->match(Token::TYPE_RBRACE) && !$this->end()) {
69 36
            if ($first) {
70 36
                $first = false;
71 36
            } else {
72 14
                $this->expect(Token::TYPE_COMMA);
73
            }
74
75 36
            if ($this->match(Token::TYPE_FRAGMENT_REFERENCE)) {
76 2
                $this->lex();
77
78 2
                if ($this->eat(Token::TYPE_ON)) {
79 1
                    $fields[] = $this->parseBodyItem(Token::TYPE_TYPED_FRAGMENT, $highLevel);
80 1
                } else {
81 1
                    $fields[] = $this->parseFragmentReference();
82
                }
83 2
            } else {
84 36
                $fields[] = $this->parseBodyItem($token, $highLevel);
85
            }
86 31
        }
87
88 32
        $this->expect(Token::TYPE_RBRACE);
89
90 32
        return $fields;
91
    }
92
93 39
    protected function expect($type)
94
    {
95 39
        if ($this->match($type)) {
96 39
            return $this->lex();
97
        }
98
99 3
        throw $this->createUnexpected($this->peek());
100
    }
101
102 36
    protected function expectMulti($types)
103
    {
104 36
        if ($this->matchMulti($types)) {
105 36
            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 1
    protected function parseFragmentReference()
123
    {
124 1
        $name = $this->parseIdentifier();
125
126 1
        return new FragmentReference($name);
127
    }
128
129 36
    protected function parseIdentifier()
130
    {
131 36
        return $this->expectMulti([
132 36
            Token::TYPE_IDENTIFIER,
133 36
            Token::TYPE_MUTATION,
134 36
            Token::TYPE_QUERY,
135 36
            Token::TYPE_FRAGMENT,
136 36
        ])->getData();
137
    }
138
139 36
    protected function parseBodyItem($type = Token::TYPE_QUERY, $highLevel = true)
140
    {
141 36
        $name  = $this->parseIdentifier();
142 36
        $alias = null;
143
144 36
        if ($this->eat(Token::TYPE_COLON)) {
145 9
            $alias = $name;
146 9
            $name  = $this->parseIdentifier();
147 9
        }
148
149 36
        $arguments = $this->match(Token::TYPE_LPAREN) ? $this->parseArgumentList() : [];
150
151 31
        if ($this->match(Token::TYPE_LBRACE)) {
152 29
            $fields = $this->parseBody($type, false);
153
154 27
            if ($type == Token::TYPE_QUERY) {
155 26
                return new Query($name, $alias, $arguments, $fields);
156 2
            } elseif ($type == Token::TYPE_TYPED_FRAGMENT) {
157 1
                return new TypedFragmentReference($name, $fields);
158
            } else {
159 1
                return new Mutation($name, $alias, $arguments, $fields);
160
            }
161
        } else {
162 31
            if ($highLevel && $type == Token::TYPE_MUTATION) {
163 2
                return new Mutation($name, $alias, $arguments);
164
            }
165
166 29
            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 1
    protected function parseFragment()
309
    {
310 1
        $this->lex();
311 1
        $name = $this->parseIdentifier();
312
313 1
        $this->eat(Token::TYPE_ON);
314
315 1
        $model  = $this->parseIdentifier();
316 1
        $fields = $this->parseBody();
317
318 1
        return new Fragment($name, $model, $fields);
319
    }
320
321 38
    protected function eat($type)
322
    {
323 38
        if ($this->match($type)) {
324 17
            return $this->lex();
325
        }
326
327 37
        return null;
328
    }
329
330 36
    protected function matchMulti($types)
331
    {
332 36
        foreach ($types as $type) {
333 36
            if ($this->peek()->getType() == $type) {
334 36
                return true;
335
            }
336 7
        }
337
338 2
        return false;
339
    }
340
341 39
    protected function match($type)
342
    {
343 39
        return $this->peek()->getType() === $type;
344
    }
345
}