[regex] Fix quantifier processing
Consider the following regular expression:
((.){0,1}b)+
The original Harmony implementation fails to match a string '0b1b'
with it. The root-cause is the following. After matching the inner
group first time (it matches '0' in the input string) we don't
reset the counter of occurrences for it and proceed to the next
elements for the regex. When we should match it the second time (we
should match '1' at this point) we obtain this obsolete value of the
counter and come to a conclusion that the group has more occurrences
than specified in the quantifier.
The fix resets this counter each time we move to next elements of
a regex from a QuantifierSet.
This commit is contained in:
committed by
Ilya Matveev
parent
70efeb1433
commit
54beb0c8e8
@@ -435,6 +435,29 @@ class MatchResultTest {
|
||||
assertFalse(regex.containsMatchIn(str))
|
||||
}
|
||||
|
||||
/**
|
||||
* Regression test for https://github.com/JetBrains/kotlin-native/issues/2297
|
||||
*/
|
||||
@Test fun test2297() {
|
||||
assertTrue(Regex("^(:[0-5]?[0-9])+$").matches(":20:30"))
|
||||
assertTrue(Regex("(.{1,}){2}").matches("aa"))
|
||||
|
||||
assertTrue(Regex("(.+b)+").matches("0b0b"))
|
||||
assertTrue(Regex("(.+?b)+").matches("0b0b"))
|
||||
assertTrue(Regex("(.?b)+").matches("0b0b"))
|
||||
assertTrue(Regex("(.??b)+").matches("0b0b"))
|
||||
assertTrue(Regex("(.*b)+").matches("0b0b"))
|
||||
assertTrue(Regex("(.*?b)+").matches("0b0b"))
|
||||
assertTrue(Regex("(.{1,2}b)+").matches("0b00b"))
|
||||
assertTrue(Regex("(.{1,2}?b)+").matches("0b00b"))
|
||||
|
||||
assertTrue(Regex("([0]?[0]?)+").matches("0000"))
|
||||
assertTrue(Regex("([0]?[0]?b)+").matches("00b00b"))
|
||||
assertTrue(Regex("((b{2}){3})+").matches("bbbbbbbbbbbb"))
|
||||
|
||||
assertTrue(Regex("[^a]").matches("b"))
|
||||
}
|
||||
|
||||
/**
|
||||
* Regression test for HARMONY-3360
|
||||
*/
|
||||
|
||||
@@ -42,44 +42,47 @@ open internal class GroupQuantifierSet(
|
||||
// We call innerSet.matches here, if it succeeds it call next.matches where next is this QuantifierSet.
|
||||
// So we have a recursive searching procedure.
|
||||
override fun matches(startIndex: Int, testString: CharSequence, matchResult: MatchResultImpl): Int {
|
||||
var enterCount = matchResult.enterCounters[groupQuantifierIndex]
|
||||
|
||||
fun matchNext(): Int {
|
||||
matchResult.enterCounters[groupQuantifierIndex] = 0
|
||||
val result = next.matches(startIndex, testString, matchResult)
|
||||
matchResult.enterCounters[groupQuantifierIndex] = enterCount
|
||||
return result
|
||||
}
|
||||
|
||||
if (!innerSet.hasConsumed(matchResult)) {
|
||||
return next.matches(startIndex, testString, matchResult)
|
||||
return matchNext()
|
||||
}
|
||||
|
||||
// Fast case: '*' or {0, } - no need to count occurrences.
|
||||
if (min == 0 && max == Quantifier.INF) {
|
||||
val nextIndex = innerSet.matches(startIndex, testString, matchResult)
|
||||
|
||||
return if (nextIndex < 0) {
|
||||
next.matches(startIndex, testString, matchResult)
|
||||
matchNext()
|
||||
} else {
|
||||
nextIndex
|
||||
}
|
||||
}
|
||||
|
||||
val enterCount = matchResult.enterCounters[groupQuantifierIndex]
|
||||
|
||||
// can't go inner set;
|
||||
if (max != Quantifier.INF && enterCount >= max) {
|
||||
return next.matches(startIndex, testString, matchResult)
|
||||
return matchNext()
|
||||
}
|
||||
|
||||
// go inner set;
|
||||
matchResult.enterCounters[groupQuantifierIndex]++
|
||||
matchResult.enterCounters[groupQuantifierIndex] = ++enterCount
|
||||
val nextIndex = innerSet.matches(startIndex, testString, matchResult)
|
||||
|
||||
if (nextIndex < 0) {
|
||||
matchResult.enterCounters[groupQuantifierIndex]--
|
||||
return if (nextIndex < 0) {
|
||||
matchResult.enterCounters[groupQuantifierIndex] = --enterCount
|
||||
if (enterCount >= min) {
|
||||
return next.matches(startIndex, testString, matchResult)
|
||||
matchNext()
|
||||
} else {
|
||||
matchResult.enterCounters[groupQuantifierIndex] = 0
|
||||
return -1
|
||||
-1
|
||||
}
|
||||
} else {
|
||||
matchResult.enterCounters[groupQuantifierIndex] = 0
|
||||
return nextIndex
|
||||
nextIndex
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -34,9 +34,17 @@ internal class ReluctantGroupQuantifierSet(
|
||||
) : GroupQuantifierSet(quantifier, innerSet, next, type, setCounter) {
|
||||
|
||||
override fun matches(startIndex: Int, testString: CharSequence, matchResult: MatchResultImpl): Int {
|
||||
var enterCount = matchResult.enterCounters[groupQuantifierIndex]
|
||||
|
||||
fun matchNext(): Int {
|
||||
matchResult.enterCounters[groupQuantifierIndex] = 0
|
||||
val result = next.matches(startIndex, testString, matchResult)
|
||||
matchResult.enterCounters[groupQuantifierIndex] = enterCount
|
||||
return result
|
||||
}
|
||||
|
||||
if (!innerSet.hasConsumed(matchResult)) {
|
||||
return next.matches(startIndex, testString, matchResult)
|
||||
return matchNext()
|
||||
}
|
||||
|
||||
// Fast case: '*' or {0, } - no need to count occurrences.
|
||||
@@ -49,29 +57,22 @@ internal class ReluctantGroupQuantifierSet(
|
||||
}
|
||||
}
|
||||
|
||||
val enterCounter = matchResult.enterCounters[groupQuantifierIndex]
|
||||
|
||||
// can't go inner set;
|
||||
if (enterCounter >= max) {
|
||||
matchResult.enterCounters[groupQuantifierIndex] = 0
|
||||
return next.matches(startIndex, testString, matchResult)
|
||||
if (max != Quantifier.INF && enterCount >= max) {
|
||||
return matchNext()
|
||||
}
|
||||
|
||||
var nextIndex: Int
|
||||
if (enterCounter >= min) {
|
||||
nextIndex = next.matches(startIndex, testString, matchResult)
|
||||
return if (enterCount >= min) {
|
||||
val nextIndex = matchNext()
|
||||
if (nextIndex < 0) {
|
||||
matchResult.enterCounters[groupQuantifierIndex]++
|
||||
nextIndex = innerSet.matches(startIndex, testString, matchResult)
|
||||
matchResult.enterCounters[groupQuantifierIndex] = ++enterCount
|
||||
innerSet.matches(startIndex, testString, matchResult)
|
||||
} else {
|
||||
matchResult.enterCounters[groupQuantifierIndex] = 0
|
||||
return nextIndex
|
||||
nextIndex
|
||||
}
|
||||
} else {
|
||||
matchResult.enterCounters[groupQuantifierIndex]++
|
||||
nextIndex = innerSet.matches(startIndex, testString, matchResult)
|
||||
matchResult.enterCounters[groupQuantifierIndex] = ++enterCount
|
||||
innerSet.matches(startIndex, testString, matchResult)
|
||||
}
|
||||
|
||||
return nextIndex
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user