diff --git a/js/js.libraries/src/core/regex.kt b/js/js.libraries/src/core/regex.kt index c14114d8264..6ffbf00d655 100644 --- a/js/js.libraries/src/core/regex.kt +++ b/js/js.libraries/src/core/regex.kt @@ -109,7 +109,7 @@ public class Regex(pattern: String, options: Set) { * the given function [transform] that takes [MatchResult] and returns a string to be used as a * replacement for that match. */ - public inline fun replace(input: CharSequence, transform: (MatchResult) -> String): String { + public inline fun replace(input: CharSequence, transform: (MatchResult) -> CharSequence): String { var match = match(input) if (match == null) return input.toString() diff --git a/js/js.libraries/src/core/stringsCode.kt b/js/js.libraries/src/core/stringsCode.kt index 852ba5f0486..66928967ac1 100644 --- a/js/js.libraries/src/core/stringsCode.kt +++ b/js/js.libraries/src/core/stringsCode.kt @@ -43,9 +43,7 @@ public inline fun String.matches(regex : String) : Boolean { return result != null && result.size() > 0 } -public inline fun CharSequence.isEmpty(): Boolean = this.length() == 0 - -public fun String.isBlank(): Boolean = length() == 0 || matches("^[\\s\\xA0]+$") +public fun CharSequence.isBlank(): Boolean = length() == 0 || (if (this is String) this else this.toString()).matches("^[\\s\\xA0]+$") public fun String?.equals(anotherString: String?, ignoreCase: Boolean = false): Boolean = if (this == null) diff --git a/libraries/stdlib/src/kotlin/text/Strings.kt b/libraries/stdlib/src/kotlin/text/Strings.kt index 126b0339405..ae71342df17 100644 --- a/libraries/stdlib/src/kotlin/text/Strings.kt +++ b/libraries/stdlib/src/kotlin/text/Strings.kt @@ -11,7 +11,7 @@ import kotlin.text.Regex /** * Returns the string with leading and trailing characters matching the [predicate] trimmed. */ -inline public fun String.trim(predicate: (Char) -> Boolean): String { +inline public fun CharSequence.trim(predicate: (Char) -> Boolean): String { var startIndex = 0 var endIndex = length() - 1 var startFound = false @@ -40,7 +40,7 @@ inline public fun String.trim(predicate: (Char) -> Boolean): String { /** * Returns the string with leading characters matching the [predicate] trimmed. */ -inline public fun String.trimStart(predicate: (Char) -> Boolean): String { +inline public fun CharSequence.trimStart(predicate: (Char) -> Boolean): String { for (index in this.indices) if (!predicate(this[index])) return substring(index) @@ -51,7 +51,7 @@ inline public fun String.trimStart(predicate: (Char) -> Boolean): String { /** * Returns the string with trailing characters matching the [predicate] trimmed. */ -inline public fun String.trimEnd(predicate: (Char) -> Boolean): String { +inline public fun CharSequence.trimEnd(predicate: (Char) -> Boolean): String { for (index in this.indices.reversed()) if (!predicate(this[index])) return substring(0, index + 1) @@ -62,32 +62,32 @@ inline public fun String.trimEnd(predicate: (Char) -> Boolean): String { /** * Returns the string with leading and trailing characters in the [chars] array trimmed. */ -public fun String.trim(vararg chars: Char): String = trim { it in chars } +public fun CharSequence.trim(vararg chars: Char): String = trim { it in chars } /** * Returns the string with leading and trailing characters in the [chars] array trimmed. */ -public fun String.trimStart(vararg chars: Char): String = trimStart { it in chars } +public fun CharSequence.trimStart(vararg chars: Char): String = trimStart { it in chars } /** * Returns the string with trailing characters in the [chars] array trimmed. */ -public fun String.trimEnd(vararg chars: Char): String = trimEnd { it in chars } +public fun CharSequence.trimEnd(vararg chars: Char): String = trimEnd { it in chars } /** * Returns a string with leading and trailing whitespace trimmed. */ -public fun String.trim(): String = trim { it.isWhitespace() } +public fun CharSequence.trim(): String = trim { it.isWhitespace() } /** * Returns a string with leading whitespace removed. */ -public fun String.trimStart(): String = trimStart { it.isWhitespace() } +public fun CharSequence.trimStart(): String = trimStart { it.isWhitespace() } /** * Returns a string with trailing whitespace removed. */ -public fun String.trimEnd(): String = trimEnd { it.isWhitespace() } +public fun CharSequence.trimEnd(): String = trimEnd { it.isWhitespace() } /** * Left pad a String with a specified character or space. @@ -97,11 +97,11 @@ public fun String.trimEnd(): String = trimEnd { it.isWhitespace() } * @returns Returns a string, of length at least [length], consisting of string prepended with [padChar] as many times. * as are necessary to reach that length. */ -public fun String.padStart(length: Int, padChar: Char = ' '): String { +public fun CharSequence.padStart(length: Int, padChar: Char = ' '): String { if (length < 0) throw IllegalArgumentException("String length $length is less than zero.") if (length <= this.length()) - return this + return this.toString() val sb = StringBuilder(length) for (i in 1..(length - this.length())) @@ -118,11 +118,11 @@ public fun String.padStart(length: Int, padChar: Char = ' '): String { * @returns Returns a string, of length at least [length], consisting of string prepended with [padChar] as many times. * as are necessary to reach that length. */ -public fun String.padEnd(length: Int, padChar: Char = ' '): String { +public fun CharSequence.padEnd(length: Int, padChar: Char = ' '): String { if (length < 0) throw IllegalArgumentException("String length $length is less than zero.") if (length <= this.length()) - return this + return this.toString() val sb = StringBuilder(length) sb.append(this) @@ -134,22 +134,22 @@ public fun String.padEnd(length: Int, padChar: Char = ' '): String { /** Returns `true` if the string is not `null` and not empty */ @Deprecated("Use !isNullOrEmpty() or isNullOrEmpty().not() for nullable strings.", ReplaceWith("this != null && this.isNotEmpty()")) @kotlin.jvm.JvmName("isNotEmptyNullable") -public fun String?.isNotEmpty(): Boolean = this != null && this.length() > 0 +public fun CharSequence?.isNotEmpty(): Boolean = this != null && this.length() > 0 /** * Returns `true` if this nullable string is either `null` or empty. */ -public fun String?.isNullOrEmpty(): Boolean = this == null || this.length() == 0 +public fun CharSequence?.isNullOrEmpty(): Boolean = this == null || this.length() == 0 /** * Returns `true` if this string is empty (contains no characters). */ -public fun String.isEmpty(): Boolean = length() == 0 +public fun CharSequence.isEmpty(): Boolean = length() == 0 /** * Returns `true` if this string is not empty. */ -public fun String.isNotEmpty(): Boolean = length() > 0 +public fun CharSequence.isNotEmpty(): Boolean = length() > 0 // implemented differently in JVM and JS //public fun String.isBlank(): Boolean = length() == 0 || all { it.isWhitespace() } @@ -158,12 +158,12 @@ public fun String.isNotEmpty(): Boolean = length() > 0 /** * Returns `true` if this string is not empty and contains some characters except of whitespace characters. */ -public fun String.isNotBlank(): Boolean = !isBlank() +public fun CharSequence.isNotBlank(): Boolean = !isBlank() /** * Returns `true` if this nullable string is either `null` or empty or consists solely of whitespace characters. */ -public fun String?.isNullOrBlank(): Boolean = this == null || this.isBlank() +public fun CharSequence?.isNullOrBlank(): Boolean = this == null || this.isBlank() /** * Iterator for characters of given CharSequence. @@ -182,13 +182,13 @@ public fun String?.orEmpty(): String = this ?: "" /** * Returns the range of valid character indices for this string. */ -public val String.indices: IntRange +public val CharSequence.indices: IntRange get() = 0..length() - 1 /** * Returns the index of the last character in the String or -1 if the String is empty. */ -public val String.lastIndex: Int +public val CharSequence.lastIndex: Int get() = this.length() - 1 /** @@ -220,11 +220,27 @@ public fun CharSequence.slice(indices: Iterable): CharSequence { return sb.toString() } +/** + * Returns a subsequence of this sequence specified by given [range]. + */ +public fun CharSequence.slice(range: IntRange): CharSequence { + return subSequence(range.start, range.end + 1) // inclusive +} + /** * Returns a substring specified by the given [range]. */ public fun String.substring(range: IntRange): String = substring(range.start, range.end + 1) +/** + * Returns a subsequence of this sequence specified by the given [range]. + */ +public fun CharSequence.subSequence(range: IntRange): CharSequence = subSequence(range.start, range.end + 1) + +public fun CharSequence.substring(start: Int, end: Int = length): String = subSequence(start, end).toString() + +public fun CharSequence.substring(range: IntRange): String = subSequence(range.start, range.end + 1).toString() + /** * Creates a string from all the elements separated using [separator] and using the given [prefix] and [postfix] if supplied. * If the collection could be huge, you can specify a non-negative value of [limit], in which case only the first [limit] @@ -332,7 +348,7 @@ public fun String.substringAfterLast(delimiter: String, missingDelimiterValue: S * @param firstIndex the index of the first character to be replaced. * @param lastIndex the index of the first character after the replacement to keep in the string. */ -public fun String.replaceRange(firstIndex: Int, lastIndex: Int, replacement: String): String { +public fun CharSequence.replaceRange(firstIndex: Int, lastIndex: Int, replacement: CharSequence): String { if (lastIndex < firstIndex) throw IndexOutOfBoundsException("Last index ($lastIndex) is less than first index ($firstIndex)") val sb = StringBuilder() @@ -347,7 +363,7 @@ public fun String.replaceRange(firstIndex: Int, lastIndex: Int, replacement: Str * * The end index of the [range] is included in the part to be replaced. */ -public fun String.replaceRange(range: IntRange, replacement: String): String = replaceRange(range.start, range.end + 1, replacement) +public fun CharSequence.replaceRange(range: IntRange, replacement: CharSequence): String = replaceRange(range.start, range.end + 1, replacement) /** * Removes the part of a string at a given range. @@ -356,12 +372,12 @@ public fun String.replaceRange(range: IntRange, replacement: String): String = r * * [lastIndex] is not included in the removed part. */ -public fun String.removeRange(firstIndex: Int, lastIndex: Int): String { +public fun CharSequence.removeRange(firstIndex: Int, lastIndex: Int): String { if (lastIndex < firstIndex) throw IndexOutOfBoundsException("Last index ($lastIndex) is less than first index ($firstIndex)") if (lastIndex == firstIndex) - return this + return this.toString() val sb = StringBuilder(length() - (lastIndex - firstIndex)) sb.append(this, 0, firstIndex) @@ -374,7 +390,7 @@ public fun String.removeRange(firstIndex: Int, lastIndex: Int): String { * * The end index of the [range] is included in the removed part. */ -public fun String.removeRange(range: IntRange): String = removeRange(range.start, range.end + 1) +public fun CharSequence.removeRange(range: IntRange): String = removeRange(range.start, range.end + 1) /** * If this string starts with the given [prefix], returns a copy of this string @@ -500,39 +516,39 @@ public fun String.replaceBeforeLast(delimiter: String, replacement: String, miss * The [replacement] can consist of any combination of literal text and $-substitutions. To treat the replacement string * literally escape it with the [kotlin.text.Regex.Companion.escapeReplacement] method. */ -public fun String.replace(regex: Regex, replacement: String): String = regex.replace(this, replacement) +public fun CharSequence.replace(regex: Regex, replacement: String): String = regex.replace(this, replacement) /** * Returns a new string obtained by replacing each substring of this string that matches the given regular expression * with the result of the given function [transform] that takes [MatchResult] and returns a string to be used as a * replacement for that match. */ -public fun String.replace(regex: Regex, transform: (MatchResult) -> String): String = regex.replace(this, transform) +public fun CharSequence.replace(regex: Regex, transform: (MatchResult) -> CharSequence): String = regex.replace(this, transform) /** * Replaces the first occurrence of the given regular expression [regex] in this string with specified [replacement] expression. * * @param replacement A replacement expression that can include substitutions. See [Regex.replaceFirst] for details. */ -public fun String.replaceFirst(regex: Regex, replacement: String): String = regex.replaceFirst(this, replacement) +public fun CharSequence.replaceFirst(regex: Regex, replacement: String): String = regex.replaceFirst(this, replacement) /** * Returns `true` if this string matches the given regular expression. */ -public fun String.matches(regex: Regex): Boolean = regex.matches(this) +public fun CharSequence.matches(regex: Regex): Boolean = regex.matches(this) /** * Returns `true` if this string starts with the specified character. */ -public fun String.startsWith(char: Char, ignoreCase: Boolean = false): Boolean = +public fun CharSequence.startsWith(char: Char, ignoreCase: Boolean = false): Boolean = this.length() > 0 && this[0].equals(char, ignoreCase) /** * Returns `true` if this string ends with the specified character. */ -public fun String.endsWith(char: Char, ignoreCase: Boolean = false): Boolean = +public fun CharSequence.endsWith(char: Char, ignoreCase: Boolean = false): Boolean = this.length() > 0 && this[lastIndex].equals(char, ignoreCase) @@ -584,8 +600,8 @@ public fun CharSequence.commonSuffixWith(other: CharSequence, ignoreCase: Boolea // indexOfAny() -private fun String.findAnyOf(chars: CharArray, startIndex: Int, ignoreCase: Boolean, last: Boolean): Pair? { - if (!ignoreCase && chars.size() == 1) { +private fun CharSequence.findAnyOf(chars: CharArray, startIndex: Int, ignoreCase: Boolean, last: Boolean): Pair? { + if (!ignoreCase && chars.size() == 1 && this is String) { val char = chars.single() val index = if (!last) nativeIndexOf(char, startIndex) else nativeLastIndexOf(char, startIndex) return if (index < 0) null else index to char @@ -610,7 +626,7 @@ private fun String.findAnyOf(chars: CharArray, startIndex: Int, ignoreCase: Bool * @returns An index of the first occurrence of matched character from [chars] or -1 if none of [chars] are found. * */ -public fun String.indexOfAny(chars: CharArray, startIndex: Int = 0, ignoreCase: Boolean = false): Int = +public fun CharSequence.indexOfAny(chars: CharArray, startIndex: Int = 0, ignoreCase: Boolean = false): Int = findAnyOf(chars, startIndex, ignoreCase, last = false)?.first ?: -1 /** @@ -622,7 +638,7 @@ public fun String.indexOfAny(chars: CharArray, startIndex: Int = 0, ignoreCase: * @returns An index of the last occurrence of matched character from [chars] or -1 if none of [chars] are found. * */ -public fun String.lastIndexOfAny(chars: CharArray, startIndex: Int = lastIndex, ignoreCase: Boolean = false): Int = +public fun CharSequence.lastIndexOfAny(chars: CharArray, startIndex: Int = lastIndex, ignoreCase: Boolean = false): Int = findAnyOf(chars, startIndex, ignoreCase, last = true)?.first ?: -1 @@ -711,8 +727,8 @@ public fun String.lastIndexOfAny(strings: Collection, startIndex: Int = * @param ignoreCase `true` to ignore character case when matching a character. By default `false`. * @returns An index of the first occurrence of [char] or -1 if none is found. */ -public fun String.indexOf(char: Char, startIndex: Int = 0, ignoreCase: Boolean = false): Int { - return if (ignoreCase) +public fun CharSequence.indexOf(char: Char, startIndex: Int = 0, ignoreCase: Boolean = false): Int { + return if (ignoreCase || this !is String) indexOfAny(charArrayOf(char), startIndex, ignoreCase) else nativeIndexOf(char, startIndex) @@ -724,7 +740,6 @@ public fun String.indexOf(char: Char, startIndex: Int = 0, ignoreCase: Boolean = * @param ignoreCase `true` to ignore character case when matching a string. By default `false`. * @returns An index of the first occurrence of [string] or -1 if none is found. */ -@kotlin.jvm.JvmOverloads public fun String.indexOf(string: String, startIndex: Int = 0, ignoreCase: Boolean = false): Int { return if (ignoreCase) indexOfAny(listOf(string), startIndex, ignoreCase) @@ -739,8 +754,8 @@ public fun String.indexOf(string: String, startIndex: Int = 0, ignoreCase: Boole * @param ignoreCase `true` to ignore character case when matching a character. By default `false`. * @returns An index of the first occurrence of [char] or -1 if none is found. */ -public fun String.lastIndexOf(char: Char, startIndex: Int = lastIndex, ignoreCase: Boolean = false): Int { - return if (ignoreCase) +public fun CharSequence.lastIndexOf(char: Char, startIndex: Int = lastIndex, ignoreCase: Boolean = false): Int { + return if (ignoreCase || this !is String) lastIndexOfAny(charArrayOf(char), startIndex, ignoreCase) else nativeLastIndexOf(char, startIndex) @@ -774,7 +789,7 @@ public operator fun String.contains(seq: CharSequence, ignoreCase: Boolean = fal * * @param ignoreCase `true` to ignore character case when comparing characters. By default `false`. */ -public operator fun String.contains(char: Char, ignoreCase: Boolean = false): Boolean = +public operator fun CharSequence.contains(char: Char, ignoreCase: Boolean = false): Boolean = indexOf(char, ignoreCase = ignoreCase) >= 0 /** diff --git a/libraries/stdlib/src/kotlin/text/StringsJVM.kt b/libraries/stdlib/src/kotlin/text/StringsJVM.kt index 817446f91d8..f8c467ca626 100644 --- a/libraries/stdlib/src/kotlin/text/StringsJVM.kt +++ b/libraries/stdlib/src/kotlin/text/StringsJVM.kt @@ -298,7 +298,7 @@ public fun String.intern(): String = (this as java.lang.String).intern() /** * Returns `true` if this string is empty or consists solely of whitespace characters. */ -public fun String.isBlank(): Boolean = length() == 0 || all { it.isWhitespace() } +public fun CharSequence.isBlank(): Boolean = length() == 0 || indices.all { this[it].isWhitespace() } /** * Returns the index within this string that is offset from the given [index] by [codePointOffset] code points. @@ -392,12 +392,7 @@ public fun String.toByteArray(charset: String): ByteArray = (this as java.lang.S */ public fun String.toByteArray(charset: Charset = Charsets.UTF_8): ByteArray = (this as java.lang.String).getBytes(charset) -/** - * Returns a subsequence of this sequence specified by given [range]. - */ -public fun CharSequence.slice(range: IntRange): CharSequence { - return subSequence(range.start, range.end + 1) // inclusive -} + /** * Converts the string into a regular expression [Pattern] optionally @@ -437,7 +432,7 @@ public fun String.repeat(n: Int): String { if (n < 0) throw IllegalArgumentException("Value should be non-negative, but was $n") - val sb = StringBuilder() + val sb = StringBuilder(n * length()) for (i in 1..n) { sb.append(this) } @@ -448,7 +443,7 @@ public fun String.repeat(n: Int): String { * Appends the contents of this string, excluding the first characters that satisfy the given [predicate], * to the given Appendable. */ -public inline fun String.dropWhileTo(result: T, predicate: (Char) -> Boolean): T { +public inline fun CharSequence.dropWhileTo(result: T, predicate: (Char) -> Boolean): T { var start = true for (element in this) { if (start && predicate(element)) { @@ -464,7 +459,7 @@ public inline fun String.dropWhileTo(result: T, predicate: (Cha /** * Appends the first characters from this string that satisfy the given [predicate] to the given Appendable. */ -public inline fun String.takeWhileTo(result: T, predicate: (Char) -> Boolean): T { +public inline fun CharSequence.takeWhileTo(result: T, predicate: (Char) -> Boolean): T { for (c in this) if (predicate(c)) result.append(c) else break return result } diff --git a/libraries/stdlib/src/kotlin/text/regex/RegexJVM.kt b/libraries/stdlib/src/kotlin/text/regex/RegexJVM.kt index a52024f207d..7c5237779ed 100644 --- a/libraries/stdlib/src/kotlin/text/regex/RegexJVM.kt +++ b/libraries/stdlib/src/kotlin/text/regex/RegexJVM.kt @@ -158,7 +158,7 @@ public class Regex internal constructor(private val nativePattern: Pattern) { * the given function [transform] that takes [MatchResult] and returns a string to be used as a * replacement for that match. */ - public inline fun replace(input: CharSequence, transform: (MatchResult) -> String): String { + public inline fun replace(input: CharSequence, transform: (MatchResult) -> CharSequence): String { var match: MatchResult? = find(input) ?: return input.toString() var lastStart = 0