diff --git a/backend.native/tests/runtime/text/string_builder0.kt b/backend.native/tests/runtime/text/string_builder0.kt index 2931ce494fa..36b3ed2b7a7 100644 --- a/backend.native/tests/runtime/text/string_builder0.kt +++ b/backend.native/tests/runtime/text/string_builder0.kt @@ -35,7 +35,8 @@ fun assertException(body: () -> Unit) { try { body() throw AssertionError ("Test failed: no IndexOutOfBoundsException on wrong indices") - } catch (e: IndexOutOfBoundsException) {} + } catch (e: IndexOutOfBoundsException) { + } catch (e: IllegalArgumentException) {} } // Insert =================================================================================================== diff --git a/runtime/src/main/kotlin/kotlin/text/Appendable.kt b/runtime/src/main/kotlin/kotlin/text/Appendable.kt index bda5d19ea6b..79c68c4ce03 100644 --- a/runtime/src/main/kotlin/kotlin/text/Appendable.kt +++ b/runtime/src/main/kotlin/kotlin/text/Appendable.kt @@ -5,8 +5,33 @@ package kotlin.text +/** + * An object to which char sequences and values can be appended. + */ public actual interface Appendable { + /** + * Appends the specified character [c] to this Appendable and returns this instance. + * + * @param c the character to append. + */ actual fun append(c: Char): Appendable + + /** + * Appends the specified character sequence [csq] to this Appendable and returns this instance. + * + * @param csq the character sequence to append. If [csq] is `null`, then the four characters `"null"` are appended to this Appendable. + */ actual fun append(csq: CharSequence?): Appendable + + /** + * Appends a subsequence of the specified character sequence [csq] to this Appendable and returns this instance. + * + * @param csq the character sequence from which a subsequence is appended. If [csq] is `null`, + * then characters are appended as if [csq] contained the four characters `"null"`. + * @param start the beginning (inclusive) of the subsequence to append. + * @param end the end (exclusive) of the subsequence to append. + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] when [start] or [end] is out of range of the [csq] character sequence indices or when `start > end`. + */ actual fun append(csq: CharSequence?, start: Int, end: Int): Appendable } diff --git a/runtime/src/main/kotlin/kotlin/text/StringBuilder.kt b/runtime/src/main/kotlin/kotlin/text/StringBuilder.kt index 4c48f9a80b1..abb3a0f9f18 100644 --- a/runtime/src/main/kotlin/kotlin/text/StringBuilder.kt +++ b/runtime/src/main/kotlin/kotlin/text/StringBuilder.kt @@ -6,34 +6,30 @@ package kotlin.text /** - * Clears the content of this string builder making it empty. + * A mutable sequence of characters. * - * @sample samples.text.Strings.clearStringBuilder + * String builder can be used to efficiently perform multiple string manipulation operations. */ -@SinceKotlin("1.3") -public actual fun StringBuilder.clear(): StringBuilder = apply { setLength(0) } - -/** - * Sets the character at the specified [index] to the specified [value]. - */ -@kotlin.internal.InlineOnly -public inline operator fun StringBuilder.set(index: Int, value: Char): Unit = this.setCharAt(index, value) - actual class StringBuilder private constructor ( private var array: CharArray) : CharSequence, Appendable { + /** Constructs an empty string builder. */ actual constructor() : this(10) + /** Constructs an empty string builder with the specified initial [capacity]. */ actual constructor(capacity: Int) : this(CharArray(capacity)) - constructor(string: String) : this(string.toCharArray()) { + /** Constructs a string builder that contains the same characters as the specified [content] string. */ + actual constructor(content: String) : this(content.toCharArray()) { _length = array.size } + /** Constructs a string builder that contains the same characters as the specified [content] char sequence. */ actual constructor(content: CharSequence): this(content.length) { append(content) } + // Of CharSequence. private var _length: Int = 0 set(capacity) { ensureCapacity(capacity) @@ -47,34 +43,33 @@ actual class StringBuilder private constructor ( return array[index] } - fun setLength(l: Int) { - _length = l - } - actual override fun subSequence(startIndex: Int, endIndex: Int): CharSequence = substring(startIndex, endIndex) - override fun toString(): String = unsafeStringFromCharArray(array, 0, _length) - - fun substring(startIndex: Int, endIndex: Int): String { - checkInsertIndex(startIndex) - checkInsertIndexFrom(endIndex, startIndex) - return unsafeStringFromCharArray(array, startIndex, endIndex - startIndex) + // Of Appenable. + actual override fun append(c: Char) : StringBuilder { + ensureExtraCapacity(1) + array[_length++] = c + return this } - fun trimToSize() { - if (_length < array.size) - array = array.copyOf(_length) + actual override fun append(csq: CharSequence?): StringBuilder { + // Kotlin/JVM processes null as if the argument was "null" char sequence. + val toAppend = csq ?: "null" + return append(toAppend, 0, toAppend.length) } - fun ensureCapacity(capacity: Int) { - if (capacity > array.size) { - var newSize = array.size * 2 + 2 - if (capacity > newSize) - newSize = capacity - array = array.copyOf(newSize) - } - } + @UseExperimental(ExperimentalStdlibApi::class) + actual override fun append(csq: CharSequence?, start: Int, end: Int): StringBuilder = this.appendRange(csq, start, end) + /** + * Reverses the contents of this string builder and returns this instance. + * + * Surrogate pairs included in this string builder are treated as single characters. + * Therefore, the order of the high-low surrogates is never reversed. + * + * Note that the reverse operation may produce new surrogate pairs that were unpaired low-surrogates and high-surrogates before the operation. + * For example, reversing `"\uDC00\uD800"` produces `"\uD800\uDC00"` which is a valid surrogate pair. + */ // Based on Apache Harmony implementation. actual fun reverse(): StringBuilder { if (this.length < 2) { @@ -142,36 +137,446 @@ actual class StringBuilder private constructor ( return this } - fun insert(index: Int, c: Char): StringBuilder { + /** + * Appends the string representation of the specified object [value] to this string builder and returns this instance. + * + * The overall effect is exactly as if the [value] were converted to a string by the `value.toString()` method, + * and then that string was appended to this string builder. + */ + actual fun append(value: Any?): StringBuilder = append(value.toString()) + + /** + * Appends the string representation of the specified boolean [value] to this string builder and returns this instance. + * + * The overall effect is exactly as if the [value] were converted to a string by the `value.toString()` method, + * and then that string was appended to this string builder. + */ + // TODO: optimize those! + actual fun append(value: Boolean): StringBuilder = append(value.toString()) + fun append(value: Byte): StringBuilder = append(value.toString()) + fun append(value: Short): StringBuilder = append(value.toString()) + fun append(value: Int): StringBuilder { + ensureExtraCapacity(11) + _length += insertInt(array, _length, value) + return this + } + fun append(value: Long): StringBuilder = append(value.toString()) + fun append(value: Float): StringBuilder = append(value.toString()) + fun append(value: Double): StringBuilder = append(value.toString()) + + /** + * Appends characters in the specified character array [value] to this string builder and returns this instance. + * + * Characters are appended in order, starting at the index 0. + */ + actual fun append(value: CharArray): StringBuilder { + ensureExtraCapacity(value.size) + value.copyInto(array, _length) + _length += value.size + return this + } + + /** + * Appends the specified string [value] to this string builder and returns this instance. + */ + actual fun append(value: String): StringBuilder { + ensureExtraCapacity(value.length) + _length += insertString(array, _length, value) + return this + } + + /** + * Returns the current capacity of this string builder. + * + * The capacity is the maximum length this string builder can have before an allocation occurs. + */ + actual fun capacity(): Int = array.size + + /** + * Ensures that the capacity of this string builder is at least equal to the specified [minimumCapacity]. + * + * If the current capacity is less than the [minimumCapacity], a new backing storage is allocated with greater capacity. + * Otherwise, this method takes no action and simply returns. + */ + actual fun ensureCapacity(minimumCapacity: Int) { + if (minimumCapacity > array.size) { + var newSize = array.size * 2 + 2 + if (minimumCapacity > newSize) + newSize = minimumCapacity + array = array.copyOf(newSize) + } + } + + /** + * Returns the index within this string builder of the first occurrence of the specified [string]. + * + * Returns `-1` if the specified [string] does not occur in this string builder. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + actual fun indexOf(string: String): Int { + return (this as CharSequence).indexOf(string, startIndex = 0, ignoreCase = false) + } + + /** + * Returns the index within this string builder of the first occurrence of the specified [string], + * starting at the specified [startIndex]. + * + * Returns `-1` if the specified [string] does not occur in this string builder starting at the specified [startIndex]. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + actual fun indexOf(string: String, startIndex: Int): Int { + if (string.isEmpty() && startIndex >= _length) return _length + return (this as CharSequence).indexOf(string, startIndex, ignoreCase = false) + } + + /** + * Returns the index within this string builder of the last occurrence of the specified [string]. + * The last occurrence of empty string `""` is considered to be at the index equal to `this.length`. + * + * Returns `-1` if the specified [string] does not occur in this string builder. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + actual fun lastIndexOf(string: String): Int { + if (string.isEmpty()) return _length + return (this as CharSequence).lastIndexOf(string, startIndex = lastIndex, ignoreCase = false) + } + + /** + * Returns the index within this string builder of the last occurrence of the specified [string], + * starting from the specified [startIndex] toward the beginning. + * + * Returns `-1` if the specified [string] does not occur in this string builder starting at the specified [startIndex]. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + actual fun lastIndexOf(string: String, startIndex: Int): Int { + if (string.isEmpty() && startIndex >= _length) return _length + return (this as CharSequence).lastIndexOf(string, startIndex, ignoreCase = false) + } + + /** + * Inserts the string representation of the specified boolean [value] into this string builder at the specified [index] and returns this instance. + * + * The overall effect is exactly as if the [value] were converted to a string by the `value.toString()` method, + * and then that string was inserted into this string builder at the specified [index]. + * + * @throws IndexOutOfBoundsException if [index] is less than zero or greater than the length of this string builder. + */ + // TODO: optimize those! + actual fun insert(index: Int, value: Boolean): StringBuilder = insert(index, value.toString()) + fun insert(index: Int, value: Byte) = insert(index, value.toString()) + fun insert(index: Int, value: Short) = insert(index, value.toString()) + fun insert(index: Int, value: Int) = insert(index, value.toString()) + fun insert(index: Int, value: Long) = insert(index, value.toString()) + fun insert(index: Int, value: Float) = insert(index, value.toString()) + fun insert(index: Int, value: Double) = insert(index, value.toString()) + + /** + * Inserts the specified character [value] into this string builder at the specified [index] and returns this instance. + * + * @throws IndexOutOfBoundsException if [index] is less than zero or greater than the length of this string builder. + */ + actual fun insert(index: Int, value: Char): StringBuilder { checkInsertIndex(index) ensureExtraCapacity(1) val newLastIndex = lastIndex + 1 for (i in newLastIndex downTo index + 1) { array[i] = array[i - 1] } - array[index] = c + array[index] = value _length++ return this } - fun insert(index: Int, csq: CharSequence?): StringBuilder { - // Kotlin/JVM inserts the "null" string if the argument is null. - val toInsert = csq ?: "null" - return insert(index, toInsert, 0, toInsert.length) + /** + * Inserts characters in the specified character array [value] into this string builder at the specified [index] and returns this instance. + * + * The inserted characters go in same order as in the [value] character array, starting at [index]. + * + * @throws IndexOutOfBoundsException if [index] is less than zero or greater than the length of this string builder. + */ + actual fun insert(index: Int, value: CharArray): StringBuilder { + checkInsertIndex(index) + ensureExtraCapacity(value.size) + + array.copyInto(array, startIndex = index, endIndex = _length, destinationOffset = index + value.size) + value.copyInto(array, destinationOffset = index) + + _length += value.size + return this } - fun insert(index: Int, csq: CharSequence?, start: Int, end: Int): StringBuilder { - // Kotlin/JVM processes null as if the argument was "null" char sequence. - val toInsert = csq ?: "null" - if (start < 0 || end < start || start > toInsert.length) throw IndexOutOfBoundsException() + /** + * Inserts characters in the specified character sequence [value] into this string builder at the specified [index] and returns this instance. + * + * The inserted characters go in the same order as in the [value] character sequence, starting at [index]. + * + * @param index the position in this string builder to insert at. + * @param value the character sequence from which characters are inserted. If [value] is `null`, then the four characters `"null"` are inserted. + * + * @throws IndexOutOfBoundsException if [index] is less than zero or greater than the length of this string builder. + */ + @UseExperimental(ExperimentalStdlibApi::class) + actual fun insert(index: Int, value: CharSequence?): StringBuilder { + // Kotlin/JVM inserts the "null" string if the argument is null. + val toInsert = value ?: "null" + return insertRange(index, toInsert, 0, toInsert.length) + } + + /** + * Inserts the string representation of the specified object [value] into this string builder at the specified [index] and returns this instance. + * + * The overall effect is exactly as if the [value] were converted to a string by the `value.toString()` method, + * and then that string was inserted into this string builder at the specified [index]. + * + * @throws IndexOutOfBoundsException if [index] is less than zero or greater than the length of this string builder. + */ + actual fun insert(index: Int, value: Any?): StringBuilder = insert(index, value.toString()) + + /** + * Inserts the string [value] into this string builder at the specified [index] and returns this instance. + * + * @throws IndexOutOfBoundsException if [index] is less than zero or greater than the length of this string builder. + */ + actual fun insert(index: Int, value: String): StringBuilder { checkInsertIndex(index) - val extraLength = end - start + ensureExtraCapacity(value.length) + array.copyInto(array, startIndex = index, endIndex = _length, destinationOffset = index + value.length) + _length += insertString(array, index, value) + return this + } + + /** + * Sets the length of this string builder to the specified [newLength]. + * + * If the [newLength] is less than the current length, it is changed to the specified [newLength]. + * Otherwise, null characters '\u0000' are appended to this string builder until its length is less than the [newLength]. + * + * Note that in Kotlin/JS [set] operator function has non-constant execution time complexity. + * Therefore, increasing length of this string builder and then updating each character by index may slow down your program. + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] if [newLength] is less than zero. + */ + actual fun setLength(newLength: Int) { + if (newLength < 0) { + throw IllegalArgumentException("Negative new length: $newLength.") + } + + if (newLength > _length) { + array.fill('\u0000', _length, newLength.coerceAtMost(array.size)) + } + _length = newLength + } + + /** + * Returns a new [String] that contains characters in this string builder at [startIndex] (inclusive) and up to the [endIndex] (exclusive). + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] when [startIndex] or [endIndex] is out of range of this string builder indices or when `startIndex > endIndex`. + */ + actual fun substring(startIndex: Int, endIndex: Int): String { + checkBoundsIndexes(startIndex, endIndex, _length) + return unsafeStringFromCharArray(array, startIndex, endIndex - startIndex) + } + + /** + * Returns a new [String] that contains characters in this string builder at [startIndex] (inclusive) and up to the [length] (exclusive). + * + * @throws IndexOutOfBoundsException if [startIndex] is less than zero or greater than the length of this string builder. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + actual fun substring(startIndex: Int): String { + return substring(startIndex, _length) + } + + /** + * Attempts to reduce storage used for this string builder. + * + * If the backing storage of this string builder is larger than necessary to hold its current contents, + * then it may be resized to become more space efficient. + * Calling this method may, but is not required to, affect the value of the [capacity] property. + */ + actual fun trimToSize() { + if (_length < array.size) + array = array.copyOf(_length) + } + + override fun toString(): String = unsafeStringFromCharArray(array, 0, _length) + + /** + * Sets the character at the specified [index] to the specified [value]. + * + * @throws IndexOutOfBoundsException if [index] is out of bounds of this string builder. + */ + operator fun set(index: Int, value: Char) { + checkIndex(index) + array[index] = value + } + + /** + * Replaces characters in the specified range of this string builder with characters in the specified string [value] and returns this instance. + * + * @param startIndex the beginning (inclusive) of the range to replace. + * @param endIndex the end (exclusive) of the range to replace. + * @param value the string to replace with. + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] if [startIndex] is less than zero, greater than the length of this string builder, or `startIndex > endIndex`. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + fun setRange(startIndex: Int, endIndex: Int, value: String): StringBuilder { + checkReplaceRange(startIndex, endIndex, _length) + + val coercedEndIndex = endIndex.coerceAtMost(_length) + val lengthDiff = value.length - (coercedEndIndex - startIndex) + ensureExtraCapacity(_length + lengthDiff) + array.copyInto(array, startIndex = coercedEndIndex, endIndex = _length, destinationOffset = startIndex + value.length) + var replaceIndex = startIndex + for (index in 0 until value.length) array[replaceIndex++] = value[index] // optimize + _length += lengthDiff + + return this + } + + /** + * Removes the character at the specified [index] from this string builder and returns this instance. + * + * If the `Char` at the specified [index] is part of a supplementary code point, this method does not remove the entire supplementary character. + * + * @param index the index of `Char` to remove. + * + * @throws IndexOutOfBoundsException if [index] is out of bounds of this string builder. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + fun deleteAt(index: Int): StringBuilder { + checkIndex(index) + array.copyInto(array, startIndex = index + 1, endIndex = _length, destinationOffset = index) + --_length + return this + } + + /** + * Removes characters in the specified range from this string builder and returns this instance. + * + * @param startIndex the beginning (inclusive) of the range to remove. + * @param endIndex the end (exclusive) of the range to remove. + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] when [startIndex] is out of range of this string builder indices or when `startIndex > endIndex`. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + fun deleteRange(startIndex: Int, endIndex: Int): StringBuilder { + checkReplaceRange(startIndex, endIndex, _length) + + val coercedEndIndex = endIndex.coerceAtMost(_length) + array.copyInto(array, startIndex = coercedEndIndex, endIndex = _length, destinationOffset = startIndex) + _length -= coercedEndIndex - startIndex + return this + } + + /** + * Copies characters from this string builder into the [destination] character array. + * + * @param destination the array to copy to. + * @param destinationOffset the position in the array to copy to, 0 by default. + * @param startIndex the beginning (inclusive) of the range to copy, 0 by default. + * @param endIndex the end (exclusive) of the range to copy, length of this string builder by default. + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] when [startIndex] or [endIndex] is out of range of this string builder indices or when `startIndex > endIndex`. + * @throws IndexOutOfBoundsException when the subrange doesn't fit into the [destination] array starting at the specified [destinationOffset], + * or when that index is out of the [destination] array indices range. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + fun toCharArray(destination: CharArray, destinationOffset: Int = 0, startIndex: Int = 0, endIndex: Int = this.length) { + checkBoundsIndexes(startIndex, endIndex, _length) + checkBoundsIndexes(destinationOffset, destinationOffset + endIndex - startIndex, destination.size) + + array.copyInto(destination, destinationOffset, startIndex, endIndex) + } + + /** + * Appends characters in a subarray of the specified character array [value] to this string builder and returns this instance. + * + * Characters are appended in order, starting at specified [startIndex]. + * + * @param value the array from which characters are appended. + * @param startIndex the beginning (inclusive) of the subarray to append. + * @param endIndex the end (exclusive) of the subarray to append. + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] when [startIndex] or [endIndex] is out of range of the [value] array indices or when `startIndex > endIndex`. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + fun appendRange(value: CharArray, startIndex: Int, endIndex: Int): StringBuilder { + checkBoundsIndexes(startIndex, endIndex, value.size) + val extraLength = endIndex - startIndex + ensureExtraCapacity(extraLength) + value.copyInto(array, _length, startIndex, endIndex) + _length += extraLength + return this + } + + /** + * Appends a subsequence of the specified character sequence [value] to this string builder and returns this instance. + * + * @param value the character sequence from which a subsequence is appended. If [value] is `null`, + * then characters are appended as if [value] contained the four characters `"null"`. + * @param startIndex the beginning (inclusive) of the subsequence to append. + * @param endIndex the end (exclusive) of the subsequence to append. + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] when [startIndex] or [endIndex] is out of range of the [value] character sequence indices or when `startIndex > endIndex`. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + fun appendRange(value: CharSequence?, startIndex: Int, endIndex: Int): StringBuilder { + // Kotlin/JVM processes null as if the argument was "null" char sequence. + val toAppend = value ?: "null" + checkBoundsIndexes(startIndex, endIndex, toAppend.length) + val extraLength = endIndex - startIndex + ensureExtraCapacity(extraLength) + (toAppend as? String)?.let { + _length += insertString(array, _length, it, startIndex, extraLength) + return this + } + var index = startIndex + while (index < endIndex) + array[_length++] = toAppend[index++] + return this + } + + /** + * Inserts characters in a subsequence of the specified character sequence [value] into this string builder at the specified [index] and returns this instance. + * + * The inserted characters go in the same order as in the [value] character sequence, starting at [index]. + * + * @param index the position in this string builder to insert at. + * @param value the character sequence from which a subsequence is inserted. If [value] is `null`, + * then characters will be inserted as if [value] contained the four characters `"null"`. + * @param startIndex the beginning (inclusive) of the subsequence to insert. + * @param endIndex the end (exclusive) of the subsequence to insert. + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] when [startIndex] or [endIndex] is out of range of the [value] character sequence indices or when `startIndex > endIndex`. + * @throws IndexOutOfBoundsException if [index] is less than zero or greater than the length of this string builder. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + fun insertRange(index: Int, value: CharSequence?, startIndex: Int, endIndex: Int): StringBuilder { + // Kotlin/JVM processes null as if the argument was "null" char sequence. + val toInsert = value ?: "null" + checkBoundsIndexes(startIndex, endIndex, toInsert.length) + checkInsertIndex(index) + val extraLength = endIndex - startIndex ensureExtraCapacity(extraLength) array.copyInto(array, startIndex = index, endIndex = _length, destinationOffset = index + extraLength) - var from = start + var from = startIndex var to = index - while (from < end) { + while (from < endIndex) { array[to++] = toInsert[from++] } @@ -179,102 +584,33 @@ actual class StringBuilder private constructor ( return this } - fun insert(index: Int, chars: CharArray): StringBuilder { + /** + * Inserts characters in a subarray of the specified character array [value] into this string builder at the specified [index] and returns this instance. + * + * The inserted characters go in same order as in the [value] array, starting at [index]. + * + * @param index the position in this string builder to insert at. + * @param value the array from which characters are inserted. + * @param startIndex the beginning (inclusive) of the subarray to insert. + * @param endIndex the end (exclusive) of the subarray to insert. + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] when [startIndex] or [endIndex] is out of range of the [value] array indices or when `startIndex > endIndex`. + * @throws IndexOutOfBoundsException if [index] is less than zero or greater than the length of this string builder. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + fun insertRange(index: Int, value: CharArray, startIndex: Int, endIndex: Int): StringBuilder { checkInsertIndex(index) - ensureExtraCapacity(chars.size) + checkBoundsIndexes(startIndex, endIndex, value.size) - array.copyInto(array, startIndex = index, endIndex = _length, destinationOffset = index + chars.size) - chars.copyInto(array, destinationOffset = index) + val extraLength = endIndex - startIndex + array.copyInto(array, startIndex = index, endIndex = _length, destinationOffset = index + extraLength) + value.copyInto(array, startIndex = startIndex, endIndex = endIndex, destinationOffset = index) - _length += chars.size + _length += extraLength return this } - fun insert(index: Int, string: String): StringBuilder { - checkInsertIndex(index) - ensureExtraCapacity(string.length) - array.copyInto(array, startIndex = index, endIndex = _length, destinationOffset = index + string.length) - _length += insertString(array, index, string) - return this - } - - // TODO: optimize those! - fun insert(index: Int, value: Boolean) = insert(index, value.toString()) - fun insert(index: Int, value: Byte) = insert(index, value.toString()) - fun insert(index: Int, value: Short) = insert(index, value.toString()) - fun insert(index: Int, value: Int) = insert(index, value.toString()) - fun insert(index: Int, value: Long) = insert(index, value.toString()) - fun insert(index: Int, value: Float) = insert(index, value.toString()) - fun insert(index: Int, value: Double) = insert(index, value.toString()) - fun insert(index: Int, value: Any?) = insert(index, value.toString()) - - - // Of Appenable. - actual override fun append(c: Char) : StringBuilder { - ensureExtraCapacity(1) - array[_length++] = c - return this - } - - actual override fun append(csq: CharSequence?): StringBuilder { - // Kotlin/JVM processes null as if the argument was "null" char sequence. - val toAppend = csq ?: "null" - return append(toAppend, 0, toAppend.length) - } - - actual override fun append(csq: CharSequence?, start: Int, end: Int): StringBuilder { - // Kotlin/JVM processes null as if the argument was "null" char sequence. - val toAppend = csq ?: "null" - if (start < 0 || end < start || start > toAppend.length) throw IndexOutOfBoundsException() - ensureExtraCapacity(end - start) - (toAppend as? String)?.let { - _length += insertString(array, _length, it, start, end - start) - return this - } - var index = start - while (index < end) - array[_length++] = toAppend[index++] - return this - } - - fun append(it: CharArray): StringBuilder { - ensureExtraCapacity(it.size) - it.copyInto(array, _length) - _length += it.size - return this - } - - fun append(it: String): StringBuilder { - ensureExtraCapacity(it.length) - _length += insertString(array, _length, it) - return this - } - - // TODO: optimize those! - fun append(it: Boolean) = append(it.toString()) - fun append(it: Byte) = append(it.toString()) - fun append(it: Short) = append(it.toString()) - fun append(it: Int): StringBuilder { - ensureExtraCapacity(11) - _length += insertInt(array, _length, it) - return this - } - fun append(it: Long) = append(it.toString()) - fun append(it: Float) = append(it.toString()) - fun append(it: Double) = append(it.toString()) - actual fun append(obj: Any?): StringBuilder = append(obj.toString()) - - fun deleteCharAt(index: Int) { - checkIndex(index) - array.copyInto(array, startIndex = index + 1, endIndex = _length, destinationOffset = index) - --_length - } - - fun setCharAt(index: Int, value: Char) { - checkIndex(index) - array[index] = value - } - // ---------------------------- private ---------------------------- private fun ensureExtraCapacity(n: Int) { @@ -292,4 +628,283 @@ actual class StringBuilder private constructor ( private fun checkInsertIndexFrom(index: Int, fromIndex: Int) { if (index < fromIndex || index > _length) throw IndexOutOfBoundsException() } + + private fun checkReplaceRange(startIndex: Int, endIndex: Int, length: Int) { + if (startIndex < 0 || startIndex > length) { + throw IndexOutOfBoundsException("startIndex: $startIndex, length: $length") + } + if (startIndex > endIndex) { + throw IllegalArgumentException("startIndex($startIndex) > endIndex($endIndex)") + } + } } + +/** + * Clears the content of this string builder making it empty. + * + * @sample samples.text.Strings.clearStringBuilder + */ +@SinceKotlin("1.3") +public actual fun StringBuilder.clear(): StringBuilder = apply { setLength(0) } + +/** + * Sets the character at the specified [index] to the specified [value]. + * + * @throws IndexOutOfBoundsException if [index] is out of bounds of this string builder. + */ +@Suppress("EXTENSION_SHADOWED_BY_MEMBER") +@kotlin.internal.InlineOnly +public actual inline operator fun StringBuilder.set(index: Int, value: Char): Unit = this.set(index, value) + +/** + * Replaces characters in the specified range of this string builder with characters in the specified string [value] and returns this instance. + * + * @param startIndex the beginning (inclusive) of the range to replace. + * @param endIndex the end (exclusive) of the range to replace. + * @param value the string to replace with. + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] if [startIndex] is less than zero, greater than the length of this string builder, or `startIndex > endIndex`. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@Suppress("EXTENSION_SHADOWED_BY_MEMBER") +@kotlin.internal.InlineOnly +public actual inline fun StringBuilder.setRange(startIndex: Int, endIndex: Int, value: String): StringBuilder = + this.setRange(startIndex, endIndex, value) + +/** + * Removes the character at the specified [index] from this string builder and returns this instance. + * + * If the `Char` at the specified [index] is part of a supplementary code point, this method does not remove the entire supplementary character. + * + * @param index the index of `Char` to remove. + * + * @throws IndexOutOfBoundsException if [index] is out of bounds of this string builder. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@Suppress("EXTENSION_SHADOWED_BY_MEMBER") +@kotlin.internal.InlineOnly +public actual inline fun StringBuilder.deleteAt(index: Int): StringBuilder = this.deleteAt(index) + +/** + * Removes characters in the specified range from this string builder and returns this instance. + * + * @param startIndex the beginning (inclusive) of the range to remove. + * @param endIndex the end (exclusive) of the range to remove. + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] when [startIndex] or [endIndex] is out of range of this string builder indices or when `startIndex > endIndex`. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@Suppress("EXTENSION_SHADOWED_BY_MEMBER") +@kotlin.internal.InlineOnly +public actual inline fun StringBuilder.deleteRange(startIndex: Int, endIndex: Int): StringBuilder = this.deleteRange(startIndex, endIndex) + +/** + * Copies characters from this string builder into the [destination] character array. + * + * @param destination the array to copy to. + * @param destinationOffset the position in the array to copy to, 0 by default. + * @param startIndex the beginning (inclusive) of the range to copy, 0 by default. + * @param endIndex the end (exclusive) of the range to copy, length of this string builder by default. + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] when [startIndex] or [endIndex] is out of range of this string builder indices or when `startIndex > endIndex`. + * @throws IndexOutOfBoundsException when the subrange doesn't fit into the [destination] array starting at the specified [destinationOffset], + * or when that index is out of the [destination] array indices range. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@Suppress("EXTENSION_SHADOWED_BY_MEMBER", "ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") +@kotlin.internal.InlineOnly +public actual inline fun StringBuilder.toCharArray(destination: CharArray, destinationOffset: Int = 0, startIndex: Int = 0, endIndex: Int = this.length) = + this.toCharArray(destination, destinationOffset, startIndex, endIndex) + +/** + * Appends characters in a subarray of the specified character array [value] to this string builder and returns this instance. + * + * Characters are appended in order, starting at specified [startIndex]. + * + * @param value the array from which characters are appended. + * @param startIndex the beginning (inclusive) of the subarray to append. + * @param endIndex the end (exclusive) of the subarray to append. + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] when [startIndex] or [endIndex] is out of range of the [value] array indices or when `startIndex > endIndex`. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@Suppress("EXTENSION_SHADOWED_BY_MEMBER") +@kotlin.internal.InlineOnly +public actual inline fun StringBuilder.appendRange(value: CharArray, startIndex: Int, endIndex: Int): StringBuilder = + this.appendRange(value, startIndex, endIndex) + +/** + * Appends a subsequence of the specified character sequence [value] to this string builder and returns this instance. + * + * @param value the character sequence from which a subsequence is appended. If [value] is `null`, + * then characters are appended as if [value] contained the four characters `"null"`. + * @param startIndex the beginning (inclusive) of the subsequence to append. + * @param endIndex the end (exclusive) of the subsequence to append. + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] when [startIndex] or [endIndex] is out of range of the [value] character sequence indices or when `startIndex > endIndex`. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@Suppress("EXTENSION_SHADOWED_BY_MEMBER") +@kotlin.internal.InlineOnly +public actual inline fun StringBuilder.appendRange(value: CharSequence?, startIndex: Int, endIndex: Int): StringBuilder = + this.appendRange(value, startIndex, endIndex) + +/** + * Inserts characters in a subarray of the specified character array [value] into this string builder at the specified [index] and returns this instance. + * + * The inserted characters go in same order as in the [value] array, starting at [index]. + * + * @param index the position in this string builder to insert at. + * @param value the array from which characters are inserted. + * @param startIndex the beginning (inclusive) of the subarray to insert. + * @param endIndex the end (exclusive) of the subarray to insert. + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] when [startIndex] or [endIndex] is out of range of the [value] array indices or when `startIndex > endIndex`. + * @throws IndexOutOfBoundsException if [index] is less than zero or greater than the length of this string builder. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@Suppress("EXTENSION_SHADOWED_BY_MEMBER") +@kotlin.internal.InlineOnly +public actual inline fun StringBuilder.insertRange(index: Int, value: CharArray, startIndex: Int, endIndex: Int): StringBuilder = + this.insertRange(index, value, startIndex, endIndex) + +/** + * Inserts characters in a subsequence of the specified character sequence [value] into this string builder at the specified [index] and returns this instance. + * + * The inserted characters go in the same order as in the [value] character sequence, starting at [index]. + * + * @param index the position in this string builder to insert at. + * @param value the character sequence from which a subsequence is inserted. If [value] is `null`, + * then characters will be inserted as if [value] contained the four characters `"null"`. + * @param startIndex the beginning (inclusive) of the subsequence to insert. + * @param endIndex the end (exclusive) of the subsequence to insert. + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] when [startIndex] or [endIndex] is out of range of the [value] character sequence indices or when `startIndex > endIndex`. + * @throws IndexOutOfBoundsException if [index] is less than zero or greater than the length of this string builder. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@Suppress("EXTENSION_SHADOWED_BY_MEMBER") +@kotlin.internal.InlineOnly +public actual inline fun StringBuilder.insertRange(index: Int, value: CharSequence?, startIndex: Int, endIndex: Int): StringBuilder = + this.insertRange(index, value, startIndex, endIndex) + +// Method parameters renamings +@Suppress("EXTENSION_SHADOWED_BY_MEMBER") +@Deprecated("Use append(value: Boolean) instead", ReplaceWith("append(value = it)"), DeprecationLevel.WARNING) +@kotlin.internal.InlineOnly +public inline fun StringBuilder.append(it: Boolean): StringBuilder = this.append(value = it) + +@Suppress("EXTENSION_SHADOWED_BY_MEMBER") +@Deprecated("Use append(value: Byte) instead", ReplaceWith("append(value = it)"), DeprecationLevel.WARNING) +@kotlin.internal.InlineOnly +public inline fun StringBuilder.append(it: Byte): StringBuilder = this.append(value = it) + +@Suppress("EXTENSION_SHADOWED_BY_MEMBER") +@Deprecated("Use append(value: Short) instead", ReplaceWith("append(value = it)"), DeprecationLevel.WARNING) +@kotlin.internal.InlineOnly +public inline fun StringBuilder.append(it: Short): StringBuilder = this.append(value = it) + +@Suppress("EXTENSION_SHADOWED_BY_MEMBER") +@Deprecated("Use append(value: Int) instead", ReplaceWith("append(value = it)"), DeprecationLevel.WARNING) +@kotlin.internal.InlineOnly +public inline fun StringBuilder.append(it: Int): StringBuilder = this.append(value = it) + +@Suppress("EXTENSION_SHADOWED_BY_MEMBER") +@Deprecated("Use append(value: Long) instead", ReplaceWith("append(value = it)"), DeprecationLevel.WARNING) +@kotlin.internal.InlineOnly +public inline fun StringBuilder.append(it: Long): StringBuilder = this.append(value = it) + +@Suppress("EXTENSION_SHADOWED_BY_MEMBER") +@Deprecated("Use append(value: Float) instead", ReplaceWith("append(value = it)"), DeprecationLevel.WARNING) +@kotlin.internal.InlineOnly +public inline fun StringBuilder.append(it: Float): StringBuilder = this.append(value = it) + +@Suppress("EXTENSION_SHADOWED_BY_MEMBER") +@Deprecated("Use append(value: Double) instead", ReplaceWith("append(value = it)"), DeprecationLevel.WARNING) +@kotlin.internal.InlineOnly +public inline fun StringBuilder.append(it: Double): StringBuilder = this.append(value = it) + +@Suppress("EXTENSION_SHADOWED_BY_MEMBER") +@Deprecated("Use append(value: String) instead", ReplaceWith("append(value = it)"), DeprecationLevel.WARNING) +@kotlin.internal.InlineOnly +public inline fun StringBuilder.append(it: String): StringBuilder = this.append(value = it) + +@Suppress("EXTENSION_SHADOWED_BY_MEMBER") +@Deprecated("Use append(value: CharArray) instead", ReplaceWith("append(value = it)"), DeprecationLevel.WARNING) +@kotlin.internal.InlineOnly +public inline fun StringBuilder.append(it: CharArray): StringBuilder = this.append(value = it) + +@Suppress("EXTENSION_SHADOWED_BY_MEMBER") +@Deprecated("Use ensureCapacity(minimumCapacity: Int) instead", ReplaceWith("ensureCapacity(minimumCapacity = capacity)"), DeprecationLevel.WARNING) +@kotlin.internal.InlineOnly +public inline fun StringBuilder.ensureCapacity(capacity: Int): Unit = this.ensureCapacity(minimumCapacity = capacity) + +@Suppress("EXTENSION_SHADOWED_BY_MEMBER") +@Deprecated("Use insert(index: Int, value: Char) instead", ReplaceWith("insert(index, value = c)"), DeprecationLevel.WARNING) +@kotlin.internal.InlineOnly +public inline fun StringBuilder.insert(index: Int, c: Char): StringBuilder = this.insert(index, value = c) + +@Suppress("EXTENSION_SHADOWED_BY_MEMBER") +@Deprecated("Use insert(index: Int, value: CharArray) instead", ReplaceWith("insert(index, value = chars)"), DeprecationLevel.WARNING) +@kotlin.internal.InlineOnly +public inline fun StringBuilder.insert(index: Int, chars: CharArray): StringBuilder = this.insert(index, value = chars) + +@Suppress("EXTENSION_SHADOWED_BY_MEMBER") +@Deprecated("Use insert(index: Int, value: CharSequence?) instead", ReplaceWith("insert(index, value = csq)"), DeprecationLevel.WARNING) +@kotlin.internal.InlineOnly +public inline fun StringBuilder.insert(index: Int, csq: CharSequence?): StringBuilder = this.insert(index, value = csq) + +@Suppress("EXTENSION_SHADOWED_BY_MEMBER") +@Deprecated("Use insert(index: Int, value: String) instead", ReplaceWith("insert(index, value = string)"), DeprecationLevel.WARNING) +@kotlin.internal.InlineOnly +public inline fun StringBuilder.insert(index: Int, string: String): StringBuilder = this.insert(index, value = string) + +@Suppress("EXTENSION_SHADOWED_BY_MEMBER") +@Deprecated("Use setLength(newLength: Int) instead", ReplaceWith("setLength(newLength = l)"), DeprecationLevel.WARNING) +@kotlin.internal.InlineOnly +public inline fun StringBuilder.setLength(l: Int) = this.setLength(newLength = l) + +// Method renamings +/** + * Inserts characters in a subsequence of the specified character sequence [csq] into this string builder at the specified [index] and returns this instance. + * + * The inserted characters go in the same order as in the [csq] character sequence, starting at [index]. + * + * @param index the position in this string builder to insert at. + * @param csq the character sequence from which a subsequence is inserted. If [csq] is `null`, + * then characters will be inserted as if [csq] contained the four characters `"null"`. + * @param start the beginning (inclusive) of the subsequence to insert. + * @param end the end (exclusive) of the subsequence to insert. + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] when [start] or [end] is out of range of the [csq] character sequence indices or when `start > end`. + * @throws IndexOutOfBoundsException if [index] is less than zero or greater than the length of this string builder. + */ +@UseExperimental(ExperimentalStdlibApi::class) +@kotlin.internal.InlineOnly +public inline fun StringBuilder.insert(index: Int, csq: CharSequence?, start: Int, end: Int): StringBuilder = + this.insertRange(index, csq, start, end) + +@Deprecated("Use set(index: Int, value: Char) instead", ReplaceWith("set(index, value)"), DeprecationLevel.WARNING) +@kotlin.internal.InlineOnly +public inline fun StringBuilder.setCharAt(index: Int, value: Char) = this.set(index, value) + +/** + * Removes the character at the specified [index] from this string builder and returns this instance. + * + * If the `Char` at the specified [index] is part of a supplementary code point, this method does not remove the entire supplementary character. + * + * @param index the index of `Char` to remove. + * + * @throws IndexOutOfBoundsException if [index] is out of bounds of this string builder. + */ +@UseExperimental(ExperimentalStdlibApi::class) +@kotlin.internal.InlineOnly +public inline fun StringBuilder.deleteCharAt(index: Int) = this.deleteAt(index) diff --git a/runtime/src/main/kotlin/kotlin/text/regex/CharClass.kt b/runtime/src/main/kotlin/kotlin/text/regex/CharClass.kt index 11328d457e0..fc4186e4adf 100644 --- a/runtime/src/main/kotlin/kotlin/text/regex/CharClass.kt +++ b/runtime/src/main/kotlin/kotlin/text/regex/CharClass.kt @@ -485,6 +485,7 @@ internal class CharClass(val ignoreCase: Boolean = false, negative: Boolean = fa } } + @UseExperimental(ExperimentalStdlibApi::class) override val instance: AbstractCharClass get() { @@ -506,7 +507,7 @@ internal class CharClass(val ignoreCase: Boolean = false, negative: Boolean = fa } if (temp.length > 0) - temp.deleteCharAt(temp.length - 1) + temp.deleteAt(temp.length - 1) return temp.toString() } @@ -518,6 +519,7 @@ internal class CharClass(val ignoreCase: Boolean = false, negative: Boolean = fa } } + @UseExperimental(ExperimentalStdlibApi::class) //for debugging purposes only override fun toString(): String { val temp = StringBuilder() @@ -529,7 +531,7 @@ internal class CharClass(val ignoreCase: Boolean = false, negative: Boolean = fa } if (temp.length > 0) - temp.deleteCharAt(temp.length - 1) + temp.deleteAt(temp.length - 1) return temp.toString() }