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