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