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