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
|
|
|
} |