Perf improvements for String.endWith(Regex) (#1884)
* Compute a derived regex expression with `\z` anchor * Ensure derived regex can't be messed up with comments (in extended mode) or quote start Follow up to #1880
This commit is contained in:
1 parent
adaafc4045
commit
ba25f4820c
4 files changed
+129
-11
No files matched your search
@@ -84,13 +84,14 @@ public final class VmUtils {
|
||||
}
|
||||
};
|
||||
|
||||
public static final int REGEX_FLAGS = Pattern.UNICODE_CHARACTER_CLASS | Pattern.UNICODE_CASE;
|
||||
|
||||
private static final Engine PKL_ENGINE =
|
||||
Engine.newBuilder("pkl").option("engine.WarnInterpreterOnly", "false").build();
|
||||
|
||||
@SuppressWarnings("MagicConstant")
|
||||
private static final Pattern DOC_COMMENT_LINE_START =
|
||||
Pattern.compile(
|
||||
"(?:^|\n|\r\n?)[ \t\f]*///[ \t\f]?",
|
||||
Pattern.UNICODE_CHARACTER_CLASS | Pattern.UNICODE_CASE);
|
||||
Pattern.compile("(?:^|\n|\r\n?)[ \t\f]*///[ \t\f]?", REGEX_FLAGS);
|
||||
|
||||
private static final SourceSection UNAVAILABLE_SOURCE_SECTION =
|
||||
Source.newBuilder("pkl", "", "unavailable")
|
||||
@@ -906,7 +907,8 @@ public final class VmUtils {
|
||||
@TruffleBoundary
|
||||
public static Pattern compilePattern(String pattern, Node location) {
|
||||
try {
|
||||
return Pattern.compile(pattern, Pattern.UNICODE_CHARACTER_CLASS | Pattern.UNICODE_CASE);
|
||||
//noinspection MagicConstant
|
||||
return Pattern.compile(pattern, REGEX_FLAGS);
|
||||
} catch (PatternSyntaxException e) {
|
||||
throw new VmExceptionBuilder()
|
||||
.withLocation(location)
|
||||
|
||||
@@ -17,6 +17,7 @@ package org.pkl.core.stdlib.base;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives;
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.dsl.Cached;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.nodes.LoopNode;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
@@ -332,15 +333,50 @@ public final class StringNodes {
|
||||
}
|
||||
|
||||
@TruffleBoundary
|
||||
@Specialization
|
||||
protected boolean eval(String self, VmRegex regex) {
|
||||
// try every suffix; `find()` would skip matches that overlap an earlier one
|
||||
var matcher = regex.matcher(self).useTransparentBounds(true).useAnchoringBounds(false);
|
||||
for (var start = self.length(); ; start = self.offsetByCodePoints(start, -1)) {
|
||||
if (matcher.region(start, self.length()).matches()) return true;
|
||||
if (start == 0) return false;
|
||||
@Specialization(guards = "regex.equals(cachedRegex)")
|
||||
protected boolean evalRegexCached(
|
||||
String self,
|
||||
VmRegex regex,
|
||||
@Cached("regex") VmRegex cachedRegex,
|
||||
@Cached("computePattern(cachedRegex)") Pattern pattern) {
|
||||
return pattern.matcher(self).find();
|
||||
}
|
||||
|
||||
@TruffleBoundary
|
||||
@Specialization(replaces = "evalRegexCached")
|
||||
protected boolean evalRegex(String self, VmRegex regex) {
|
||||
return computePattern(regex).matcher(self).find();
|
||||
}
|
||||
|
||||
// incorrect warning; non-capture group here is used to ensure correct precedence (e.g. prevent
|
||||
// constructing a regex like `foo|bar$`)
|
||||
@SuppressWarnings({
|
||||
"RegExpUnnecessaryNonCapturingGroup",
|
||||
"RegExpUnexpectedAnchor",
|
||||
"MagicConstant"
|
||||
})
|
||||
protected final Pattern computePattern(VmRegex regex) {
|
||||
var existingPattern = regex.getPattern();
|
||||
var suffix = isCommentsEnabled(regex) ? "\n)\\z" : ")\\z";
|
||||
try {
|
||||
return Pattern.compile("(?:" + existingPattern.pattern() + suffix, VmUtils.REGEX_FLAGS);
|
||||
} catch (PatternSyntaxException e) {
|
||||
// if we have an unclosed group, the issue here must be that there was a quote start with
|
||||
// an unmatched quote end (e.g. `foo\Qs`)
|
||||
if (e.getMessage().contains("Unclosed group")) {
|
||||
// incorrect regex error diagnostic here but not suppressable.
|
||||
return Pattern.compile(
|
||||
"(?:" + existingPattern.pattern() + "\\E" + suffix, VmUtils.REGEX_FLAGS);
|
||||
} else {
|
||||
CompilerDirectives.transferToInterpreter();
|
||||
throw exceptionBuilder().bug(e.getMessage()).withCause(e).build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isCommentsEnabled(VmRegex regex) {
|
||||
return (regex.getPattern().flags() & Pattern.COMMENTS) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract static class indexOf extends ExternalMethod1Node {
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
res1 =
|
||||
!"""
|
||||
aa
|
||||
zz
|
||||
""".endsWith(Regex("(?m)aa"))
|
||||
|
||||
res1a =
|
||||
"""
|
||||
zz
|
||||
aaa
|
||||
""".endsWith(Regex("(?m)aa"))
|
||||
|
||||
res2 =
|
||||
!"""
|
||||
aa
|
||||
zz
|
||||
""".endsWith("aa")
|
||||
|
||||
res3 =
|
||||
"""
|
||||
aa
|
||||
zz
|
||||
""".endsWith("zz")
|
||||
|
||||
res4 =
|
||||
"""
|
||||
aa
|
||||
zz
|
||||
""".endsWith(
|
||||
Regex(
|
||||
"""
|
||||
(?x)
|
||||
zz # this is the comment for zz
|
||||
""",
|
||||
),
|
||||
)
|
||||
|
||||
res5 =
|
||||
!"""
|
||||
aabbcc
|
||||
""".endsWith(Regex("aa|zz"))
|
||||
|
||||
res6 = "zz".endsWith(Regex("(?x)zz"))
|
||||
res7 = "zz".endsWith(Regex("(?x) zz # comment"))
|
||||
|
||||
res8 = "a\né".endsWith(Regex(#"(?x)\w"#))
|
||||
|
||||
res9 = "a\nÉ".endsWith(Regex("(?x)(?i)é"))
|
||||
res10 = "é".endsWith(Regex(#"\w"#))
|
||||
|
||||
res11 = !"foo\n".endsWith(Regex("foo"))
|
||||
res12 = !"foo\r\n".endsWith(Regex("foo"))
|
||||
|
||||
res13 = "a\nb".endsWith(Regex(#"(?m)a(?:\nb)??"#))
|
||||
|
||||
res14 = "x)".endsWith(Regex(#"x\Q)"#))
|
||||
res14a = "x # some comment".endsWith(Regex(#"(?x)x\Q # some comment"#))
|
||||
|
||||
res15 = !"Ab".endsWith(Regex("a(?i)b"))
|
||||
res16 = !"x\n".endsWith(Regex("x.(?s)"))
|
||||
res17 = "a b".endsWith(Regex("a b(?x)"))
|
||||
@@ -0,0 +1,19 @@
|
||||
res1 = true
|
||||
res1a = true
|
||||
res2 = true
|
||||
res3 = true
|
||||
res4 = true
|
||||
res5 = true
|
||||
res6 = true
|
||||
res7 = true
|
||||
res8 = true
|
||||
res9 = true
|
||||
res10 = true
|
||||
res11 = true
|
||||
res12 = true
|
||||
res13 = true
|
||||
res14 = true
|
||||
res14a = true
|
||||
res15 = true
|
||||
res16 = true
|
||||
res17 = true
|
||||
Reference in new issue
Block a user