Passed
Push — master ( da0f52...6413a2 )
by Edward
03:06
created

CallbackBuilder::onFinishProduction()   F

Complexity

Conditions 29
Paths 29

Size

Total Lines 257
Code Lines 193

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 29
eloc 193
nc 29
nop 1
dl 0
loc 257
rs 3.3333
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
declare(strict_types=1);
3
4
namespace Remorhaz\JSON\Path\Query;
5
6
use function array_map;
7
use function array_reverse;
8
use PhpParser\BuilderFactory;
9
use PhpParser\Node as PhpAstNode;
10
use PhpParser\Node\Arg;
11
use PhpParser\Node\Expr;
12
use PhpParser\Node\Expr\Assign;
13
use PhpParser\Node\Stmt\Expression;
14
use PhpParser\Node\Stmt\Return_;
15
use PhpParser\PrettyPrinter\Standard;
16
use Remorhaz\JSON\Data\Value\NodeValueInterface;
17
use Remorhaz\JSON\Path\Value\ValueListInterface;
18
use Remorhaz\JSON\Path\Runtime\RuntimeInterface;
19
use Remorhaz\UniLex\AST\AbstractTranslatorListener;
20
use Remorhaz\UniLex\AST\Node as QueryAstNode;
21
use Remorhaz\UniLex\Exception as UniLexException;
22
use Remorhaz\UniLex\Stack\PushInterface;
23
24
final class CallbackBuilder extends AbstractTranslatorListener implements CallbackBuilderInterface
25
{
26
27
    private const ARG_RUNTIME = 'runtime';
28
29
    private const ARG_INPUT = 'input';
30
31
    private $php;
32
33
    private $references = [];
34
35
    private $runtime;
36
37
    private $input;
38
39
    private $stmts = [];
40
41
    private $queryCallback;
42
43
    private $capabilities;
44
45
    public function __construct()
46
    {
47
        $this->php = new BuilderFactory;
48
    }
49
50
    public function getQueryCallback(): callable
51
    {
52
        if (isset($this->queryCallback)) {
53
            return $this->queryCallback;
54
        }
55
56
        throw new Exception\QueryCallbackNotFoundException;
57
    }
58
59
    public function getQueryCapabilities(): CapabilitiesInterface
60
    {
61
        if (isset($this->capabilities)) {
62
            return $this->capabilities;
63
        }
64
65
        throw new Exception\CapabilitiesNotFoundException;
66
    }
67
68
    public function onStart(QueryAstNode $node): void
69
    {
70
        $this->runtime = $this->php->var(self::ARG_RUNTIME);
71
        $this->input = $this->php->var(self::ARG_INPUT);
72
    }
73
74
    public function onFinish(): void
75
    {
76
        $runtimeParam = $this
77
            ->php
78
            ->param(self::ARG_RUNTIME)
79
            ->setType(RuntimeInterface::class)
80
            ->getNode();
81
        $inputParam = $this
82
            ->php
83
            ->param(self::ARG_INPUT)
84
            ->setType(NodeValueInterface::class)
85
            ->getNode();
86
        $stmts = array_map(
87
            function (PhpAstNode $stmt): PhpAstNode {
88
                return $stmt instanceof Expr ? new Expression($stmt): $stmt;
89
            },
90
            $this->stmts
91
        );
92
93
        $closure = new Expr\Closure(
94
            [
95
                'stmts' => $stmts,
96
                'returnType' => ValueListInterface::class,
97
                'params' => [$runtimeParam, $inputParam],
98
            ]
99
        );
100
        $return = new Return_($closure);
101
102
        $callbackCode = (new Standard)->prettyPrint([$return]);
103
        $this->queryCallback = eval($callbackCode);
104
    }
105
106
    public function onBeginProduction(QueryAstNode $node, PushInterface $stack): void
107
    {
108
        $stack->push(...array_reverse($node->getChildList()));
109
    }
110
111
    /**
112
     * @param QueryAstNode $node
113
     * @throws UniLexException
114
     */
115
    public function onFinishProduction(QueryAstNode $node): void
116
    {
117
        if ($this->hasReference($node)) {
118
            return;
119
        }
120
        switch ($node->getName()) {
121
            case AstNodeType::GET_INPUT:
122
                $this->addMethodCall(
123
                    $node,
124
                    'getInput',
125
                    $this->input
126
                );
127
                break;
128
129
            case AstNodeType::SET_OUTPUT:
130
                $this->capabilities = new Capabilities(
131
                    $node->getAttribute('is_definite'),
132
                    $node->getAttribute('is_path')
133
                );
134
                $this->stmts[] = new Return_($this->getReference($node->getChild(0)));
135
                break;
136
137
            case AstNodeType::CREATE_FILTER_CONTEXT:
138
                $this->addMethodCall(
139
                    $node,
140
                    'createFilterContext',
141
                    $this->getReference($node->getChild(0))
142
                );
143
                break;
144
145
            case AstNodeType::SPLIT:
146
                $this->addMethodCall(
147
                    $node,
148
                    'split',
149
                    $this->getReference($node->getChild(0))
150
                );
151
                break;
152
153
            case AstNodeType::EVALUATE:
154
                $this->addMethodCall(
155
                    $node,
156
                    'evaluate',
157
                    $this->getReference($node->getChild(0)),
158
                    $this->getReference($node->getChild(1))
159
                );
160
                break;
161
162
            case AstNodeType::FILTER:
163
                $this->addMethodCall(
164
                    $node,
165
                    'filter',
166
                    $this->getReference($node->getChild(0)),
167
                    $this->getReference($node->getChild(1))
168
                );
169
                break;
170
171
            case AstNodeType::EVALUATE_LOGICAL_OR:
172
                $this->addMethodCall(
173
                    $node,
174
                    'evaluateLogicalOr',
175
                    $this->getReference($node->getChild(0)),
176
                    $this->getReference($node->getChild(1))
177
                );
178
                break;
179
180
            case AstNodeType::EVALUATE_LOGICAL_AND:
181
                $this->addMethodCall(
182
                    $node,
183
                    'evaluateLogicalAnd',
184
                    $this->getReference($node->getChild(0)),
185
                    $this->getReference($node->getChild(1))
186
                );
187
                break;
188
189
            case AstNodeType::EVALUATE_LOGICAL_NOT:
190
                $this->addMethodCall(
191
                    $node,
192
                    'evaluateLogicalNot',
193
                    $this->getReference($node->getChild(0))
194
                );
195
                break;
196
197
            case AstNodeType::CALCULATE_IS_EQUAL:
198
                $this->addMethodCall(
199
                    $node,
200
                    'calculateIsEqual',
201
                    $this->getReference($node->getChild(0)),
202
                    $this->getReference($node->getChild(1))
203
                );
204
                break;
205
206
            case AstNodeType::CALCULATE_IS_GREATER:
207
                $this->addMethodCall(
208
                    $node,
209
                    'calculateIsGreater',
210
                    $this->getReference($node->getChild(0)),
211
                    $this->getReference($node->getChild(1))
212
                );
213
                break;
214
215
            case AstNodeType::CALCULATE_IS_REGEXP:
216
                $this->addMethodCall(
217
                    $node,
218
                    'calculateIsRegExp',
219
                    $this->php->val($node->getAttribute('pattern')),
220
                    $this->getReference($node->getChild(0))
221
                );
222
                break;
223
224
            case AstNodeType::FETCH_CHILDREN:
225
                $this->addMethodCall(
226
                    $node,
227
                    'fetchChildren',
228
                    $this->getReference($node->getChild(0)),
229
                    new Arg(
230
                        $this->getReference($node->getChild(1)),
231
                        false,
232
                        true
233
                    )
234
                );
235
                break;
236
237
            case AstNodeType::FETCH_CHILDREN_DEEP:
238
                $this->addMethodCall(
239
                    $node,
240
                    'fetchChildrenDeep',
241
                    $this->getReference($node->getChild(0)),
242
                    new Arg(
243
                        $this->getReference($node->getChild(1)),
244
                        false,
245
                        true
246
                    )
247
                );
248
                break;
249
250
            case AstNodeType::MATCH_ANY_CHILD:
251
                $this->addMethodCall(
252
                    $node,
253
                    'matchAnyChild',
254
                    $this->getReference($node->getChild(0))
255
                );
256
                break;
257
258
            case AstNodeType::MATCH_PROPERTY_STRICTLY:
259
                $this->addMethodCall(
260
                    $node,
261
                    'matchPropertyStrictly',
262
                    $this->getReference($node->getChild(0)),
263
                );
264
                break;
265
266
            case AstNodeType::MATCH_ELEMENT_STRICTLY:
267
                $this->addMethodCall(
268
                    $node,
269
                    'matchElementStrictly',
270
                    $this->getReference($node->getChild(0)),
271
                );
272
                break;
273
274
            case AstNodeType::AGGREGATE:
275
                $this->addMethodCall(
276
                    $node,
277
                    'aggregate',
278
                    $this->php->val($node->getAttribute('name')),
279
                    $this->getReference($node->getChild(0)),
280
                );
281
                break;
282
283
            case AstNodeType::POPULATE_LITERAL:
284
                $this->addMethodCall(
285
                    $node,
286
                    'populateLiteral',
287
                    $this->getReference($node->getChild(0)),
288
                    $this->getReference($node->getChild(1))
289
                );
290
                break;
291
292
            case AstNodeType::POPULATE_LITERAL_ARRAY:
293
                $this->addMethodCall(
294
                    $node,
295
                    'populateLiteralArray',
296
                    $this->getReference($node->getChild(0)),
297
                    new Arg(
298
                        $this->getReference($node->getChild(1)),
299
                        false,
300
                        true
301
                    )
302
                );
303
                break;
304
305
            case AstNodeType::POPULATE_INDEX_LIST:
306
                $this->addMethodCall(
307
                    $node,
308
                    'populateIndexList',
309
                    $this->getReference($node->getChild(0)),
310
                    ...array_map(
311
                        [$this->php, 'val'],
312
                        $node->getAttribute('indexList')
313
                    )
314
                );
315
                break;
316
317
            case AstNodeType::POPULATE_INDEX_SLICE:
318
                $attributes = $node->getAttributeList();
319
                $this->addMethodCall(
320
                    $node,
321
                    'populateIndexSlice',
322
                    $this->getReference($node->getChild(0)),
323
                    $this->php->val($attributes['start'] ?? null),
324
                    $this->php->val($attributes['end'] ?? null),
325
                    $this->php->val($attributes['step'] ?? null)
326
                );
327
328
                break;
329
330
            case AstNodeType::POPULATE_NAME_LIST:
331
                $this->addMethodCall(
332
                    $node,
333
                    'populateNameList',
334
                    $this->getReference($node->getChild(0)),
335
                    ...array_map(
336
                        [$this->php, 'val'],
337
                        $node->getAttribute('nameList')
338
                    )
339
                );
340
                break;
341
342
            case AstNodeType::CREATE_SCALAR:
343
                $attributes = $node->getAttributeList();
344
                // TODO: allow accessing null attributes
345
                $value = $this->php->val($attributes['value'] ?? null);
346
                $this->addMethodCall(
347
                    $node,
348
                    'createScalar',
349
                    $value
350
                );
351
                break;
352
353
            case AstNodeType::CREATE_ARRAY:
354
                // [ X:APPEND_TO_ARRAY ]
355
                $items = [];
356
                foreach ($node->getChildList() as $child) {
357
                    $items[] = $this->getReference($child);
358
                }
359
                $this->stmts[] = new Assign(
360
                    $this->createReference($node),
361
                    $this->php->val($items)
362
                );
363
                break;
364
365
            case AstNodeType::APPEND_TO_ARRAY:
366
                // [ 0:<value> ]
367
                $this->setReference(
368
                    $node,
369
                    $this->getReference($node->getChild(0))
370
                );
371
                break;
372
        }
373
    }
374
375
    private function getVarName(QueryAstNode $node): string
376
    {
377
        return "var{$node->getId()}";
378
    }
379
380
    private function createReference(QueryAstNode $node): Expr
381
    {
382
        $reference = $this->php->var($this->getVarName($node));
383
        $this->setReference($node, $reference);
384
385
        return $reference;
386
    }
387
388
    private function setReference(QueryAstNode $node, Expr $expr): void
389
    {
390
        if (isset($this->references[$node->getId()])) {
391
            throw new Exception\ReferenceAlreadyExistsException($node->getId());
392
        }
393
394
        $this->references[$node->getId()] = $expr;
395
    }
396
397
    private function hasReference(QueryAstNode $node): bool
398
    {
399
        return isset($this->references[$node->getId()]);
400
    }
401
402
    private function getReference(QueryAstNode $node): Expr
403
    {
404
        if (!isset($this->references[$node->getId()])) {
405
            throw new Exception\ReferenceNotFoundException($node->getId());
406
        }
407
408
        return $this->references[$node->getId()];
409
    }
410
411
    private function addMethodCall(QueryAstNode $node, string $method, PhpAstNode ...$args): void
412
    {
413
        $methodCall = $this
414
            ->php
415
            ->methodCall($this->runtime, $method, $args);
416
        $this->stmts[] = new Assign($this->createReference($node), $methodCall);
417
    }
418
}
419