[K/N][stdlib] Fixed lookaround regexes (KT-45932)
This commit is contained in:
@@ -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"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user