packages/deriver/src/Deriver/Evaluator/Memory/Elements.php

1<?php
2
3declare(strict_types=1);
4
5namespace Deriver\Deriver\Evaluator\Memory;
6
7use Deriver\Deriver\Evaluator\Evaluator;
8use Deriver\Deriver\Evaluator\Term;
9
10/**
11 * Reconstructs nested updates without discarding unaffected array entries.
12 * @visibility parent
13 */
14final class Elements
15{
16    /**
17     * Reuses ordinary array projection and update rules.
18     */
19    public function __construct(private readonly Evaluator $engine)
20    {
21    }
22
23    /**
24
25     * @param non-empty-list<Term> $path
26
27     */
28    public function write(Term $array, array $path, Term $value): Term
29    {
30        $key = array_shift($path);
31        if ($path !== []) {
32            $child = $this->engine->operation('array-read', '', [$array, $key]);
33            $value = $this->write($child, $path, $value);
34        }
35        return $this->engine->operation('array-set', '', [$array, $key, $value]);
36    }
37    /**
38     * Applies the leaf mutation and rebuilds only its containing arrays.
39     * @param non-empty-list<Term> $path Nested element keys
40     */
41    public function mutate(Term $array, array $path, \Deriver\Deriver\Lowerer\Graph\Instruction $write, Term $right): Term
42    {
43        $key = array_shift($path);
44        $before = $this->engine->element($array, $key);
45        if ($path !== []) {
46            return $this->engine->operation('array-set', '', [$array, $key, $this->mutate($before, $path, $write, $right)]);
47        }
48        if ($write->operation === 'unset') {
49            return (new \Deriver\Deriver\Evaluator\Enumeration\Choices())->apply('array-unset', [$array, $key], static function (array $values): Term {
50                [$array, $key] = $values;
51                if ($array->kind !== \Deriver\Deriver\Evaluator\TermKind::Array || $key->kind !== \Deriver\Deriver\Evaluator\TermKind::Constant) {
52                    return new Term(\Deriver\Deriver\Evaluator\TermKind::ArrayUnset, operands: $values, attributes: ['reason' => 'UNRESOLVED_ELEMENT']);
53                }
54                $entries = $array->operands;
55                unset($entries[(string) $key->literal]);
56                return new Term(\Deriver\Deriver\Evaluator\TermKind::Array, operands: $entries, attributes: $array->attributes);
57            }, $this->engine->context->budget->partitions);
58        }
59        $value = match ($write->operation) {
60            'increment' => Mutations::increment($this->engine, $write, $before),
61            'compound' => $this->engine->operation('binary', $write->name, [$before, $right]),
62            default => $right,
63        };
64        return $this->engine->operation('array-set', '', [$array, $key, $value]);
65    }
66
67}
68