Regex.matchAt/matchesAt #KT-34021
This commit is contained in:
committed by
teamcityserver
parent
d99d25e51e
commit
28a0698463
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright 2010-2018 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.
|
||||
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package kotlin.text
|
||||
@@ -150,6 +150,12 @@ public actual class Regex internal constructor(internal val nativePattern: Patte
|
||||
/** Indicates whether the regular expression can find at least one match in the specified [input]. */
|
||||
actual fun containsMatchIn(input: CharSequence): Boolean = find(input) != null
|
||||
|
||||
@SinceKotlin("1.5")
|
||||
@ExperimentalStdlibApi
|
||||
public actual fun matchesAt(input: CharSequence, index: Int): Boolean =
|
||||
// TODO: expand and simplify
|
||||
matchAt(input, index) != null
|
||||
|
||||
/**
|
||||
* Returns the first match of a regular expression in the [input], beginning at the specified [startIndex].
|
||||
*
|
||||
@@ -197,6 +203,23 @@ public actual class Regex internal constructor(internal val nativePattern: Patte
|
||||
*/
|
||||
actual fun matchEntire(input: CharSequence): MatchResult?= doMatch(input, Mode.MATCH)
|
||||
|
||||
@SinceKotlin("1.5")
|
||||
@ExperimentalStdlibApi
|
||||
public actual fun matchAt(input: CharSequence, index: Int): MatchResult? {
|
||||
if (index < 0 || index > input.length) {
|
||||
throw IndexOutOfBoundsException("index is out of bounds: $index, input length: ${input.length}")
|
||||
}
|
||||
val matchResult = MatchResultImpl(input, this)
|
||||
matchResult.mode = Mode.FIND
|
||||
matchResult.startIndex = index
|
||||
val matches = startNode.matches(index, input, matchResult) >= 0
|
||||
if (!matches) {
|
||||
return null
|
||||
}
|
||||
matchResult.finalizeMatch()
|
||||
return matchResult
|
||||
}
|
||||
|
||||
private fun processReplacement(match: MatchResult, replacement: String): String {
|
||||
val result = StringBuilder(replacement.length)
|
||||
var escaped = false
|
||||
|
||||
@@ -1409,10 +1409,18 @@ public final class Regex {
|
||||
|
||||
public final fun findAll(input: kotlin.CharSequence, startIndex: kotlin.Int = ...): kotlin.sequences.Sequence<kotlin.text.MatchResult>
|
||||
|
||||
@kotlin.SinceKotlin(version = "1.5")
|
||||
@kotlin.ExperimentalStdlibApi
|
||||
public final fun matchAt(input: kotlin.CharSequence, index: kotlin.Int): kotlin.text.MatchResult?
|
||||
|
||||
public final fun matchEntire(input: kotlin.CharSequence): kotlin.text.MatchResult?
|
||||
|
||||
public final infix fun matches(input: kotlin.CharSequence): kotlin.Boolean
|
||||
|
||||
@kotlin.SinceKotlin(version = "1.5")
|
||||
@kotlin.ExperimentalStdlibApi
|
||||
public final fun matchesAt(input: kotlin.CharSequence, index: kotlin.Int): kotlin.Boolean
|
||||
|
||||
public final inline fun replace(input: kotlin.CharSequence, transform: (kotlin.text.MatchResult) -> kotlin.CharSequence): kotlin.String
|
||||
|
||||
public final fun replace(input: kotlin.CharSequence, replacement: kotlin.String): kotlin.String
|
||||
|
||||
@@ -1409,10 +1409,18 @@ public final class Regex {
|
||||
|
||||
public final fun findAll(input: kotlin.CharSequence, startIndex: kotlin.Int = ...): kotlin.sequences.Sequence<kotlin.text.MatchResult>
|
||||
|
||||
@kotlin.SinceKotlin(version = "1.5")
|
||||
@kotlin.ExperimentalStdlibApi
|
||||
public final fun matchAt(input: kotlin.CharSequence, index: kotlin.Int): kotlin.text.MatchResult?
|
||||
|
||||
public final fun matchEntire(input: kotlin.CharSequence): kotlin.text.MatchResult?
|
||||
|
||||
public final infix fun matches(input: kotlin.CharSequence): kotlin.Boolean
|
||||
|
||||
@kotlin.SinceKotlin(version = "1.5")
|
||||
@kotlin.ExperimentalStdlibApi
|
||||
public final fun matchesAt(input: kotlin.CharSequence, index: kotlin.Int): kotlin.Boolean
|
||||
|
||||
public final inline fun replace(input: kotlin.CharSequence, transform: (kotlin.text.MatchResult) -> kotlin.CharSequence): kotlin.String
|
||||
|
||||
public final fun replace(input: kotlin.CharSequence, replacement: kotlin.String): kotlin.String
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
@@ -15,6 +15,31 @@ expect class Regex {
|
||||
|
||||
fun matchEntire(input: CharSequence): MatchResult?
|
||||
infix fun matches(input: CharSequence): Boolean
|
||||
|
||||
/**
|
||||
* Attempts to match a regular expression exactly at the specified [index] in the [input] char sequence.
|
||||
*
|
||||
* Unlike [matchEntire] function, it doesn't require the match to span to the end of [input].
|
||||
*
|
||||
* @return An instance of [MatchResult] if the input matches this [Regex] at the specified [index] or `null` otherwise.
|
||||
* @throws IndexOutOfBoundsException if [index] is less than zero or greater than the length of the [input] char sequence.
|
||||
*/
|
||||
@SinceKotlin("1.5")
|
||||
@ExperimentalStdlibApi
|
||||
fun matchAt(input: CharSequence, index: Int): MatchResult?
|
||||
|
||||
/**
|
||||
* Checks if a regular expression matches a part of the specified [input] char sequence
|
||||
* exactly at the specified [index].
|
||||
*
|
||||
* Unlike [matches] function, it doesn't require the match to span to the end of [input].
|
||||
*
|
||||
* @throws IndexOutOfBoundsException if [index] is less than zero or greater than the length of the [input] char sequence.
|
||||
*/
|
||||
@SinceKotlin("1.5")
|
||||
@ExperimentalStdlibApi
|
||||
fun matchesAt(input: CharSequence, index: Int): Boolean
|
||||
|
||||
fun containsMatchIn(input: CharSequence): Boolean
|
||||
fun replace(input: CharSequence, replacement: String): String
|
||||
fun replace(input: CharSequence, transform: (MatchResult) -> CharSequence): String
|
||||
|
||||
@@ -20,6 +20,8 @@ public actual enum class RegexOption(val value: String) {
|
||||
MULTILINE("m")
|
||||
}
|
||||
|
||||
private fun Iterable<RegexOption>.toFlags(prepend: String): String = joinToString("", prefix = prepend) { it.value }
|
||||
|
||||
|
||||
/**
|
||||
* Represents the results from a single capturing group within a [MatchResult] of [Regex].
|
||||
@@ -51,7 +53,11 @@ public actual class Regex actual constructor(pattern: String, options: Set<Regex
|
||||
public actual val pattern: String = pattern
|
||||
/** The set of options that were used to create this regular expression. */
|
||||
public actual val options: Set<RegexOption> = options.toSet()
|
||||
private val nativePattern: RegExp = RegExp(pattern, options.joinToString(separator = "", prefix = "gu") { it.value })
|
||||
private val nativePattern: RegExp = RegExp(pattern, options.toFlags("gu"))
|
||||
private var nativeStickyPattern: RegExp? = null
|
||||
private fun initStickyPattern(): RegExp =
|
||||
nativeStickyPattern ?: RegExp(pattern, options.toFlags("yu")).also { nativeStickyPattern = it }
|
||||
|
||||
|
||||
/** Indicates whether the regular expression matches the entire [input]. */
|
||||
public actual infix fun matches(input: CharSequence): Boolean {
|
||||
@@ -66,6 +72,17 @@ public actual class Regex actual constructor(pattern: String, options: Set<Regex
|
||||
return nativePattern.test(input.toString())
|
||||
}
|
||||
|
||||
@SinceKotlin("1.5")
|
||||
@ExperimentalStdlibApi
|
||||
public actual fun matchesAt(input: CharSequence, index: Int): Boolean {
|
||||
if (index < 0 || index > input.length) {
|
||||
throw IndexOutOfBoundsException("index out of bounds: $index, input length: ${input.length}")
|
||||
}
|
||||
val pattern = initStickyPattern()
|
||||
pattern.lastIndex = index
|
||||
return pattern.test(input.toString())
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first match of a regular expression in the [input], beginning at the specified [startIndex].
|
||||
*
|
||||
@@ -109,6 +126,16 @@ public actual class Regex actual constructor(pattern: String, options: Set<Regex
|
||||
return Regex("^${pattern.trimStart('^').trimEnd('$')}$", options).find(input)
|
||||
}
|
||||
|
||||
@SinceKotlin("1.5")
|
||||
@ExperimentalStdlibApi
|
||||
public actual fun matchAt(input: CharSequence, index: Int): MatchResult? {
|
||||
if (index < 0 || index > input.length) {
|
||||
throw IndexOutOfBoundsException("index out of bounds: $index, input length: ${input.length}")
|
||||
}
|
||||
return initStickyPattern().findNext(input.toString(), index)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Replaces all occurrences of this regular expression in the specified [input] string with specified [replacement] expression.
|
||||
*
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
package kotlin.text
|
||||
@@ -143,6 +143,18 @@ internal constructor(private val nativePattern: Pattern) : Serializable {
|
||||
*/
|
||||
public actual fun matchEntire(input: CharSequence): MatchResult? = nativePattern.matcher(input).matchEntire(input)
|
||||
|
||||
@SinceKotlin("1.5")
|
||||
@ExperimentalStdlibApi
|
||||
public actual fun matchAt(input: CharSequence, index: Int): MatchResult? =
|
||||
nativePattern.matcher(input).useAnchoringBounds(false).useTransparentBounds(true).region(index, input.length).run {
|
||||
if (lookingAt()) MatcherMatchResult(this, input) else null
|
||||
}
|
||||
|
||||
@SinceKotlin("1.5")
|
||||
@ExperimentalStdlibApi
|
||||
public actual fun matchesAt(input: CharSequence, index: Int): Boolean =
|
||||
nativePattern.matcher(input).useAnchoringBounds(false).useTransparentBounds(true).region(index, input.length).lookingAt()
|
||||
|
||||
/**
|
||||
* Replaces all occurrences of this regular expression in the specified [input] string with specified [replacement] expression.
|
||||
*
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
@@ -179,6 +179,39 @@ class RegexTest {
|
||||
assertEquals("aaaabbbb", regex.matchEntire(input)!!.value)
|
||||
}
|
||||
|
||||
@Test fun matchAt() {
|
||||
val regex = Regex("[a-z][1-5]", RegexOption.IGNORE_CASE)
|
||||
val input = "...a4...B1"
|
||||
val positions = 0..input.length
|
||||
|
||||
val matchIndices = positions.filter { index -> regex.matchesAt(input, index) }
|
||||
assertEquals(listOf(3, 8), matchIndices)
|
||||
val reversedIndices = positions.reversed().filter { index -> regex.matchesAt(input, index) }.reversed()
|
||||
assertEquals(matchIndices, reversedIndices)
|
||||
|
||||
val matches = positions.mapNotNull { index -> regex.matchAt(input, index)?.let { index to it } }
|
||||
assertEquals(matchIndices, matches.map { it.first })
|
||||
matches.forEach { (index, match) ->
|
||||
assertEquals(index..index + 1, match.range)
|
||||
assertEquals(input.substring(match.range), match.value)
|
||||
}
|
||||
|
||||
for (index in listOf(-1, input.length + 1)) {
|
||||
assertFailsWith<IndexOutOfBoundsException> { regex.matchAt(input, index) }
|
||||
assertFailsWith<IndexOutOfBoundsException> { regex.matchesAt(input, index) }
|
||||
}
|
||||
|
||||
val anchoringRegex = Regex("^[a-z]")
|
||||
assertFalse(anchoringRegex.matchesAt(input, 3))
|
||||
assertNull(anchoringRegex.matchAt(input, 3))
|
||||
|
||||
val lookbehindRegex = Regex("(?<=[a-z])\\d")
|
||||
assertTrue(lookbehindRegex.matchesAt(input, 4))
|
||||
assertNotNull(lookbehindRegex.matchAt(input, 4)).let { match ->
|
||||
assertEquals("4", match.value)
|
||||
}
|
||||
}
|
||||
|
||||
@Test fun escapeLiteral() {
|
||||
val literal = """[-\/\\^$*+?.()|[\]{}]"""
|
||||
assertTrue(Regex.fromLiteral(literal).matches(literal))
|
||||
|
||||
@@ -20,6 +20,9 @@ actual class Regex {
|
||||
actual fun replace(input: CharSequence, transform: (MatchResult) -> CharSequence): String = TODO("Wasm stdlib: Text")
|
||||
actual fun replaceFirst(input: CharSequence, replacement: String): String = TODO("Wasm stdlib: Text")
|
||||
|
||||
actual fun matchAt(input: CharSequence, index: Int): MatchResult? = TODO("Wasm stdlib: Text")
|
||||
actual fun matchesAt(input: CharSequence, index: Int): Boolean = TODO("Wasm stdlib: Text")
|
||||
|
||||
/**
|
||||
* Returns the first match of a regular expression in the [input], beginning at the specified [startIndex].
|
||||
*
|
||||
|
||||
+2
@@ -5201,8 +5201,10 @@ public final class kotlin/text/Regex : java/io/Serializable {
|
||||
public static synthetic fun findAll$default (Lkotlin/text/Regex;Ljava/lang/CharSequence;IILjava/lang/Object;)Lkotlin/sequences/Sequence;
|
||||
public final fun getOptions ()Ljava/util/Set;
|
||||
public final fun getPattern ()Ljava/lang/String;
|
||||
public final fun matchAt (Ljava/lang/CharSequence;I)Lkotlin/text/MatchResult;
|
||||
public final fun matchEntire (Ljava/lang/CharSequence;)Lkotlin/text/MatchResult;
|
||||
public final fun matches (Ljava/lang/CharSequence;)Z
|
||||
public final fun matchesAt (Ljava/lang/CharSequence;I)Z
|
||||
public final fun replace (Ljava/lang/CharSequence;Ljava/lang/String;)Ljava/lang/String;
|
||||
public final fun replace (Ljava/lang/CharSequence;Lkotlin/jvm/functions/Function1;)Ljava/lang/String;
|
||||
public final fun replaceFirst (Ljava/lang/CharSequence;Ljava/lang/String;)Ljava/lang/String;
|
||||
|
||||
Reference in New Issue
Block a user