Passed
Push — master ( c01dfe...f39ce2 )
by Edward
04:04
created

DfaBuilder::fromBuffer()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 5
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Remorhaz\UniLex\RegExp\FSM;
4
5
use Remorhaz\UniLex\AST\Tree;
6
use Remorhaz\UniLex\Exception as UniLexException;
7
use Remorhaz\UniLex\IO\CharBufferInterface;
8
9
use function array_merge;
10
use function array_unique;
11
use function sort;
12
13
class DfaBuilder
14
{
15
16
    private $dfa;
17
18
    private $nfa;
19
20
    private $nfaCalc;
21
22
    private $stateBuffer = [];
23
24
    public function __construct(Dfa $dfa, Nfa $nfa)
25
    {
26
        $this->dfa = $dfa;
27
        $this->nfa = $nfa;
28
    }
29
30
    /**
31
     * @param Nfa $nfa
32
     * @return Dfa
33
     * @throws UniLexException
34
     */
35
    public static function fromNfa(Nfa $nfa): Dfa
36
    {
37
        // Minimization is made using Brzozowski's algorithm
38
        $reverser = new NfaReverser();
39
40
        $r1 = $reverser->reverseNfa($nfa);
41
        $r1->joinStartStates();
42
43
        $d1 = new Dfa();
44
        (new self($d1, $r1))->run();
45
46
        $r2 = $reverser->reverseDfa($d1);
47
        $r2->joinStartStates();
48
49
        $d2 = new Dfa();
50
        (new self($d2, $r2))->run();
51
52
        // restoring correct NFA state map that was lost on building $d2
53
        $dfa = new Dfa();
54
        $dfa->setSymbolTable($d2->getSymbolTable());
55
        $stateMap = [];
56
        foreach ($d2->getStateMap()->getStateList() as $d2StateId) {
57
            $d2StateValue = $d2->getStateMap()->getStateValue($d2StateId);
58
            foreach ($d2StateValue as $r2StateId) {
59
                $r2StateValue = $r2->getStateMap()->getStateValue($r2StateId);
60
                $stateMap[$d2StateId] = array_merge($stateMap[$d2StateId] ?? [], $r2StateValue);
61
            }
62
        }
63
        foreach ($stateMap as $stateId => $stateValue) {
64
            sort($stateValue);
65
            $dfa->getStateMap()->importState(array_unique($stateValue), $stateId);
66
        }
67
        $dfa->getStateMap()->addStartState(...$d2->getStateMap()->getStartStateList());
68
        $dfa->getStateMap()->addFinishState(...$d2->getStateMap()->getFinishStateList());
69
        foreach ($d2->getTransitionMap()->getTransitionList() as $sourceStateId => $targetStates) {
70
            foreach ($targetStates as $targetStateId => $value) {
71
                $dfa->getTransitionMap()->addTransition($sourceStateId, $targetStateId, $value);
72
            }
73
        }
74
75
        return $dfa;
76
    }
77
78
    /**
79
     * @param CharBufferInterface $buffer
80
     * @return Dfa
81
     * @throws UniLexException
82
     */
83
    public static function fromBuffer(CharBufferInterface $buffer): Dfa
84
    {
85
        $nfa = NfaBuilder::fromBuffer($buffer);
86
87
        return self::fromNfa($nfa);
88
    }
89
90
    /**
91
     * @param Tree $tree
92
     * @return Dfa
93
     * @throws UniLexException
94
     */
95
    public static function fromTree(Tree $tree): Dfa
96
    {
97
        $nfa = NfaBuilder::fromTree($tree);
98
99
        return self::fromNfa($nfa);
100
    }
101
102
    /**
103
     * @throws UniLexException
104
     */
105
    public function run(): void
106
    {
107
        $this->initStateBuffer();
108
        while ($state = $this->getNextState()) {
109
            [$dfaStateIn, $nfaStateInList] = $state;
110
            foreach ($this->getMovesBySymbol(...$nfaStateInList) as $symbolId => $nfaStateOutList) {
111
                $dfaStateOut = $this->createStateIfNotExists($isStateProcessed, ...$nfaStateOutList);
112
                $this->mergeTransition($dfaStateIn, $dfaStateOut, $symbolId);
113
                if (!$isStateProcessed) {
114
                    $this->addNextState($dfaStateOut, ...$nfaStateOutList);
115
                }
116
            }
117
        }
118
    }
119
120
    private function getMovesBySymbol(int ...$nfaStateList): array
121
    {
122
        $result = [];
123
        foreach ($this->dfa->getSymbolTable()->getSymbolList() as $symbolId) {
124
            $symbolMoves = $this->getNfaCalc()->getSymbolMoves($symbolId, ...$nfaStateList);
125
            $nextStateList = $this->getNfaCalc()->getEpsilonClosure(...$symbolMoves);
126
            if (!empty($nextStateList)) {
127
                $result[$symbolId] = $nextStateList;
128
            }
129
        }
130
131
        return $result;
132
    }
133
134
    /**
135
     * @param bool $exists
136
     * @param int  ...$nfaStateList
137
     * @return int
138
     * @throws UniLexException
139
     */
140
    private function createStateIfNotExists(&$exists, int ...$nfaStateList): int
141
    {
142
        $dfaStateMap = $this->dfa->getStateMap();
143
        $exists = $dfaStateMap->stateValueExists($nfaStateList);
144
        if ($exists) {
145
            return $dfaStateMap->getValueState($nfaStateList);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $dfaStateMap->getValueState($nfaStateList) could return the type string which is incompatible with the type-hinted return integer. Consider adding an additional type-check to rule them out.
Loading history...
146
        }
147
        $dfaState = $dfaStateMap->createState($nfaStateList);
0 ignored issues
show
Bug introduced by
$nfaStateList of type array<integer,integer> is incompatible with the type boolean expected by parameter $value of Remorhaz\UniLex\RegExp\FSM\StateMap::createState(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

147
        $dfaState = $dfaStateMap->createState(/** @scrutinizer ignore-type */ $nfaStateList);
Loading history...
148
        foreach ($nfaStateList as $nfaState) {
149
            if ($this->nfa->getStateMap()->isFinishState($nfaState)) {
150
                $dfaStateMap->addFinishState($dfaState);
151
                break;
152
            }
153
        }
154
155
        return $dfaState;
156
    }
157
158
    /**
159
     * @param int $stateIn
160
     * @param int $stateOut
161
     * @param int $symbolId
162
     * @throws UniLexException
163
     */
164
    private function mergeTransition(int $stateIn, int $stateOut, int $symbolId): void
165
    {
166
        $transitionMap = $this
167
            ->dfa
168
            ->getTransitionMap();
169
        $symbolList = $transitionMap->transitionExists($stateIn, $stateOut)
170
            ? $transitionMap->getTransition($stateIn, $stateOut)
171
            : [];
172
        $symbolList[] = $symbolId;
173
        $transitionMap->replaceTransition($stateIn, $stateOut, $symbolList);
174
    }
175
176
    private function getNfaCalc(): NfaCalc
177
    {
178
        if (!isset($this->nfaCalc)) {
179
            $this->nfaCalc = new NfaCalc($this->nfa);
180
        }
181
182
        return $this->nfaCalc;
183
    }
184
185
    /**
186
     * @throws UniLexException
187
     */
188
    private function initStateBuffer(): void
189
    {
190
        $this->dfa->setSymbolTable($this->nfa->getSymbolTable());
191
        $startStateId = $this->nfa->getStateMap()->getStartState();
192
        $nfaStateList = $this->getNfaCalc()->getEpsilonClosure($startStateId);
193
        $startState = $this->dfa->getStateMap()->createState($nfaStateList);
0 ignored issues
show
Bug introduced by
$nfaStateList of type array is incompatible with the type boolean expected by parameter $value of Remorhaz\UniLex\RegExp\FSM\StateMap::createState(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

193
        $startState = $this->dfa->getStateMap()->createState(/** @scrutinizer ignore-type */ $nfaStateList);
Loading history...
194
        $this->dfa->getStateMap()->addStartState($startState);
195
        $this->stateBuffer = [[$startState, $nfaStateList]];
196
    }
197
198
    private function getNextState(): ?array
199
    {
200
        return array_pop($this->stateBuffer);
201
    }
202
203
    private function addNextState(int $dfaState, int ...$nfaStateList): void
204
    {
205
        $this->stateBuffer[] = [$dfaState, $nfaStateList];
206
    }
207
}
208