TableConflictChecker::checkFirstFollowConflict()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 6
c 0
b 0
f 0
nc 2
nop 2
dl 0
loc 9
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Remorhaz\UniLex\Parser\LL1\Lookup;
6
7
use Remorhaz\UniLex\Exception;
8
use Remorhaz\UniLex\Grammar\ContextFree\GrammarInterface;
9
use Remorhaz\UniLex\Grammar\ContextFree\Production;
10
11
class TableConflictChecker
12
{
13
    private $grammar;
14
15
    private $first;
16
17
    private $follow;
18
19
    public function __construct(GrammarInterface $grammar, FirstInterface $first, FollowInterface $follow)
20
    {
21
        $this->grammar = $grammar;
22
        $this->first = $first;
23
        $this->follow = $follow;
24
    }
25
26
    /**
27
     * @throws Exception
28
     */
29
    public function check(): void
30
    {
31
        foreach ($this->grammar->getNonTerminalList() as $symbolId) {
32
            $this->checkSymbolConflicts($symbolId);
33
        }
34
    }
35
36
    /**
37
     * @param int $symbolId
38
     * @throws Exception
39
     */
40
    private function checkSymbolConflicts(int $symbolId): void
41
    {
42
        $productionList = $this->grammar->getProductionList($symbolId);
43
        $this->checkMultipleEpsilons($symbolId, ...$productionList);
44
        foreach ($productionList as $alpha) {
45
            foreach ($productionList as $beta) {
46
                $this->checkProductionConflicts($alpha, $beta);
47
            }
48
        }
49
    }
50
51
    /**
52
     * @param Production $alpha
53
     * @param Production $beta
54
     * @throws Exception
55
     */
56
    private function checkProductionConflicts(Production $alpha, Production $beta): void
57
    {
58
        if ($alpha->getIndex() == $beta->getIndex()) {
59
            return;
60
        }
61
        $this->checkFirstFirstConflict($alpha, $beta);
62
        $this->checkFirstFollowConflict($alpha, $beta);
63
    }
64
65
    /**
66
     * @param int $symbolId
67
     * @param Production ...$productionList
68
     * @throws Exception
69
     */
70
    private function checkMultipleEpsilons(int $symbolId, Production ...$productionList): void
71
    {
72
        $isEpsilonProduction = function (Production $production): bool {
73
            return $production->isEpsilon();
74
        };
75
        $epsilonProductionList = array_filter($productionList, $isEpsilonProduction);
76
        if (count($epsilonProductionList) > 1) {
77
            throw new Exception("Symbol {$symbolId} has multiple ε-productions");
78
        }
79
    }
80
81
    /**
82
     * @param Production $alpha
83
     * @param Production $beta
84
     * @throws Exception
85
     */
86
    private function checkFirstFirstConflict(Production $alpha, Production $beta): void
87
    {
88
        $firstAlpha = $this->first->getProductionTokens(...$alpha->getSymbolList());
89
        $firstBeta = $this->first->getProductionTokens(...$beta->getSymbolList());
90
        $message = "FIRST({$alpha})/FIRST({$beta}) conflict";
91
        $this->checkConflict($firstAlpha, $firstBeta, $message);
92
    }
93
94
    /**
95
     * @param Production $alpha
96
     * @param Production $beta
97
     * @throws Exception
98
     */
99
    private function checkFirstFollowConflict(Production $alpha, Production $beta): void
100
    {
101
        if (!$this->first->productionHasEpsilon(...$beta->getSymbolList())) {
102
            return;
103
        }
104
        $follow = $this->follow->getTokens($alpha->getHeaderId());
105
        $firstAlpha = $this->first->getProductionTokens(...$alpha->getSymbolList());
106
        $message = "FIRST({$alpha})/FOLLOW({$alpha->getHeaderId()}) conflict (ε ∈ {$beta})";
107
        $this->checkConflict($follow, $firstAlpha, $message);
108
    }
109
110
    /**
111
     * @param array $tokenListA
112
     * @param array $tokenListB
113
     * @param string $message
114
     * @throws Exception
115
     */
116
    private function checkConflict(array $tokenListA, array $tokenListB, string $message): void
117
    {
118
        $conflict = array_intersect($tokenListA, $tokenListB);
119
        if (!empty($conflict)) {
120
            $conflictText = implode(', ', $conflict);
121
            throw new Exception("{$message}: {$conflictText}");
122
        }
123
    }
124
}
125