LanguageBuilder::forNfa()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 3
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Remorhaz\UniLex\RegExp\FSM;
6
7
use Remorhaz\IntRangeSets\RangeInterface;
8
use Remorhaz\IntRangeSets\RangeSet;
9
use Remorhaz\UniLex\Exception as UniLexException;
10
11
class LanguageBuilder
12
{
13
    private $symbolTable;
14
15
    private $transitionMap;
16
17
    public function __construct(SymbolTable $symbolTable, TransitionMap $transitionMap)
18
    {
19
        $this->symbolTable = $symbolTable;
20
        $this->transitionMap = $transitionMap;
21
    }
22
23
    public static function forNfa(Nfa $nfa): self
24
    {
25
        return new self($nfa->getSymbolTable(), $nfa->getSymbolTransitionMap());
26
    }
27
28
    /**
29
     * @param int            $stateIn
30
     * @param int            $stateOut
31
     * @param RangeInterface ...$ranges
32
     * @throws UniLexException
33
     */
34
    public function addTransition(int $stateIn, int $stateOut, RangeInterface ...$ranges): void
35
    {
36
        $this->transitionMap->addTransition($stateIn, $stateOut, $this->getSymbolList(...$ranges));
37
    }
38
39
    /**
40
     * @param RangeInterface ...$ranges
41
     * @return array
42
     * @throws UniLexException
43
     */
44
    public function getSymbolList(RangeInterface ...$ranges): array
45
    {
46
        $newRangeSet = RangeSet::create(...$ranges);
47
        $symbolList = [];
48
        $shouldAddNewSymbol = true;
49
        foreach ($this->symbolTable->getRangeSetList() as $symbolId => $oldRangeSet) {
50
            if ($oldRangeSet->equals($newRangeSet)) {
51
                $symbolList[] = $symbolId;
52
                $shouldAddNewSymbol = false;
53
                break;
54
            }
55
            $rangeSetDiff = $oldRangeSet->createSymmetricDifference($newRangeSet);
56
            $onlyInOldRangeSet = $oldRangeSet->createIntersection($rangeSetDiff);
57
            if ($onlyInOldRangeSet->isEmpty()) {
58
                $symbolList[] = $symbolId;
59
                $newRangeSet = $rangeSetDiff;
60
                continue;
61
            }
62
            if ($onlyInOldRangeSet->equals($oldRangeSet)) {
63
                continue;
64
            }
65
            $splitSymbolId = $this
66
                ->symbolTable
67
                ->replaceSymbol($symbolId, $onlyInOldRangeSet)
68
                ->addSymbol($and = $oldRangeSet->createIntersection($newRangeSet));
69
            $this->splitSymbolInTransitions($symbolId, $splitSymbolId);
70
            $symbolList[] = $splitSymbolId;
71
            $newRangeSet = $newRangeSet->createIntersection($rangeSetDiff);
72
        }
73
        if ($shouldAddNewSymbol && !$newRangeSet->isEmpty()) {
74
            $newSymbolId = $this->symbolTable->addSymbol($newRangeSet);
75
            $symbolList[] = $newSymbolId;
76
        }
77
78
        return $symbolList;
79
    }
80
81
    private function splitSymbolInTransitions(int $symbolId, int $symbolToAdd): void
82
    {
83
        $addSymbol = function (array $symbolList) use ($symbolId, $symbolToAdd) {
84
            if (in_array($symbolId, $symbolList)) {
85
                $symbolList[] = $symbolToAdd;
86
            }
87
88
            return $symbolList;
89
        };
90
        $this->transitionMap->replaceEachTransition($addSymbol);
91
    }
92
}
93