[K/N][stdlib] Fixed lookaround regexes (KT-45932)

This commit is contained in:
Elena Lepilkina
2021-04-23 12:12:49 +03:00
committed by Space
parent d7a87a8552
commit 18462445b7
5 changed files with 120 additions and 13 deletions
@@ -0,0 +1,70 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
package test.text.harmony_regex
import kotlin.text.*
import kotlin.test.*
class FindAllTest {
internal fun Regex.allGroups(text: String) =
findAll(text).map {
it.groups.mapIndexed { index, it ->
"$index => ${it?.value}"
}.joinToString("; ")
}.toList()
/**
* Tests regular expressions with lookbehind asserts.
*/
@Test fun testLookBehind() {
var regex: Regex
var result: List<String>
regex = "(?<=^/nl(?:/nl)?/\\d{1,600}[\\d+]{0,600}/[\\d+]{0,600})(\\d+)".toRegex()
result = regex.allGroups("/nl/nl/1+2/3+4/")
assertEquals(2, result.count())
assertEquals("0 => 3; 1 => 3", result[0])
assertEquals("0 => 4; 1 => 4", result[1])
regex = "abe(?<=[ab][!be](.|\\b))(=|t)".toRegex()
result = regex.allGroups("abet abe=")
assertEquals(2, result.count())
assertEquals("0 => abet; 1 => e; 2 => t", result[0])
assertEquals("0 => abe=; 1 => ; 2 => =", result[1])
}
/**
* Tests regular expressions with lookahead asserts.
*/
@Test fun testLookAheadBehind() {
var regex: Regex
var result: List<String>
regex = "a(?=b?)(\\w|)c".toRegex()
result = regex.allGroups("abcfgac")
assertEquals(2, result.count())
assertEquals("0 => abc; 1 => b", result[0])
assertEquals("0 => ac; 1 => ", result[1])
regex = "[a!](?=d|&)\\b[&d]".toRegex()
result = regex.allGroups("ada& !d!&")
assertEquals(2, result.count())
assertEquals("0 => a&", result[0])
assertEquals("0 => !d", result[1])
regex = "(?=ab)(a|^)b".toRegex()
result = regex.allGroups("abcab")
assertEquals(2, result.count())
assertEquals("0 => ab; 1 => a", result[0])
assertEquals("0 => ab; 1 => a", result[1])
regex = "(?=[a-k][a-z])(?=[a-d][c-x])[d-y][x-z]".toRegex()
result = regex.allGroups("abdydx")
assertEquals(1, result.count())
assertEquals("0 => dx", result[0])
}
}
@@ -56,6 +56,26 @@ constructor (internal val input: CharSequence,
var previousMatch = -1
var mode = Regex.Mode.MATCH
private data class MatchResultState(val groupBounds: IntArray, val consumers: IntArray, val enterCounters: IntArray,
val startIndex: Int, val previousMatch: Int)
private var state: MatchResultState? = null
internal fun saveState() {
state = MatchResultState(groupBounds.copyOf(), consumers.copyOf(), enterCounters.copyOf(), startIndex, previousMatch)
}
internal fun rollbackState(): Boolean {
return state?.let {
it.groupBounds.copyInto(groupBounds)
it.consumers.copyInto(consumers)
it.enterCounters.copyInto(enterCounters)
startIndex = it.startIndex
previousMatch = it.previousMatch
true
} ?: false
}
// MatchResult interface ===========================================================================================
/** The range of indices in the original string where match was captured. */
override val range: IntRange
@@ -25,10 +25,10 @@ package kotlin.text.regex
/**
* Positive lookahead node.
*/
internal class PositiveLookAheadSet(children: List<AbstractSet>, fSet: FSet) : AtomicJointSet(children, fSet) {
internal class PositiveLookAheadSet(children: List<AbstractSet>, fSet: FSet) : LookAroundSet(children, fSet) {
/** Returns startIndex+shift, the next position to match */
override fun matches(startIndex: Int, testString: CharSequence, matchResult: MatchResultImpl): Int {
override fun tryToMatch(startIndex: Int, testString: CharSequence, matchResult: MatchResultImpl): Int {
children.forEach {
val shift = it.matches(startIndex, testString, matchResult)
if (shift >= 0) {
@@ -39,7 +39,6 @@ internal class PositiveLookAheadSet(children: List<AbstractSet>, fSet: FSet) : A
return -1
}
override fun hasConsumed(matchResult: MatchResultImpl): Boolean = true
override val name: String
get() = "PositiveLookaheadJointSet"
}
@@ -47,10 +46,10 @@ internal class PositiveLookAheadSet(children: List<AbstractSet>, fSet: FSet) : A
/**
* Negative look ahead node.
*/
internal class NegativeLookAheadSet(children: List<AbstractSet>, fSet: FSet) : AtomicJointSet(children, fSet) {
internal class NegativeLookAheadSet(children: List<AbstractSet>, fSet: FSet) : LookAroundSet(children, fSet) {
/** Returns startIndex+shift, the next position to match */
override fun matches(startIndex: Int, testString: CharSequence, matchResult: MatchResultImpl): Int {
override fun tryToMatch(startIndex: Int, testString: CharSequence, matchResult: MatchResultImpl): Int {
children.forEach {
if (it.matches(startIndex, testString, matchResult) >= 0) {
return -1
@@ -60,7 +59,6 @@ internal class NegativeLookAheadSet(children: List<AbstractSet>, fSet: FSet) : A
return next.matches(startIndex, testString, matchResult)
}
override fun hasConsumed(matchResult: MatchResultImpl): Boolean = true
override val name: String
get() = "NegativeLookaheadJointSet"
}
@@ -0,0 +1,22 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
package kotlin.text.regex
/**
* Abstract class for lookahead and lookbehind nodes.
*/
internal abstract class LookAroundSet(children: List<AbstractSet>, fSet: FSet) : AtomicJointSet(children, fSet) {
protected abstract fun tryToMatch(startIndex: Int, testString: CharSequence, matchResult: MatchResultImpl): Int
/** Returns startIndex+shift, the next position to match */
override fun matches(startIndex: Int, testString: CharSequence, matchResult: MatchResultImpl): Int {
matchResult.saveState()
return tryToMatch(startIndex, testString, matchResult).also { if (it < 0) matchResult.rollbackState() }
}
override fun hasConsumed(matchResult: MatchResultImpl): Boolean = true
}
@@ -25,11 +25,10 @@ package kotlin.text.regex
/**
* Positive lookbehind node.
*/
internal class PositiveLookBehindSet(children: List<AbstractSet>, fSet: FSet) : AtomicJointSet(children, fSet) {
internal class PositiveLookBehindSet(children: List<AbstractSet>, fSet: FSet) : LookAroundSet(children, fSet) {
/** Returns startIndex+shift, the next position to match */
override fun matches(startIndex: Int, testString: CharSequence, matchResult: MatchResultImpl): Int {
override fun tryToMatch(startIndex: Int, testString: CharSequence, matchResult: MatchResultImpl): Int {
matchResult.setConsumed(groupIndex, startIndex)
children.forEach {
if (it.findBack(0, startIndex, testString, matchResult) >= 0) {
@@ -41,7 +40,6 @@ internal class PositiveLookBehindSet(children: List<AbstractSet>, fSet: FSet) :
return -1
}
override fun hasConsumed(matchResult: MatchResultImpl): Boolean = true
override val name: String
get() = "PositiveBehindJointSet"
}
@@ -49,10 +47,10 @@ internal class PositiveLookBehindSet(children: List<AbstractSet>, fSet: FSet) :
/**
* Negative look behind node.
*/
internal class NegativeLookBehindSet(children: List<AbstractSet>, fSet: FSet) : AtomicJointSet(children, fSet) {
internal class NegativeLookBehindSet(children: List<AbstractSet>, fSet: FSet) : LookAroundSet(children, fSet) {
/** Returns startIndex+shift, the next position to match */
override fun matches(startIndex: Int, testString: CharSequence, matchResult: MatchResultImpl): Int {
override fun tryToMatch(startIndex: Int, testString: CharSequence, matchResult: MatchResultImpl): Int {
matchResult.setConsumed(groupIndex, startIndex)
children.forEach {
@@ -65,7 +63,6 @@ internal class NegativeLookBehindSet(children: List<AbstractSet>, fSet: FSet) :
return next.matches(startIndex, testString, matchResult)
}
override fun hasConsumed(matchResult: MatchResultImpl): Boolean = true
override val name: String
get() = "NegativeBehindJointSet"
}