packages/deriver/tests/Unit/Deriver/Evaluator/Recurrence/CandidatesTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Deriver\Evaluator\Recurrence;
6
7use Deriver\Deriver\Evaluator\Call\Evaluators;
8use Deriver\Deriver\Evaluator\Enumeration\Choices;
9use Deriver\Deriver\Evaluator\Evaluator;
10use Deriver\Deriver\Evaluator\EvaluatorConfig;
11use Deriver\Deriver\Evaluator\Index\DefinitionIndex;
12use Deriver\Deriver\Evaluator\Recurrence\Candidates;
13use Deriver\Deriver\Evaluator\Request\Budget;
14use Deriver\Deriver\Evaluator\Scope\Context;
15use Deriver\Deriver\Evaluator\Scope\Frame;
16use Deriver\Deriver\Evaluator\Term;
17use Deriver\Deriver\Evaluator\TermKind;
18use Deriver\Deriver\Lowerer\Declaration\Index;
19use Deriver\Deriver\Parser\Parser;
20use Deriver\Deriver\Parser\ParserConfig;
21use Deriver\Deriver\Resolver\Resolver;
22use Deriver\Deriver\Sources\Source;
23use PHPUnit\Framework\Attributes\CoversNothing;
24use PHPUnit\Framework\TestCase;
25
26#[CoversNothing]
27final class CandidatesTest extends TestCase
28{
29    public function testValueKeepsAnInitialValueAndAnIndependentWrite(): void
30    {
31        $tree = (new Resolver())->resolve((new Parser(new ParserConfig('8.3')))->parse(new Source('loop.php', '<?php function subject(){$x=1;while(missing()){$x=7;}return $x;}')));
32        $engine = new Evaluator(new Context(new DefinitionIndex(new Index('loop', [$tree])), new EvaluatorConfig(), new Evaluators(), new Budget()));
33        $graph = $engine->context->index->graph('subject');
34        self::assertNotNull($graph);
35        $frame = new Frame($graph, 'source:subject');
36        $value = $engine->returns($frame, 64);
37        $values = array_map(static fn (array $alternative) => $alternative[0]->native(), iterator_to_array((new Choices())->alternatives($value), false));
38        self::assertSame([1, 7], $values);
39        self::assertSame(['subject:$x' => 1], $engine->context->references);
40    }
41
42    public function testUpdateRetainsThePreviousValueInADependentUpdate(): void
43    {
44        $tree = (new Resolver())->resolve((new Parser(new ParserConfig('8.3')))->parse(new Source('loop.php', '<?php function subject(){$x=1;while(missing()){$x+=2;}return $x;}')));
45        $engine = new Evaluator(new Context(new DefinitionIndex(new Index('loop', [$tree])), new EvaluatorConfig(), new Evaluators(), new Budget()));
46        $graph = $engine->context->index->graph('subject');
47        self::assertNotNull($graph);
48        $value = $engine->returns(new Frame($graph, 'source:subject'), 64);
49        $alternatives = iterator_to_array((new Choices())->alternatives($value), false);
50        self::assertCount(1, $alternatives);
51        $recurrence = $alternatives[0][0];
52        self::assertSame(TermKind::Recurrence, $recurrence->kind);
53        self::assertSame(1, $recurrence->operands[0]->native());
54        self::assertSame(TermKind::Binary, $recurrence->operands[1]->kind);
55        self::assertSame($recurrence->operands[2]->attributes['identity'], $recurrence->operands[1]->operands[0]->attributes['identity']);
56        self::assertSame(2, $recurrence->operands[1]->operands[1]->native());
57        self::assertSame(['subject:$x' => 1], $engine->context->references);
58    }
59
60    public function testDependsOnRecognizesCopiedReferencesByIdentity(): void
61    {
62        $engine = new Evaluator(new Context(new DefinitionIndex(new Index('loop', [])), new EvaluatorConfig(), new Evaluators(), new Budget()));
63        $reference = new Term(TermKind::Recursive, attributes: ['identity' => 'loop:x']);
64        $copy = new Term(TermKind::Recursive, attributes: ['identity' => 'loop:x']);
65        $update = new Term(TermKind::Binary, '+', [$copy, Term::constant(2)]);
66        self::assertTrue((new Candidates($engine))->dependsOn($update, $reference));
67        self::assertFalse((new Candidates($engine))->dependsOn($update, new Term(TermKind::Recursive, attributes: ['identity' => 'loop:y'])));
68    }
69
70    public function testResolveKeepsACycleBetweenDifferentStorageLocationsPartial(): void
71    {
72        $tree = (new Resolver())->resolve((new Parser(new ParserConfig('8.3')))->parse(new Source('loop.php', '<?php function subject(){$x=1;$y=2;while(missing()){$x=$y;$y=$x+1;}return $x;}')));
73        $engine = new Evaluator(new Context(new DefinitionIndex(new Index('loop', [$tree])), new EvaluatorConfig(), new Evaluators(), new Budget()));
74        $graph = $engine->context->index->graph('subject');
75        self::assertNotNull($graph);
76        $value = $engine->returns(new Frame($graph, 'source:subject'), 64);
77        $alternatives = iterator_to_array((new Choices())->alternatives($value), false);
78        self::assertCount(3, $alternatives);
79        self::assertSame([1, 2], [$alternatives[0][0]->native(), $alternatives[1][0]->native()]);
80        self::assertSame(TermKind::Binary, $alternatives[2][0]->kind);
81        self::assertSame(TermKind::Recursive, $alternatives[2][0]->operands[0]->kind);
82        self::assertSame('CYCLE', $alternatives[2][0]->operands[0]->attributes['reason']);
83        self::assertSame(1, $alternatives[2][0]->operands[1]->native());
84        self::assertNull($engine->context->stopReason);
85    }
86}
87