@@ -31,14 +31,7 @@ final class LinkParserHelper
31
31
public static function parseLinkDestination (Cursor $ cursor ): ?string
32
32
{
33
33
if ($ cursor ->getCurrentCharacter () === '< ' ) {
34
- if ($ res = $ cursor ->match (RegexHelper::REGEX_LINK_DESTINATION_BRACES )) {
35
- // Chop off surrounding <..>:
36
- return UrlEncoder::unescapeAndEncode (
37
- RegexHelper::unescape (\substr ($ res , 1 , -1 ))
38
- );
39
- }
40
-
41
- return null ;
34
+ return self ::parseDestinationBraces ($ cursor );
42
35
}
43
36
44
37
$ destination = self ::manuallyParseLinkDestination ($ cursor );
@@ -137,4 +130,36 @@ private static function manuallyParseLinkDestination(Cursor $cursor): ?string
137
130
138
131
return $ destination ;
139
132
}
133
+
134
+ /** @var \WeakReference<Cursor>|null */
135
+ private static ?\WeakReference $ lastCursor = null ;
136
+ private static bool $ lastCursorLacksClosingBrace = false ;
137
+
138
+ private static function parseDestinationBraces (Cursor $ cursor ): ?string
139
+ {
140
+ // Optimization: If we've previously parsed this cursor and returned `null`, we know
141
+ // that no closing brace exists, so we can skip the regex entirely. This helps avoid
142
+ // certain pathological cases where the regex engine can take a very long time to
143
+ // determine that no match exists.
144
+ if (self ::$ lastCursor !== null && self ::$ lastCursor ->get () === $ cursor ) {
145
+ if (self ::$ lastCursorLacksClosingBrace ) {
146
+ return null ;
147
+ }
148
+ } else {
149
+ self ::$ lastCursor = \WeakReference::create ($ cursor );
150
+ }
151
+
152
+ if ($ res = $ cursor ->match (RegexHelper::REGEX_LINK_DESTINATION_BRACES )) {
153
+ self ::$ lastCursorLacksClosingBrace = false ;
154
+
155
+ // Chop off surrounding <..>:
156
+ return UrlEncoder::unescapeAndEncode (
157
+ RegexHelper::unescape (\substr ($ res , 1 , -1 ))
158
+ );
159
+ }
160
+
161
+ self ::$ lastCursorLacksClosingBrace = true ;
162
+
163
+ return null ;
164
+ }
140
165
}
0 commit comments