Passed
Push — master ( bae1fe...e6b663 )
by Edward
04:30
created

CallbackBuilder::addEvaluatorMethodCall()   A

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 3
dl 0
loc 6
ccs 0
cts 4
cp 0
crap 2
rs 10
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\Runtime\EvaluatorInterface;
18
use Remorhaz\JSON\Path\Value\ValueListInterface;
19
use Remorhaz\JSON\Path\Runtime\RuntimeInterface;
20
use Remorhaz\UniLex\AST\AbstractTranslatorListener;
21
use Remorhaz\UniLex\AST\Node as QueryAstNode;
22
use Remorhaz\UniLex\Exception as UniLexException;
23
use Remorhaz\UniLex\Stack\PushInterface;
24
25
final class CallbackBuilder extends AbstractTranslatorListener implements CallbackBuilderInterface
26
{
27
28
    private const ARG_RUNTIME = 'runtime';
29
30
    private const ARG_EVALUATOR = 'evaluator';
31
32
    private const ARG_INPUT = 'input';
33
34
    private $php;
35
36
    private $references = [];
37
38
    private $runtime;
39
40
    private $evaluator;
41
42
    private $input;
43
44
    private $stmts = [];
45
46
    private $queryCallback;
47
48
    private $capabilities;
49
50 9
    public function __construct()
51
    {
52 9
        $this->php = new BuilderFactory;
53 9
    }
54
55 4
    public function getCallback(): callable
56
    {
57 4
        if (isset($this->queryCallback)) {
58 3
            return $this->queryCallback;
59
        }
60
61 1
        throw new Exception\QueryCallbackNotFoundException;
62
    }
63
64 3
    public function getCapabilities(): CapabilitiesInterface
65
    {
66 3
        if (isset($this->capabilities)) {
67 2
            return $this->capabilities;
68
        }
69
70 1
        throw new Exception\CapabilitiesNotFoundException;
71
    }
72
73 5
    public function onStart(QueryAstNode $node): void
74
    {
75 5
        $this->runtime = $this->php->var(self::ARG_RUNTIME);
76 5
        $this->evaluator = $this->php->var(self::ARG_EVALUATOR);
77 5
        $this->input = $this->php->var(self::ARG_INPUT);
78 5
    }
79
80 3
    public function onFinish(): void
81
    {
82
        $inputParam = $this
83 3
            ->php
84 3
            ->param(self::ARG_INPUT)
85 3
            ->setType(NodeValueInterface::class)
86 3
            ->getNode();
87
        $runtimeParam = $this
88 3
            ->php
89 3
            ->param(self::ARG_RUNTIME)
90 3
            ->setType(RuntimeInterface::class)
91 3
            ->getNode();
92
        $evaluatorParam = $this
93 3
            ->php
94 3
            ->param(self::ARG_EVALUATOR)
95 3
            ->setType(EvaluatorInterface::class)
96 3
            ->getNode();
97 3
        $stmts = array_map(
98
            function (PhpAstNode $stmt): PhpAstNode {
99 2
                return $stmt instanceof Expr ? new Expression($stmt): $stmt;
100 3
            },
101 3
            $this->stmts
102
        );
103
104 3
        $closure = new Expr\Closure(
105
            [
106 3
                'stmts' => $stmts,
107
                'returnType' => ValueListInterface::class,
108 3
                'params' => [$inputParam, $runtimeParam, $evaluatorParam],
109
            ]
110
        );
111 3
        $return = new Return_($closure);
112
113 3
        $callbackCode = (new Standard)->prettyPrint([$return]);
114 3
        $this->queryCallback = eval($callbackCode);
115 3
    }
116
117 1
    public function onBeginProduction(QueryAstNode $node, PushInterface $stack): void
118
    {
119 1
        $stack->push(...array_reverse($node->getChildList()));
120 1
    }
121
122
    /**
123
     * @param QueryAstNode $node
124
     * @throws UniLexException
125
     */
126 5
    public function onFinishProduction(QueryAstNode $node): void
127
    {
128 5
        if ($this->hasReference($node)) {
129
            return;
130
        }
131 5
        switch ($node->getName()) {
132
            case AstNodeType::GET_INPUT:
133
                /** @see RuntimeInterface::getInput() */
134 4
                $this->addRuntimeMethodCall(
135 4
                    $node,
136 4
                    'getInput',
137 4
                    $this->input,
138
                );
139 4
                break;
140
141
            case AstNodeType::SET_OUTPUT:
142 5
                $this->capabilities = new Capabilities(
143 5
                    $node->getAttribute('is_definite'),
144 5
                    $node->getAttribute('is_path'),
145
                );
146 5
                $this->stmts[] = new Return_($this->getReference($node->getChild(0)));
147 4
                break;
148
149
            case AstNodeType::CREATE_FILTER_CONTEXT:
150
                /** @see RuntimeInterface::createFilterContext() */
151
                $this->addRuntimeMethodCall(
152
                    $node,
153
                    'createFilterContext',
154
                    $this->getReference($node->getChild(0)),
155
                );
156
                break;
157
158
            case AstNodeType::SPLIT_FILTER_CONTEXT:
159
                /** @see RuntimeInterface::splitFilterContext() */
160
                $this->addRuntimeMethodCall(
161
                    $node,
162
                    'splitFilterContext',
163
                    $this->getReference($node->getChild(0)),
164
                );
165
                break;
166
167
            case AstNodeType::JOIN_FILTER_RESULTS:
168
                /** @see RuntimeInterface::joinFilterResults() */
169
                $this->addRuntimeMethodCall(
170
                    $node,
171
                    'joinFilterResults',
172
                    $this->getReference($node->getChild(0)),
173
                    $this->getReference($node->getChild(1)),
174
                );
175
                break;
176
177
            case AstNodeType::EVALUATE:
178
                /** @see EvaluatorInterface::evaluate() */
179
                $this->addEvaluatorMethodCall(
180
                    $node,
181
                    'evaluate',
182
                    $this->getReference($node->getChild(0)),
183
                    $this->getReference($node->getChild(1)),
184
                );
185
                break;
186
187
            case AstNodeType::FILTER:
188
                /** @see RuntimeInterface::fetchFilteredValues() */
189
                $this->addRuntimeMethodCall(
190
                    $node,
191
                    'fetchFilteredValues',
192
                    $this->getReference($node->getChild(0)),
193
                    $this->getReference($node->getChild(1)),
194
                );
195
                break;
196
197
            case AstNodeType::EVALUATE_LOGICAL_OR:
198
                /** @see EvaluatorInterface::logicalOr() */
199
                $this->addEvaluatorMethodCall(
200
                    $node,
201
                    'logicalOr',
202
                    $this->getReference($node->getChild(0)),
203
                    $this->getReference($node->getChild(1)),
204
                );
205
                break;
206
207
            case AstNodeType::EVALUATE_LOGICAL_AND:
208
                /** @see EvaluatorInterface::logicalAnd() */
209
                $this->addEvaluatorMethodCall(
210
                    $node,
211
                    'logicalAnd',
212
                    $this->getReference($node->getChild(0)),
213
                    $this->getReference($node->getChild(1)),
214
                );
215
                break;
216
217
            case AstNodeType::EVALUATE_LOGICAL_NOT:
218
                /** @see EvaluatorInterface::logicalNot() */
219
                $this->addEvaluatorMethodCall(
220
                    $node,
221
                    'logicalNot',
222
                    $this->getReference($node->getChild(0)),
223
                );
224
                break;
225
226
            case AstNodeType::CALCULATE_IS_EQUAL:
227
                /** @see EvaluatorInterface::isEqual() */
228
                $this->addEvaluatorMethodCall(
229
                    $node,
230
                    'isEqual',
231
                    $this->getReference($node->getChild(0)),
232
                    $this->getReference($node->getChild(1)),
233
                );
234
                break;
235
236
            case AstNodeType::CALCULATE_IS_GREATER:
237
                /** @see EvaluatorInterface::isGreater() */
238
                $this->addEvaluatorMethodCall(
239
                    $node,
240
                    'isGreater',
241
                    $this->getReference($node->getChild(0)),
242
                    $this->getReference($node->getChild(1)),
243
                );
244
                break;
245
246
            case AstNodeType::CALCULATE_IS_REGEXP:
247
                /** @see EvaluatorInterface::isRegExp() */
248
                $this->addEvaluatorMethodCall(
249
                    $node,
250
                    'isRegExp',
251
                    $this->php->val($node->getAttribute('pattern')),
252
                    $this->getReference($node->getChild(0)),
253
                );
254
                break;
255
256
            case AstNodeType::FETCH_CHILDREN:
257
                /** @see RuntimeInterface::fetchChildren() */
258
                $this->addRuntimeMethodCall(
259
                    $node,
260
                    'fetchChildren',
261
                    $this->getReference($node->getChild(0)),
262
                    new Arg(
263
                        $this->getReference($node->getChild(1)),
264
                        false,
265
                        true,
266
                    ),
267
                );
268
                break;
269
270
            case AstNodeType::FETCH_CHILDREN_DEEP:
271
                /** @see RuntimeInterface::fetchChildrenDeep() */
272
                $this->addRuntimeMethodCall(
273
                    $node,
274
                    'fetchChildrenDeep',
275
                    $this->getReference($node->getChild(0)),
276
                    new Arg(
277
                        $this->getReference($node->getChild(1)),
278
                        false,
279
                        true,
280
                    ),
281
                );
282
                break;
283
284
            case AstNodeType::MATCH_ANY_CHILD:
285
                /** @see RuntimeInterface::matchAnyChild() */
286
                $this->addRuntimeMethodCall(
287
                    $node,
288
                    'matchAnyChild',
289
                    $this->getReference($node->getChild(0)),
290
                );
291
                break;
292
293
            case AstNodeType::MATCH_PROPERTY_STRICTLY:
294
                /** @see RuntimeInterface::matchPropertyStrictly() */
295
                $this->addRuntimeMethodCall(
296
                    $node,
297
                    'matchPropertyStrictly',
298
                    $this->getReference($node->getChild(0)),
299
                );
300
                break;
301
302
            case AstNodeType::MATCH_ELEMENT_STRICTLY:
303
                /** @see RuntimeInterface::matchElementStrictly() */
304
                $this->addRuntimeMethodCall(
305
                    $node,
306
                    'matchElementStrictly',
307
                    $this->getReference($node->getChild(0)),
308
                );
309
                break;
310
311
            case AstNodeType::MATCH_ELEMENT_SLICE:
312
                /** @see RuntimeInterface::matchElementSlice() */
313
                $this->addRuntimeMethodCall(
314
                    $node,
315
                    'matchElementSlice',
316
                    $this->getReference($node->getChild(0)),
317
                    $this
318
                        ->php
319
                        ->val($node->getAttribute('hasStart') ? $node->getAttribute('start') : null),
320
                    $this
321
                        ->php
322
                        ->val($node->getAttribute('hasEnd') ? $node->getAttribute('end') : null),
323
                    $this
324
                        ->php
325
                        ->val($node->getAttribute('step')),
326
                );
327
                break;
328
329
            case AstNodeType::AGGREGATE:
330
                /** @see EvaluatorInterface::aggregate() */
331
                $this->addEvaluatorMethodCall(
332
                    $node,
333
                    'aggregate',
334
                    $this->php->val($node->getAttribute('name')),
335
                    $this->getReference($node->getChild(0)),
336
                );
337
                break;
338
339
            case AstNodeType::POPULATE_LITERAL:
340
                /** @see RuntimeInterface::populateLiteral() */
341
                $this->addRuntimeMethodCall(
342
                    $node,
343
                    'populateLiteral',
344
                    $this->getReference($node->getChild(0)),
345
                    $this->getReference($node->getChild(1)),
346
                );
347
                break;
348
349
            case AstNodeType::POPULATE_ARRAY_ELEMENTS:
350
                /** @see RuntimeInterface::populateArrayElements() */
351
                $this->addRuntimeMethodCall(
352
                    $node,
353
                    'populateArrayElements',
354
                    $this->getReference($node->getChild(0)),
355
                    new Arg(
356
                        $this->getReference($node->getChild(1)),
357
                        false,
358
                        true,
359
                    ),
360
                );
361
                break;
362
363
            case AstNodeType::POPULATE_INDEX_LIST:
364
                /** @see RuntimeInterface::populateIndexList() */
365
                $this->addRuntimeMethodCall(
366
                    $node,
367
                    'populateIndexList',
368
                    $this->getReference($node->getChild(0)),
369
                    ...array_map(
370
                        [$this->php, 'val'],
371
                        $node->getAttribute('indexList'),
372
                    ),
373
                );
374
                break;
375
376
            case AstNodeType::POPULATE_NAME_LIST:
377
                /** @see RuntimeInterface::populateNameList() */
378
                $this->addRuntimeMethodCall(
379
                    $node,
380
                    'populateNameList',
381
                    $this->getReference($node->getChild(0)),
382
                    ...array_map(
383
                        [$this->php, 'val'],
384
                        $node->getAttribute('nameList'),
385
                    ),
386
                );
387
                break;
388
389
            case AstNodeType::CREATE_SCALAR:
390
                $attributes = $node->getAttributeList();
391
                // TODO: allow accessing null attributes
392
                $value = $this->php->val($attributes['value'] ?? null);
393
                /** @see RuntimeInterface::createScalar() */
394
                $this->addRuntimeMethodCall(
395
                    $node,
396
                    'createScalar',
397
                    $value,
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
            case AstNodeType::CREATE_LITERAL_ARRAY:
422
                /** @see RuntimeInterface::createArray() */
423
                $this->addRuntimeMethodCall(
424
                    $node,
425
                    'createArray',
426
                    $this->getReference($node->getChild(0)),
427
                    new Arg(
428
                        $this->getReference($node->getChild(1)),
429
                        false,
430
                        true,
431
                    ),
432
                );
433
                break;
434
        }
435 4
    }
436
437 4
    private function getVarName(QueryAstNode $node): string
438
    {
439 4
        return "var{$node->getId()}";
440
    }
441
442 4
    private function createReference(QueryAstNode $node): Expr
443
    {
444 4
        $reference = $this->php->var($this->getVarName($node));
445 4
        $this->setReference($node, $reference);
446
447 4
        return $reference;
448
    }
449
450 4
    private function setReference(QueryAstNode $node, Expr $expr): void
451
    {
452 4
        if (isset($this->references[$node->getId()])) {
453
            throw new Exception\ReferenceAlreadyExistsException($node->getId());
454
        }
455
456 4
        $this->references[$node->getId()] = $expr;
457 4
    }
458
459 5
    private function hasReference(QueryAstNode $node): bool
460
    {
461 5
        return isset($this->references[$node->getId()]);
462
    }
463
464 5
    private function getReference(QueryAstNode $node): Expr
465
    {
466 5
        if (!isset($this->references[$node->getId()])) {
467 1
            throw new Exception\ReferenceNotFoundException($node->getId());
468
        }
469
470 4
        return $this->references[$node->getId()];
471
    }
472
473 4
    private function addRuntimeMethodCall(QueryAstNode $node, string $method, PhpAstNode ...$args): void
474
    {
475
        $methodCall = $this
476 4
            ->php
477 4
            ->methodCall($this->runtime, $method, $args);
478 4
        $this->stmts[] = new Assign($this->createReference($node), $methodCall);
479 4
    }
480
481
    private function addEvaluatorMethodCall(QueryAstNode $node, string $method, PhpAstNode ...$args): void
482
    {
483
        $methodCall = $this
484
            ->php
485
            ->methodCall($this->evaluator, $method, $args);
486
        $this->stmts[] = new Assign($this->createReference($node), $methodCall);
487
    }
488
}
489