CallbackBuilder::addMethodCall()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 4
c 0
b 0
f 0
nc 1
nop 4
dl 0
loc 6
ccs 0
cts 4
cp 0
crap 2
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Remorhaz\JSON\Path\Query;
6
7
use PhpParser\BuilderFactory;
8
use PhpParser\Node as PhpAstNode;
9
use PhpParser\Node\Arg;
10
use PhpParser\Node\Expr;
11
use PhpParser\Node\Expr\Assign;
12
use PhpParser\Node\Stmt\Expression;
13
use PhpParser\Node\Stmt\Return_;
14
use PhpParser\PrettyPrinter\Standard;
15
use Remorhaz\JSON\Path\Runtime\EvaluatorInterface;
16
use Remorhaz\JSON\Path\Runtime\LiteralFactoryInterface;
17
use Remorhaz\JSON\Path\Runtime\Matcher\MatcherFactoryInterface;
18
use Remorhaz\JSON\Path\Runtime\ValueListFetcherInterface;
19
use Remorhaz\JSON\Path\Value\NodeValueListInterface;
20
use Remorhaz\JSON\Path\Value\ValueListInterface;
21
use Remorhaz\UniLex\AST\AbstractTranslatorListener;
22
use Remorhaz\UniLex\AST\Node as QueryAstNode;
23
use Remorhaz\UniLex\Exception as UniLexException;
24
use Remorhaz\UniLex\Stack\PushInterface;
25
26
use function array_map;
27
use function array_reverse;
28
29
final class CallbackBuilder extends AbstractTranslatorListener implements CallbackBuilderInterface
30
{
31
32
    private const ARG_INPUT = 'input';
33
34
    private const ARG_VALUE_LIST_FETCHER = 'valueListFetcher';
35
36
    private const ARG_EVALUATOR = 'evaluator';
37
38
    private const ARG_LITERAL_FACTORY = 'literalFactory';
39
40
    private const ARG_MATCHER_FACTORY = 'matcherFactory';
41
42
    private $php;
43
44
    private $references = [];
45
46
    private $input;
47
48
    private $valueListFetcher;
49
50
    private $evaluator;
51
52
    private $literalFactory;
53
54
    private $matcherFactory;
55
56
    private $stmts = [];
57
58
    private $callback;
59
60
    private $callbackCode;
61
62
    private $capabilities;
63
64 6
    public function __construct()
65
    {
66 6
        $this->php = new BuilderFactory();
67 6
    }
68
69
    /**
70
     * @noinspection PhpUnusedParameterInspection
71
     */
72
    public function getCallback(): callable
73
    {
74
        if (!isset($this->callback)) {
75
            $this->callback = function (
76
                NodeValueListInterface $input,
0 ignored issues
show
Unused Code introduced by
The parameter $input is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

76
                /** @scrutinizer ignore-unused */ NodeValueListInterface $input,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
77
                ValueListFetcherInterface $valueListFetcher,
0 ignored issues
show
Unused Code introduced by
The parameter $valueListFetcher is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

77
                /** @scrutinizer ignore-unused */ ValueListFetcherInterface $valueListFetcher,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
78
                EvaluatorInterface $evaluator,
0 ignored issues
show
Unused Code introduced by
The parameter $evaluator is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

78
                /** @scrutinizer ignore-unused */ EvaluatorInterface $evaluator,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
79
                LiteralFactoryInterface $literalFactory,
0 ignored issues
show
Unused Code introduced by
The parameter $literalFactory is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

79
                /** @scrutinizer ignore-unused */ LiteralFactoryInterface $literalFactory,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
80
                MatcherFactoryInterface $matcherFactory
0 ignored issues
show
Unused Code introduced by
The parameter $matcherFactory is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

80
                /** @scrutinizer ignore-unused */ MatcherFactoryInterface $matcherFactory

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
81
            ): ValueListInterface {
82
                return eval($this->getCallbackCode());
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
83
            };
84
        }
85
86
        return $this->callback;
87
    }
88
89 1
    public function getCallbackCode(): string
90
    {
91 1
        if (isset($this->callbackCode)) {
92
            return $this->callbackCode;
93
        }
94
95 1
        throw new Exception\QueryCallbackCodeNotFoundException();
96
    }
97
98 3
    public function getCapabilities(): CapabilitiesInterface
99
    {
100 3
        if (isset($this->capabilities)) {
101 2
            return $this->capabilities;
102
        }
103
104 1
        throw new Exception\CapabilitiesNotFoundException();
105
    }
106
107 3
    public function onStart(QueryAstNode $node): void
108
    {
109 3
        $this->input = $this->php->var(self::ARG_INPUT);
110 3
        $this->valueListFetcher = $this->php->var(self::ARG_VALUE_LIST_FETCHER);
111 3
        $this->evaluator = $this->php->var(self::ARG_EVALUATOR);
112 3
        $this->literalFactory = $this->php->var(self::ARG_LITERAL_FACTORY);
113 3
        $this->matcherFactory = $this->php->var(self::ARG_MATCHER_FACTORY);
114
115 3
        $this->stmts = [];
116 3
        $this->references = [];
117 3
        unset($this->callback, $this->callbackCode, $this->capabilities);
118 3
    }
119
120
    public function onFinish(): void
121
    {
122
        $stmts = array_map(
123
            function (PhpAstNode $stmt): PhpAstNode {
124
                return $stmt instanceof Expr ? new Expression($stmt) : $stmt;
125
            },
126
            $this->stmts
127
        );
128
129
        $this->callbackCode = (new Standard())->prettyPrint($stmts);
130
    }
131
132 1
    public function onBeginProduction(QueryAstNode $node, PushInterface $stack): void
133
    {
134 1
        $stack->push(...array_reverse($node->getChildList()));
135 1
    }
136
137
    /**
138
     * @param QueryAstNode $node
139
     * @throws UniLexException
140
     */
141 3
    public function onFinishProduction(QueryAstNode $node): void
142
    {
143 3
        if ($this->hasReference($node)) {
144
            return;
145
        }
146 3
        switch ($node->getName()) {
147 3
            case AstNodeType::GET_INPUT:
148 2
                $this->setReference($node, $this->input);
149 2
                break;
150
151 3
            case AstNodeType::SET_OUTPUT:
152 3
                $this->capabilities = new Capabilities(
153 3
                    $node->getAttribute('is_definite'),
154 3
                    $node->getAttribute('is_addressable'),
155
                );
156 3
                $this->stmts[] = new Return_($this->getReference($node->getChild(0)));
157 2
                break;
158
159
            case AstNodeType::FETCH_CHILDREN:
160
                /** @see ValueListFetcherInterface::fetchChildren() */
161
                $this->addMethodCall(
162
                    $node,
163
                    $this->valueListFetcher,
164
                    'fetchChildren',
165
                    $this->getReference($node->getChild(0)),
166
                    $this->getReference($node->getChild(1)),
167
                );
168
                break;
169
170
            case AstNodeType::FETCH_CHILDREN_DEEP:
171
                /** @see ValueListFetcherInterface::fetchChildrenDeep() */
172
                $this->addMethodCall(
173
                    $node,
174
                    $this->valueListFetcher,
175
                    'fetchChildrenDeep',
176
                    $this->getReference($node->getChild(0)),
177
                    $this->getReference($node->getChild(1)),
178
                );
179
                break;
180
181
            case AstNodeType::MERGE:
182
                /** @see ValueListFetcherInterface::merge() */
183
                $this->addMethodCall(
184
                    $node,
185
                    $this->valueListFetcher,
186
                    'merge',
187
                    ...$this->getReferences(...$node->getChildList()),
188
                );
189
                break;
190
191
            case AstNodeType::FETCH_FILTER_CONTEXT:
192
                /** @see ValueListFetcherInterface::fetchFilterContext() */
193
                $this->addMethodCall(
194
                    $node,
195
                    $this->valueListFetcher,
196
                    'fetchFilterContext',
197
                    $this->getReference($node->getChild(0)),
198
                );
199
                break;
200
201
            case AstNodeType::SPLIT_FILTER_CONTEXT:
202
                /** @see ValueListFetcherInterface::splitFilterContext() */
203
                $this->addMethodCall(
204
                    $node,
205
                    $this->valueListFetcher,
206
                    'splitFilterContext',
207
                    $this->getReference($node->getChild(0)),
208
                );
209
                break;
210
211
            case AstNodeType::JOIN_FILTER_RESULTS:
212
                /** @see ValueListFetcherInterface::joinFilterResults() */
213
                $this->addMethodCall(
214
                    $node,
215
                    $this->valueListFetcher,
216
                    'joinFilterResults',
217
                    $this->getReference($node->getChild(0)),
218
                    $this->getReference($node->getChild(1)),
219
                );
220
                break;
221
222
            case AstNodeType::FILTER:
223
                /** @see ValueListFetcherInterface::fetchFilteredValues() */
224
                $this->addMethodCall(
225
                    $node,
226
                    $this->valueListFetcher,
227
                    'fetchFilteredValues',
228
                    $this->getReference($node->getChild(0)),
229
                    $this->getReference($node->getChild(1)),
230
                );
231
                break;
232
233
            case AstNodeType::EVALUATE:
234
                /** @see EvaluatorInterface::evaluate() */
235
                $this->addMethodCall(
236
                    $node,
237
                    $this->evaluator,
238
                    'evaluate',
239
                    $this->getReference($node->getChild(0)),
240
                    $this->getReference($node->getChild(1)),
241
                );
242
                break;
243
244
            case AstNodeType::EVALUATE_LOGICAL_OR:
245
                /** @see EvaluatorInterface::logicalOr() */
246
                $this->addMethodCall(
247
                    $node,
248
                    $this->evaluator,
249
                    'logicalOr',
250
                    $this->getReference($node->getChild(0)),
251
                    $this->getReference($node->getChild(1)),
252
                );
253
                break;
254
255
            case AstNodeType::EVALUATE_LOGICAL_AND:
256
                /** @see EvaluatorInterface::logicalAnd() */
257
                $this->addMethodCall(
258
                    $node,
259
                    $this->evaluator,
260
                    'logicalAnd',
261
                    $this->getReference($node->getChild(0)),
262
                    $this->getReference($node->getChild(1)),
263
                );
264
                break;
265
266
            case AstNodeType::EVALUATE_LOGICAL_NOT:
267
                /** @see EvaluatorInterface::logicalNot() */
268
                $this->addMethodCall(
269
                    $node,
270
                    $this->evaluator,
271
                    'logicalNot',
272
                    $this->getReference($node->getChild(0)),
273
                );
274
                break;
275
276
            case AstNodeType::CALCULATE_IS_EQUAL:
277
                /** @see EvaluatorInterface::isEqual() */
278
                $this->addMethodCall(
279
                    $node,
280
                    $this->evaluator,
281
                    'isEqual',
282
                    $this->getReference($node->getChild(0)),
283
                    $this->getReference($node->getChild(1)),
284
                );
285
                break;
286
287
            case AstNodeType::CALCULATE_IS_GREATER:
288
                /** @see EvaluatorInterface::isGreater() */
289
                $this->addMethodCall(
290
                    $node,
291
                    $this->evaluator,
292
                    'isGreater',
293
                    $this->getReference($node->getChild(0)),
294
                    $this->getReference($node->getChild(1)),
295
                );
296
                break;
297
298
            case AstNodeType::CALCULATE_IS_REGEXP:
299
                /** @see EvaluatorInterface::isRegExp() */
300
                $this->addMethodCall(
301
                    $node,
302
                    $this->evaluator,
303
                    'isRegExp',
304
                    $this->php->val($node->getAttribute('pattern')),
305
                    $this->getReference($node->getChild(0)),
306
                );
307
                break;
308
309
            case AstNodeType::MATCH_ANY_CHILD:
310
                /** @see MatcherFactoryInterface::matchAnyChild() */
311
                $this->addMethodCall(
312
                    $node,
313
                    $this->matcherFactory,
314
                    'matchAnyChild',
315
                );
316
                break;
317
318
            case AstNodeType::MATCH_PROPERTY_STRICTLY:
319
                /** @see MatcherFactoryInterface::matchPropertyStrictly() */
320
                $this->addMethodCall(
321
                    $node,
322
                    $this->matcherFactory,
323
                    'matchPropertyStrictly',
324
                    ...array_map(
325
                        [$this->php, 'val'],
326
                        $node->getAttribute('names')
327
                    ),
328
                );
329
                break;
330
331
            case AstNodeType::MATCH_ELEMENT_STRICTLY:
332
                /** @see MatcherFactoryInterface::matchElementStrictly() */
333
                $this->addMethodCall(
334
                    $node,
335
                    $this->matcherFactory,
336
                    'matchElementStrictly',
337
                    ...array_map(
338
                        [$this->php, 'val'],
339
                        $node->getAttribute('indexes')
340
                    ),
341
                );
342
                break;
343
344
            case AstNodeType::MATCH_ELEMENT_SLICE:
345
                /** @see MatcherFactoryInterface::matchElementSlice() */
346
                $this->addMethodCall(
347
                    $node,
348
                    $this->matcherFactory,
349
                    'matchElementSlice',
350
                    $this
351
                        ->php
352
                        ->val($node->getAttribute('hasStart') ? $node->getAttribute('start') : null),
353
                    $this
354
                        ->php
355
                        ->val($node->getAttribute('hasEnd') ? $node->getAttribute('end') : null),
356
                    $this
357
                        ->php
358
                        ->val($node->getAttribute('step')),
359
                );
360
                break;
361
362
            case AstNodeType::AGGREGATE:
363
                /** @see EvaluatorInterface::aggregate() */
364
                $this->addMethodCall(
365
                    $node,
366
                    $this->evaluator,
367
                    'aggregate',
368
                    $this->php->val($node->getAttribute('name')),
369
                    $this->getReference($node->getChild(0)),
370
                );
371
                break;
372
373
            case AstNodeType::CREATE_LITERAL_SCALAR:
374
                $attributes = $node->getAttributeList();
375
                $value = $attributes['value'] ?? null; // TODO: allow pass null in attribute
376
                /** @see LiteralFactoryInterface::createScalar() */
377
                $this->addMethodCall(
378
                    $node,
379
                    $this->literalFactory,
380
                    'createScalar',
381
                    $this->getReference($node->getChild(0)),
382
                    $this->php->val($value),
383
                );
384
                break;
385
386
            case AstNodeType::CREATE_LITERAL_ARRAY:
387
                /** @see LiteralFactoryInterface::createArray() */
388
                $this->addMethodCall(
389
                    $node,
390
                    $this->literalFactory,
391
                    'createArray',
392
                    $this->getReference($node->getChild(0)),
393
                    new Arg(
394
                        $this->getReference($node->getChild(1)),
395
                        false,
396
                        true,
397
                    ),
398
                );
399
                break;
400
401
            case AstNodeType::CREATE_ARRAY:
402
                // [ X:APPEND_TO_ARRAY ]
403
                $items = [];
404
                foreach ($node->getChildList() as $child) {
405
                    $items[] = $this->getReference($child);
406
                }
407
                $this->stmts[] = new Assign(
408
                    $this->createReference($node),
409
                    $this->php->val($items),
410
                );
411
                break;
412
413
            case AstNodeType::APPEND_TO_ARRAY:
414
                // [ 0:<value> ]
415
                $this->setReference(
416
                    $node,
417
                    $this->getReference($node->getChild(0)),
418
                );
419
                break;
420
        }
421 2
    }
422
423
    private function getVarName(QueryAstNode $node): string
424
    {
425
        return "var{$node->getId()}";
426
    }
427
428
    private function createReference(QueryAstNode $node): Expr
429
    {
430
        $reference = $this->php->var($this->getVarName($node));
431
        $this->setReference($node, $reference);
432
433
        return $reference;
434
    }
435
436 2
    private function setReference(QueryAstNode $node, Expr $expr): void
437
    {
438 2
        if (isset($this->references[$node->getId()])) {
439
            throw new Exception\ReferenceAlreadyExistsException($node->getId());
440
        }
441
442 2
        $this->references[$node->getId()] = $expr;
443 2
    }
444
445 3
    private function hasReference(QueryAstNode $node): bool
446
    {
447 3
        return isset($this->references[$node->getId()]);
448
    }
449
450 3
    private function getReference(QueryAstNode $node): Expr
451
    {
452 3
        if (!isset($this->references[$node->getId()])) {
453 1
            throw new Exception\ReferenceNotFoundException($node->getId());
454
        }
455
456 2
        return $this->references[$node->getId()];
457
    }
458
459
    /**
460
     * @param QueryAstNode ...$nodes
461
     * @return Expr[]
462
     */
463
    private function getReferences(QueryAstNode ...$nodes): array
464
    {
465
        return array_map([$this, 'getReference'], $nodes);
466
    }
467
468
    private function addMethodCall(QueryAstNode $node, Expr $object, string $method, PhpAstNode ...$args): void
469
    {
470
        $methodCall = $this
471
            ->php
472
            ->methodCall($object, $method, $args);
473
        $this->stmts[] = new Assign($this->createReference($node), $methodCall);
474
    }
475
}
476