Regex.splitToSequence, CharSequence.splitToSequence(Regex) #KT-23351

This commit is contained in:
Abduqodiri Qurbonzoda
2021-07-19 00:06:13 +03:00
committed by Space
parent 1fee6b191f
commit b65c477e68
11 changed files with 216 additions and 22 deletions
@@ -305,14 +305,14 @@ public actual class Regex internal constructor(internal val nativePattern: Patte
}
/**
* Splits the [input] CharSequence around matches of this regular expression.
* Splits the [input] CharSequence to a list of strings around matches of this regular expression.
*
* @param limit Non-negative value specifying the maximum number of substrings the string can be split to.
* Zero by default means no limit is set.
*/
@Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS")
actual fun split(input: CharSequence, limit: Int = 0): List<String> {
require(limit >= 0, { "Limit must be non-negative, but was $limit." } )
requireNonNegativeLimit(limit)
var match: MatchResult? = find(input)
@@ -334,6 +334,39 @@ public actual class Regex internal constructor(internal val nativePattern: Patte
return result
}
/**
* Splits the [input] CharSequence to a sequence of strings around matches of this regular expression.
*
* @param limit Non-negative value specifying the maximum number of substrings the string can be split to.
* Zero by default means no limit is set.
*/
@SinceKotlin("1.5")
@ExperimentalStdlibApi
@Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS")
public actual fun splitToSequence(input: CharSequence, limit: Int = 0): Sequence<String> {
requireNonNegativeLimit(limit)
return sequence {
var match = find(input)
if (match == null || limit == 1) {
yield(input.toString())
return@sequence
}
var nextStart = 0
var splitCount = 0
do {
val foundMatch = match!!
yield(input.substring(nextStart, foundMatch.range.first))
nextStart = foundMatch.range.endInclusive + 1
match = foundMatch.next()
} while (++splitCount != limit - 1 && match != null)
yield(input.substring(nextStart, input.length))
}
}
/**
* Returns the string representation of this regular expression, namely the [pattern] of this regular expression.
*
@@ -834,6 +834,11 @@ public fun kotlin.CharSequence.splitToSequence(vararg delimiters: kotlin.String,
public fun kotlin.CharSequence.splitToSequence(vararg delimiters: kotlin.Char, ignoreCase: kotlin.Boolean = ..., limit: kotlin.Int = ...): kotlin.sequences.Sequence<kotlin.String>
@kotlin.SinceKotlin(version = "1.5")
@kotlin.ExperimentalStdlibApi
@kotlin.internal.InlineOnly
public inline fun kotlin.CharSequence.splitToSequence(regex: kotlin.text.Regex, limit: kotlin.Int = ...): kotlin.sequences.Sequence<kotlin.String>
public fun kotlin.CharSequence.startsWith(char: kotlin.Char, ignoreCase: kotlin.Boolean = ...): kotlin.Boolean
public fun kotlin.CharSequence.startsWith(prefix: kotlin.CharSequence, ignoreCase: kotlin.Boolean = ...): kotlin.Boolean
@@ -1429,6 +1434,10 @@ public final class Regex {
public final fun split(input: kotlin.CharSequence, limit: kotlin.Int = ...): kotlin.collections.List<kotlin.String>
@kotlin.SinceKotlin(version = "1.5")
@kotlin.ExperimentalStdlibApi
public final fun splitToSequence(input: kotlin.CharSequence, limit: kotlin.Int = ...): kotlin.sequences.Sequence<kotlin.String>
public open override fun toString(): kotlin.String
public companion object of Regex {
+9
View File
@@ -834,6 +834,11 @@ public fun kotlin.CharSequence.splitToSequence(vararg delimiters: kotlin.String,
public fun kotlin.CharSequence.splitToSequence(vararg delimiters: kotlin.Char, ignoreCase: kotlin.Boolean = ..., limit: kotlin.Int = ...): kotlin.sequences.Sequence<kotlin.String>
@kotlin.SinceKotlin(version = "1.5")
@kotlin.ExperimentalStdlibApi
@kotlin.internal.InlineOnly
public inline fun kotlin.CharSequence.splitToSequence(regex: kotlin.text.Regex, limit: kotlin.Int = ...): kotlin.sequences.Sequence<kotlin.String>
public fun kotlin.CharSequence.startsWith(char: kotlin.Char, ignoreCase: kotlin.Boolean = ...): kotlin.Boolean
public fun kotlin.CharSequence.startsWith(prefix: kotlin.CharSequence, ignoreCase: kotlin.Boolean = ...): kotlin.Boolean
@@ -1429,6 +1434,10 @@ public final class Regex {
public final fun split(input: kotlin.CharSequence, limit: kotlin.Int = ...): kotlin.collections.List<kotlin.String>
@kotlin.SinceKotlin(version = "1.5")
@kotlin.ExperimentalStdlibApi
public final fun splitToSequence(input: kotlin.CharSequence, limit: kotlin.Int = ...): kotlin.sequences.Sequence<kotlin.String>
public open override fun toString(): kotlin.String
public companion object of Regex {
+11 -1
View File
@@ -65,13 +65,23 @@ expect class Regex {
fun findAll(input: CharSequence, startIndex: Int = 0): Sequence<MatchResult>
/**
* Splits the [input] CharSequence around matches of this regular expression.
* Splits the [input] CharSequence to a list of strings around matches of this regular expression.
*
* @param limit Non-negative value specifying the maximum number of substrings the string can be split to.
* Zero by default means no limit is set.
*/
fun split(input: CharSequence, limit: Int = 0): List<String>
/**
* Splits the [input] CharSequence to a sequence of strings around matches of this regular expression.
*
* @param limit Non-negative value specifying the maximum number of substrings the string can be split to.
* Zero by default means no limit is set.
*/
@SinceKotlin("1.5")
@ExperimentalStdlibApi
public fun splitToSequence(input: CharSequence, limit: Int = 0): Sequence<String>
companion object {
fun fromLiteral(literal: String): Regex
fun escape(literal: String): String
+35 -2
View File
@@ -186,14 +186,14 @@ public actual class Regex actual constructor(pattern: String, options: Set<Regex
}
/**
* Splits the [input] CharSequence around matches of this regular expression.
* Splits the [input] CharSequence to a list of strings around matches of this regular expression.
*
* @param limit Non-negative value specifying the maximum number of substrings the string can be split to.
* Zero by default means no limit is set.
*/
@Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS")
public actual fun split(input: CharSequence, limit: Int = 0): List<String> {
require(limit >= 0) { "Limit must be non-negative, but was $limit" }
requireNonNegativeLimit(limit)
val matches = findAll(input).let { if (limit == 0) it else it.take(limit - 1) }
val result = mutableListOf<String>()
var lastStart = 0
@@ -206,6 +206,39 @@ public actual class Regex actual constructor(pattern: String, options: Set<Regex
return result
}
/**
* Splits the [input] CharSequence to a sequence of strings around matches of this regular expression.
*
* @param limit Non-negative value specifying the maximum number of substrings the string can be split to.
* Zero by default means no limit is set.
*/
@SinceKotlin("1.5")
@ExperimentalStdlibApi
@Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS")
public actual fun splitToSequence(input: CharSequence, limit: Int = 0): Sequence<String> {
requireNonNegativeLimit(limit)
return sequence {
var match = find(input)
if (match == null || limit == 1) {
yield(input.toString())
return@sequence
}
var nextStart = 0
var splitCount = 0
do {
val foundMatch = match!!
yield(input.substring(nextStart, foundMatch.range.first))
nextStart = foundMatch.range.endInclusive + 1
match = foundMatch.next()
} while (++splitCount != limit - 1 && match != null)
yield(input.substring(nextStart, input.length))
}
}
/**
* Returns the string representation of this regular expression, namely the [pattern] of this regular expression.
@@ -387,7 +387,7 @@ public inline fun String.Companion.format(locale: Locale?, format: String, varar
* Zero by default means no limit is set.
*/
public fun CharSequence.split(regex: Pattern, limit: Int = 0): List<String> {
require(limit >= 0, { "Limit must be non-negative, but was $limit." })
requireNonNegativeLimit(limit)
return regex.split(this, if (limit == 0) -1 else limit).asList()
}
@@ -198,17 +198,17 @@ internal constructor(private val nativePattern: Pattern) : Serializable {
/**
* Splits the [input] CharSequence around matches of this regular expression.
* Splits the [input] CharSequence to a list of strings around matches of this regular expression.
*
* @param limit Non-negative value specifying the maximum number of substrings the string can be split to.
* Zero by default means no limit is set.
*/
@Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS")
public actual fun split(input: CharSequence, limit: Int = 0): List<String> {
require(limit >= 0, { "Limit must be non-negative, but was $limit." })
requireNonNegativeLimit(limit)
val matcher = nativePattern.matcher(input)
if (!matcher.find() || limit == 1) return listOf(input.toString())
if (limit == 1 || !matcher.find()) return listOf(input.toString())
val result = ArrayList<String>(if (limit > 0) limit.coerceAtMost(10) else 10)
var lastStart = 0
@@ -225,6 +225,37 @@ internal constructor(private val nativePattern: Pattern) : Serializable {
return result
}
/**
* Splits the [input] CharSequence to a sequence of strings around matches of this regular expression.
*
* @param limit Non-negative value specifying the maximum number of substrings the string can be split to.
* Zero by default means no limit is set.
*/
@SinceKotlin("1.5")
@ExperimentalStdlibApi
@Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS")
public actual fun splitToSequence(input: CharSequence, limit: Int = 0): Sequence<String> {
requireNonNegativeLimit(limit)
return sequence {
val matcher = nativePattern.matcher(input)
if (limit == 1 || !matcher.find()) {
yield(input.toString())
return@sequence
}
var nextStart = 0
var splitCount = 0
do {
yield(input.substring(nextStart, matcher.start()))
nextStart = matcher.end()
} while (++splitCount != limit - 1 && matcher.find())
yield(input.substring(nextStart, input.length))
}
}
/**
* Returns the string representation of this regular expression, namely the [pattern] of this regular expression.
*
+18 -4
View File
@@ -1237,7 +1237,7 @@ private class DelimitedRangesSequence(
* @param limit The maximum number of substrings to return. Zero by default means no limit is set.
*/
private fun CharSequence.rangesDelimitedBy(delimiters: CharArray, startIndex: Int = 0, ignoreCase: Boolean = false, limit: Int = 0): Sequence<IntRange> {
require(limit >= 0, { "Limit must be non-negative, but was $limit." })
requireNonNegativeLimit(limit)
return DelimitedRangesSequence(this, startIndex, limit, { currentIndex ->
indexOfAny(delimiters, currentIndex, ignoreCase = ignoreCase).let { if (it < 0) null else it to 1 }
@@ -1260,13 +1260,16 @@ private fun CharSequence.rangesDelimitedBy(delimiters: CharArray, startIndex: In
* that matches this string at that position.
*/
private fun CharSequence.rangesDelimitedBy(delimiters: Array<out String>, startIndex: Int = 0, ignoreCase: Boolean = false, limit: Int = 0): Sequence<IntRange> {
require(limit >= 0, { "Limit must be non-negative, but was $limit." } )
requireNonNegativeLimit(limit)
val delimitersList = delimiters.asList()
return DelimitedRangesSequence(this, startIndex, limit, { currentIndex -> findAnyOf(delimitersList, currentIndex, ignoreCase = ignoreCase, last = false)?.let { it.first to it.second.length } })
}
internal fun requireNonNegativeLimit(limit: Int) =
require(limit >= 0) { "Limit must be non-negative, but was $limit" }
// split
@@ -1340,7 +1343,7 @@ public fun CharSequence.split(vararg delimiters: Char, ignoreCase: Boolean = fal
* @param limit The maximum number of substrings to return.
*/
private fun CharSequence.split(delimiter: String, ignoreCase: Boolean, limit: Int): List<String> {
require(limit >= 0, { "Limit must be non-negative, but was $limit." })
requireNonNegativeLimit(limit)
var currentOffset = 0
var nextIndex = indexOf(delimiter, currentOffset, ignoreCase)
@@ -1363,7 +1366,7 @@ private fun CharSequence.split(delimiter: String, ignoreCase: Boolean, limit: In
}
/**
* Splits this char sequence around matches of the given regular expression.
* Splits this char sequence to a list of strings around matches of the given regular expression.
*
* @param limit Non-negative value specifying the maximum number of substrings to return.
* Zero by default means no limit is set.
@@ -1371,6 +1374,17 @@ private fun CharSequence.split(delimiter: String, ignoreCase: Boolean, limit: In
@kotlin.internal.InlineOnly
public inline fun CharSequence.split(regex: Regex, limit: Int = 0): List<String> = regex.split(this, limit)
/**
* Splits this char sequence to a sequence of strings around matches of the given regular expression.
*
* @param limit Non-negative value specifying the maximum number of substrings to return.
* Zero by default means no limit is set.
*/
@SinceKotlin("1.5")
@ExperimentalStdlibApi
@kotlin.internal.InlineOnly
public inline fun CharSequence.splitToSequence(regex: Regex, limit: Int = 0): Sequence<String> = regex.splitToSequence(this, limit)
/**
* Splits this char sequence to a sequence of lines delimited by any of the following character sequences: CRLF, LF or CR.
*
+53 -8
View File
@@ -250,6 +250,18 @@ class RegexTest {
assertEquals("/2/3/4/", pattern.replace(input, { it.value.length.toString() }))
}
private fun testSplitEquals(expected: List<String>, input: CharSequence, regex: Regex, limit: Int = 0) {
assertEquals(expected, input.split(regex, limit))
assertEquals(expected, regex.split(input, limit))
listOf(
input.splitToSequence(regex, limit),
regex.splitToSequence(input, limit)
).forEach { sequence ->
assertEquals(expected, sequence.toList())
assertEquals(expected, sequence.toList()) // assert multiple iterations over the same sequence succeed
}
}
@Test fun split() {
val input = """
@@ -257,9 +269,9 @@ class RegexTest {
split
""".trim()
assertEquals(listOf("some", "word", "split"), "\\s+".toRegex().split(input))
testSplitEquals(listOf("some", "word", "split"), input, "\\s+".toRegex())
assertEquals(listOf("name", "value=5"), "=".toRegex().split("name=value=5", limit = 2))
testSplitEquals(listOf("name", "value=5"), "name=value=5", "=".toRegex(), limit = 2)
}
@@ -268,19 +280,52 @@ class RegexTest {
val emptyMatch = "".toRegex()
assertEquals(input.split(""), input.split(emptyMatch))
assertEquals(input.split("", limit = 3), input.split(emptyMatch, limit = 3))
testSplitEquals(listOf("", "t", "e", "s", "t", ""), input, emptyMatch)
testSplitEquals(listOf("", "t", "est"), input, emptyMatch, limit = 3)
assertEquals("".split(""), "".split(emptyMatch))
testSplitEquals("".split(""), "", emptyMatch)
val emptyMatchBeforeT = "(?=t)".toRegex()
assertEquals(listOf("", "tes", "t"), input.split(emptyMatchBeforeT))
assertEquals(listOf("", "test"), input.split(emptyMatchBeforeT, limit = 2))
testSplitEquals(listOf("", "tes", "t"), input, emptyMatchBeforeT)
testSplitEquals(listOf("", "test"), input, emptyMatchBeforeT, limit = 2)
assertEquals(listOf("", "tee"), "tee".split(emptyMatchBeforeT))
testSplitEquals(listOf("", "tee"), "tee", emptyMatchBeforeT)
}
@Test fun splitByNoMatch() {
val input = "test"
val xMatch = "x".toRegex()
for (limit in 0..2) {
testSplitEquals(listOf(input), input, xMatch, limit)
}
}
@Test fun splitWithLimitOne() {
val input = "/12/456/7890/"
val regex = "\\d+".toRegex()
testSplitEquals(listOf(input), input, regex, limit = 1)
}
@Test fun findAllAndSplitToSequence() {
val input = "a12bc456def7890ghij"
val regex = "\\d+".toRegex()
val matches = regex.findAll(input).map { it.value }.iterator()
val splits = regex.splitToSequence(input).iterator()
assertEquals("12", matches.next())
assertEquals("a", splits.next())
assertEquals("456", matches.next())
assertEquals("bc", splits.next())
assertEquals("def", splits.next())
assertEquals("ghij", splits.next())
assertEquals("7890", matches.next())
assertFailsWith<NoSuchElementException> { matches.next() }
assertFailsWith<NoSuchElementException> { splits.next() }
}
}
+9 -1
View File
@@ -40,13 +40,21 @@ actual class Regex {
actual fun findAll(input: CharSequence, startIndex: Int): Sequence<MatchResult> = TODO("Wasm stdlib: Text")
/**
* Splits the [input] CharSequence around matches of this regular expression.
* Splits the [input] CharSequence to a list of strings around matches of this regular expression.
*
* @param limit Non-negative value specifying the maximum number of substrings the string can be split to.
* Zero by default means no limit is set.
*/
actual fun split(input: CharSequence, limit: Int): List<String> = TODO("Wasm stdlib: Text")
/**
* Splits the [input] CharSequence to a sequence of strings around matches of this regular expression.
*
* @param limit Non-negative value specifying the maximum number of substrings the string can be split to.
* Zero by default means no limit is set.
*/
public actual fun splitToSequence(input: CharSequence, limit: Int): Sequence<String> = TODO("Wasm stdlib: Text")
actual companion object {
actual fun fromLiteral(literal: String): Regex = TODO("Wasm stdlib: Text")
actual fun escape(literal: String): String = TODO("Wasm stdlib: Text")
@@ -5210,6 +5210,8 @@ public final class kotlin/text/Regex : java/io/Serializable {
public final fun replaceFirst (Ljava/lang/CharSequence;Ljava/lang/String;)Ljava/lang/String;
public final fun split (Ljava/lang/CharSequence;I)Ljava/util/List;
public static synthetic fun split$default (Lkotlin/text/Regex;Ljava/lang/CharSequence;IILjava/lang/Object;)Ljava/util/List;
public final fun splitToSequence (Ljava/lang/CharSequence;I)Lkotlin/sequences/Sequence;
public static synthetic fun splitToSequence$default (Lkotlin/text/Regex;Ljava/lang/CharSequence;IILjava/lang/Object;)Lkotlin/sequences/Sequence;
public final fun toPattern ()Ljava/util/regex/Pattern;
public fun toString ()Ljava/lang/String;
}