Skip to content

Commit a0baf9e

Browse files
committed
Merge pull request #117 from thephpleague/optimize-delimiter-iteration
Optimize delimiter iteration
2 parents 3468773 + 433ae9b commit a0baf9e

File tree

2 files changed

+41
-9
lines changed

2 files changed

+41
-9
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Updates should follow the [Keep a CHANGELOG](http://keepachangelog.com/) princip
1515
- Use U+FFFD for entities resolving to 0 (jgm/CommonMark#323)
1616
- Moved `IndentedCodeParser::CODE_INDENT_LEVEL` to `Cursor::INDENT_LEVEL`
1717
- Changed arrays to short syntax (#116)
18+
- Improved efficiency of DelimiterStack iteration (jgm/commonmark.js#43)
1819

1920
### Fixed
2021
- Fixed open block tag followed by newline not being recognized (jgm/CommonMark#324)

src/Delimiter/DelimiterStack.php

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -127,28 +127,59 @@ public function iterateByCharacters($characters, $callback, Delimiter $stackBott
127127
$characters = [$characters];
128128
}
129129

130+
$openersBottom = array_fill_keys($characters, $stackBottom);
131+
130132
// Find first closer above stackBottom
131133
$closer = $this->findEarliest($stackBottom);
132134

133135
while ($closer !== null) {
134-
if ($closer->canClose() && (in_array($closer->getChar(), $characters))) {
136+
$closerChar = $closer->getChar();
137+
if ($closer->canClose() && (in_array($closerChar, $characters))) {
135138
// Found emphasis closer. Now look back for first matching opener:
136-
$opener = $closer->getPrevious();
137-
while ($opener !== null && $opener !== $stackBottom) {
138-
if ($opener->getChar() === $closer->getChar() && $opener->canOpen()) {
139-
break;
140-
}
141-
$opener = $opener->getPrevious();
142-
}
139+
$opener = $this->findFirstMatchingOpener($closer, $openersBottom, $stackBottom);
143140

144-
if ($opener !== null && $opener !== $stackBottom) {
141+
$oldCloser = $closer;
142+
143+
if ($opener !== null) {
145144
$closer = $callback($opener, $closer, $this);
146145
} else {
147146
$closer = $closer->getNext();
147+
// Set lower bound for future searches for openers:
148+
$openersBottom[$closerChar] = $oldCloser->getPrevious();
149+
if (!$oldCloser->canOpen()) {
150+
// We can remove a closer that can't be an opener,
151+
// once we've seen there's no matching opener:
152+
$this->removeDelimiter($oldCloser);
153+
}
148154
}
149155
} else {
150156
$closer = $closer->getNext();
151157
}
152158
}
153159
}
160+
161+
/**
162+
* @param Delimiter $closer
163+
* @param array $openersBottom
164+
* @param Delimiter|null $stackBottom
165+
*
166+
* @return Delimiter|null
167+
*/
168+
protected function findFirstMatchingOpener(Delimiter $closer, $openersBottom, Delimiter $stackBottom = null)
169+
{
170+
$closerChar = $closer->getChar();
171+
$opener = $closer->getPrevious();
172+
173+
while ($opener !== null && $opener !== $stackBottom && $opener !== $openersBottom[$closerChar]) {
174+
if ($opener->getChar() === $closerChar && $opener->canOpen()) {
175+
return $opener;
176+
}
177+
178+
$opener = $opener->getPrevious();
179+
}
180+
181+
return null;
182+
}
183+
184+
154185
}

0 commit comments

Comments
 (0)