Skip to content

Commit 5f4ce14

Browse files
committed
Regex: Use the native 'd' flag to compute indices when available.
That feature is enabled by a flag, and not by default, because of performance concerns. JS engine implementers have shown that enabling the feature has a significant performance impact. Therefore, we only enable it the first time group indices are required for any given `Pattern` instance. This means that the first match for which group indices are required will be run twice, but this is probably the better trade-off.
1 parent deb2479 commit 5f4ce14

File tree

2 files changed

+28
-4
lines changed

2 files changed

+28
-4
lines changed

javalib/src/main/scala/java/util/regex/Pattern.scala

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ final class Pattern private[regex] (
3434
@inline private def jsFlagsForFind: String =
3535
jsFlags + (if (sticky && supportsSticky) "gy" else "g")
3636

37+
/** Whether we already added the 'd' flag to the native RegExp's. */
38+
private var enabledNativeIndices: Boolean = false
39+
3740
/** The RegExp that is used for `Matcher.find()`.
3841
*
3942
* It receives the 'g' flag so that `lastIndex` is taken into acount.
@@ -45,7 +48,7 @@ final class Pattern private[regex] (
4548
* Since that RegExp is only used locally within `execFind()`, we can
4649
* always reuse the same instance.
4750
*/
48-
private[this] val jsRegExpForFind =
51+
private[this] var jsRegExpForFind =
4952
new js.RegExp(jsPattern, jsFlagsForFind)
5053

5154
/** Another version of the RegExp that is used by `Matcher.matches()`.
@@ -57,7 +60,7 @@ final class Pattern private[regex] (
5760
* Since that RegExp is only used locally within `execMatches()`, we can
5861
* always reuse the same instance.
5962
*/
60-
private[this] val jsRegExpForMatches: js.RegExp =
63+
private[this] var jsRegExpForMatches: js.RegExp =
6164
new js.RegExp("^" + jsPattern + "$", jsFlags)
6265

6366
private lazy val indicesBuilder: IndicesBuilder =
@@ -131,8 +134,20 @@ final class Pattern private[regex] (
131134

132135
private[regex] def getIndices(lastMatch: js.RegExp.ExecResult, forMatches: Boolean): IndicesArray = {
133136
val lastMatchDyn = lastMatch.asInstanceOf[js.Dynamic]
134-
if (js.isUndefined(lastMatchDyn.indices))
135-
lastMatchDyn.indices = indicesBuilder(forMatches, lastMatch.input, lastMatch.index)
137+
if (js.isUndefined(lastMatchDyn.indices)) {
138+
if (supportsIndices) {
139+
if (!enabledNativeIndices) {
140+
jsRegExpForFind = new js.RegExp(jsPattern, jsFlagsForFind + "d")
141+
jsRegExpForMatches = new js.RegExp("^" + jsPattern + "$", jsFlags + "d")
142+
enabledNativeIndices = true
143+
}
144+
val regexp = if (forMatches) jsRegExpForMatches else jsRegExpForFind
145+
regexp.lastIndex = lastMatch.index
146+
lastMatchDyn.indices = regexp.exec(lastMatch.input).asInstanceOf[js.Dynamic].indices
147+
} else {
148+
lastMatchDyn.indices = indicesBuilder(forMatches, lastMatch.input, lastMatch.index)
149+
}
150+
}
136151
lastMatchDyn.indices.asInstanceOf[IndicesArray]
137152
}
138153

javalib/src/main/scala/java/util/regex/PatternCompiler.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,10 @@ private[regex] object PatternCompiler {
9090
private val _supportsDotAll =
9191
(esVersion >= ESVersion.ES2018) || featureTest("", "us")
9292

93+
/** Cache for `Support.supportsIndices`. */
94+
private val _supportsIndices =
95+
featureTest("a(b)", "d")
96+
9397
/** Feature-test methods.
9498
*
9599
* They are located in a separate object so that the methods can be fully
@@ -112,6 +116,11 @@ private[regex] object PatternCompiler {
112116
def supportsDotAll: Boolean =
113117
(esVersion >= ESVersion.ES2018) || _supportsDotAll
114118

119+
/** Tests whether the underlying JS RegExp supports the 'd' flag. */
120+
@inline
121+
def supportsIndices: Boolean =
122+
_supportsIndices
123+
115124
/** Tests whether features requiring support for the 'u' flag are enabled.
116125
*
117126
* They are enabled if and only if the project is configured to rely on

0 commit comments

Comments
 (0)