Completed
Push — master ( d2636b...e88807 )
by Colin
02:51
created

DelimiterStack::push()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 10
ccs 7
cts 7
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 5
nc 2
nop 1
crap 2
1
<?php
2
3
/*
4
 * This file is part of the league/commonmark package.
5
 *
6
 * (c) Colin O'Dell <[email protected]>
7
 *
8
 * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
9
 *  - (c) John MacFarlane
10
 *
11
 * For the full copyright and license information, please view the LICENSE
12
 * file that was distributed with this source code.
13
 */
14
15
namespace League\CommonMark\Delimiter;
16
17
class DelimiterStack
18
{
19
    /**
20
     * @var Delimiter|null
21
     */
22
    protected $top;
23
24
    public function getTop()
25
    {
26
        return $this->top;
27
    }
28
29 864
    public function push(Delimiter $newDelimiter)
30
    {
31 864
        $newDelimiter->setPrevious($this->top);
32
33 864
        if ($this->top !== null) {
34 507
            $this->top->setNext($newDelimiter);
35 338
        }
36
37 864
        $this->top = $newDelimiter;
38 864
    }
39
40
    /**
41
     * @param Delimiter|null $stackBottom
42
     *
43
     * @return Delimiter|null
44
     */
45 1656
    public function findEarliest(Delimiter $stackBottom = null)
46
    {
47 1656
        $delimiter = $this->top;
48 1656
        while ($delimiter !== null && $delimiter->getPrevious() !== $stackBottom) {
49 465
            $delimiter = $delimiter->getPrevious();
50 310
        }
51
52 1656
        return $delimiter;
53
    }
54
55
    /**
56
     * @param Delimiter $delimiter
57
     */
58 864
    public function removeDelimiter(Delimiter $delimiter)
59
    {
60 864
        if ($delimiter->getPrevious() !== null) {
61 342
            $delimiter->getPrevious()->setNext($delimiter->getNext());
62 228
        }
63
64 864
        if ($delimiter->getNext() === null) {
65
            // top of stack
66 864
            $this->top = $delimiter->getPrevious();
67 576
        } else {
68 333
            $delimiter->getNext()->setPrevious($delimiter->getPrevious());
69
        }
70 864
    }
71
72
    /**
73
     * @param Delimiter|null $stackBottom
74
     */
75 1656
    public function removeAll(Delimiter $stackBottom = null)
76
    {
77 1656
        while ($this->top && $this->top !== $stackBottom) {
78 528
            $this->removeDelimiter($this->top);
79 352
        }
80 1656
    }
81
82
    /**
83
     * @param string $character
84
     */
85 279
    public function removeEarlierMatches($character)
86
    {
87 279
        $opener = $this->top;
88 279
        while ($opener !== null) {
89 54
            if ($opener->getChar() === $character) {
90 24
                $opener->setActive(false);
91 16
            }
92
93 54
            $opener = $opener->getPrevious();
94 36
        }
95 279
    }
96
97
    /**
98
     * @param string|string[] $characters
99
     *
100
     * @return Delimiter|null
101
     */
102 405
    public function searchByCharacter($characters)
103
    {
104 405
        if (!is_array($characters)) {
105
            $characters = [$characters];
106
        }
107
108 405
        $opener = $this->top;
109 405
        while ($opener !== null) {
110 399
            if (in_array($opener->getChar(), $characters)) {
111 396
                break;
112
            }
113 72
            $opener = $opener->getPrevious();
114 48
        }
115
116 405
        return $opener;
117
    }
118
119
    /**
120
     * @param string|string[] $characters
121
     * @param callable        $callback
122
     * @param Delimiter       $stackBottom
123
     */
124 1656
    public function iterateByCharacters($characters, $callback, Delimiter $stackBottom = null)
125
    {
126 1656
        if (!is_array($characters)) {
127
            $characters = [$characters];
128
        }
129
130 1656
        $openersBottom = array_fill_keys($characters, $stackBottom);
131
132
        // Find first closer above stackBottom
133 1656
        $closer = $this->findEarliest($stackBottom);
134
135 1656
        while ($closer !== null) {
136 807
            $closerChar = $closer->getChar();
137
138 807
            if (!$closer->canClose() || !in_array($closerChar, $characters)) {
139 768
                $closer = $closer->getNext();
140 768
                continue;
141
            }
142
143 408
            $oddMatch = false;
144 408
            $opener = $this->findMatchingOpener($closer, $openersBottom, $stackBottom, $oddMatch);
145 408
            if ($opener) {
146 336
                $closer = $callback($opener, $closer, $this);
147 316
            } elseif ($oddMatch) {
148 15
                $closer = $closer->getNext();
149 10
            } else {
150 117
                $oldCloser = $closer;
151 117
                $closer = $closer->getNext();
152
                // Set lower bound for future searches for openers:
153 117
                $openersBottom[$closerChar] = $oldCloser->getPrevious();
154 117
                if (!$oldCloser->canOpen()) {
155
                    // We can remove a closer that can't be an opener,
156
                    // once we've seen there's no matching opener:
157 87
                    $this->removeDelimiter($oldCloser);
158 58
                }
159 117
                continue;
160
            }
161 224
        }
162 1656
    }
163
164
    /**
165
     * @param Delimiter      $closer
166
     * @param array          $openersBottom
167
     * @param Delimiter|null $stackBottom
168
     * @param bool           $oddMatch
169
     *
170
     * @return Delimiter|null
171
     */
172 408
    protected function findMatchingOpener(Delimiter $closer, $openersBottom, Delimiter $stackBottom = null, &$oddMatch = false)
173
    {
174 408
        $closerChar = $closer->getChar();
175 408
        $opener = $closer->getPrevious();
176
177 408
        while ($opener !== null && $opener !== $stackBottom && $opener !== $openersBottom[$closerChar]) {
178 384
            $oddMatch = ($closer->canOpen() || $opener->canClose()) && ($opener->getOrigDelims() + $closer->getOrigDelims()) % 3 === 0;
179 384
            if ($opener->getChar() === $closerChar && $opener->canOpen() && !$oddMatch) {
180 336
                return $opener;
181
            }
182
183 81
            $opener = $opener->getPrevious();
184 54
        }
185 132
    }
186
}
187