diff --git a/backend.native/tests/build.gradle b/backend.native/tests/build.gradle index ae249a833f5..aed54b05377 100644 --- a/backend.native/tests/build.gradle +++ b/backend.native/tests/build.gradle @@ -1085,6 +1085,11 @@ task string_builder_reverse(type: RunKonanTest) { source = "runtime/text/string_builder_reverse.kt" } +task string_builder_insert(type: RunKonanTest) { + goldValue = "OK\n" + source = "runtime/text/string_builder_insert.kt" +} + task string0(type: RunKonanTest) { goldValue = "true\ntrue\nПРИВЕТ\nпривет\nПока\ntrue\n" source = "runtime/text/string0.kt" diff --git a/backend.native/tests/runtime/text/string_builder_insert.kt b/backend.native/tests/runtime/text/string_builder_insert.kt new file mode 100644 index 00000000000..d9d1a6e8519 --- /dev/null +++ b/backend.native/tests/runtime/text/string_builder_insert.kt @@ -0,0 +1,136 @@ +fun assertEquals(builder: StringBuilder, content: String) { + if (builder.toString() != content) throw AssertionError("Test failed: " + builder.toString() + " != " + content) +} + +fun assertTrue(condition: Boolean) { + if (!condition) throw AssertionError("The condition is false") +} + +// IndexOutOfBoundsException +fun assertException(body: () -> Unit) { + try { + body() + throw AssertionError ("Test failed: no IndexOutOfBoundsException on wrong indices") + } catch (e: IndexOutOfBoundsException) {} +} + +fun String.toCharArray(): CharArray = CharArray(length) { i -> this[i] } + +fun testInsertString(initial: String, index: Int, toInsert: String, expected: String) { + assertEquals(StringBuilder(initial).insert(index, toInsert), expected) + assertEquals(StringBuilder(initial).insert(index, toInsert.toCharArray()), expected) + assertEquals(StringBuilder(initial).insert(index, toInsert as CharSequence), expected) +} + +fun testInsertStringException(initial: String, index: Int, toInsert: String) { + assertException { StringBuilder(initial).insert(index, toInsert) } + assertException { StringBuilder(initial).insert(index, toInsert.toCharArray()) } + assertException { StringBuilder(initial).insert(index, toInsert as CharSequence) } +} + +fun testInsertSingle(value: Byte) { + assertEquals(StringBuilder("abcd").insert(0, value), value.toString() + "abcd") + assertEquals(StringBuilder("abcd").insert(4, value), "abcd" + value.toString()) + assertEquals(StringBuilder("abcd").insert(2, value), "ab" + value.toString() + "cd") + assertEquals(StringBuilder("").insert(0, value), value.toString()) +} + +fun testInsertSingle(value: Short) { + assertEquals(StringBuilder("abcd").insert(0, value), value.toString() + "abcd") + assertEquals(StringBuilder("abcd").insert(4, value), "abcd" + value.toString()) + assertEquals(StringBuilder("abcd").insert(2, value), "ab" + value.toString() + "cd") + assertEquals(StringBuilder("").insert(0, value), value.toString()) +} + +fun testInsertSingle(value: Int) { + assertEquals(StringBuilder("abcd").insert(0, value), value.toString() + "abcd") + assertEquals(StringBuilder("abcd").insert(4, value), "abcd" + value.toString()) + assertEquals(StringBuilder("abcd").insert(2, value), "ab" + value.toString() + "cd") + assertEquals(StringBuilder("").insert(0, value), value.toString()) +} + +fun testInsertSingle(value: Long) { + assertEquals(StringBuilder("abcd").insert(0, value), value.toString() + "abcd") + assertEquals(StringBuilder("abcd").insert(4, value), "abcd" + value.toString()) + assertEquals(StringBuilder("abcd").insert(2, value), "ab" + value.toString() + "cd") + assertEquals(StringBuilder("").insert(0, value), value.toString()) +} + +fun testInsertSingle(value: Float) { + assertEquals(StringBuilder("abcd").insert(0, value), value.toString() + "abcd") + assertEquals(StringBuilder("abcd").insert(4, value), "abcd" + value.toString()) + assertEquals(StringBuilder("abcd").insert(2, value), "ab" + value.toString() + "cd") + assertEquals(StringBuilder("").insert(0, value), value.toString()) +} + +fun testInsertSingle(value: Double) { + assertEquals(StringBuilder("abcd").insert(0, value), value.toString() + "abcd") + assertEquals(StringBuilder("abcd").insert(4, value), "abcd" + value.toString()) + assertEquals(StringBuilder("abcd").insert(2, value), "ab" + value.toString() + "cd") + assertEquals(StringBuilder("").insert(0, value), value.toString()) +} + +fun testInsertSingle(value: Any?) { + assertEquals(StringBuilder("abcd").insert(0, value), value.toString() + "abcd") + assertEquals(StringBuilder("abcd").insert(4, value), "abcd" + value.toString()) + assertEquals(StringBuilder("abcd").insert(2, value), "ab" + value.toString() + "cd") + assertEquals(StringBuilder("").insert(0, value), value.toString()) +} + +fun testInsertSingle(value: Char) { + assertEquals(StringBuilder("abcd").insert(0, value), value.toString() + "abcd") + assertEquals(StringBuilder("abcd").insert(4, value), "abcd" + value.toString()) + assertEquals(StringBuilder("abcd").insert(2, value), "ab" + value.toString() + "cd") + assertEquals(StringBuilder("").insert(0, value), value.toString()) +} + +fun main(args: Array) { + // String/CharSequence/CharArray + testInsertString("abcd", 0, "12", "12abcd") + testInsertString("abcd", 4, "12", "abcd12") + testInsertString("abcd", 2, "12", "ab12cd") + testInsertString("", 0, "12", "12") + testInsertStringException("a", -1, "1") + testInsertStringException("a", 2, "1") + + // Subsequence of CharSequence + // Insert in the beginning + assertEquals(StringBuilder("abcd").insert(0, "1234", 0, 0), "abcd") // 0 symbols + assertEquals(StringBuilder("abcd").insert(0, "1234", 0, 1), "1abcd") // 1 symbol + assertEquals(StringBuilder("abcd").insert(0, "1234", 1, 3), "23abcd") // 2 symbols + + // Insert in the end + assertEquals(StringBuilder("abcd").insert(4, "1234", 0, 0), "abcd") + assertEquals(StringBuilder("abcd").insert(4, "1234", 0, 1), "abcd1") + assertEquals(StringBuilder("abcd").insert(4, "1234", 1, 3), "abcd23") + + // Insert in the middle + assertEquals(StringBuilder("abcd").insert(2, "1234", 0, 0), "abcd") + assertEquals(StringBuilder("abcd").insert(2, "1234", 0, 1), "ab1cd") + assertEquals(StringBuilder("abcd").insert(2, "1234", 1, 3), "ab23cd") + + // Incorrect indices + assertException { StringBuilder("a").insert(-1, "1", 0, 0) } + assertException { StringBuilder("a").insert(2, "1", 0, 0) } + assertException { StringBuilder("a").insert(1, "1", -1, 0) } + assertException { StringBuilder("a").insert(1, "1", 0, 2) } + assertException { StringBuilder("a").insert(1, "123", 2, 0) } + + + // Other types + testInsertSingle(true) + testInsertSingle(42.toByte()) + testInsertSingle(42.toShort()) + testInsertSingle(42.toInt()) + testInsertSingle(42.toLong()) + testInsertSingle(42.2.toFloat()) + testInsertSingle(42.2.toDouble()) + testInsertSingle(object { + override fun toString(): String { + return "Object" + } + }) + testInsertSingle('a') + + println("OK") +} \ No newline at end of file diff --git a/runtime/src/main/kotlin/kotlin/text/StringBuilder.kt b/runtime/src/main/kotlin/kotlin/text/StringBuilder.kt index 467cf68e9bd..5b1124d5937 100644 --- a/runtime/src/main/kotlin/kotlin/text/StringBuilder.kt +++ b/runtime/src/main/kotlin/kotlin/text/StringBuilder.kt @@ -29,6 +29,29 @@ external fun fromCharArray(array: CharArray, start: Int, size: Int) : String @SymbolName("Kotlin_String_toCharArray") external fun toCharArray(string: String) : CharArray +/** + * Builds new string by populating newly created [StringBuilder] using provided [builderAction] + * and then converting it to [String]. + */ +@kotlin.internal.InlineOnly +public inline fun buildString(builderAction: StringBuilder.() -> Unit): String = + StringBuilder().apply(builderAction).toString() + +/** + * Builds new string by populating newly created [StringBuilder] initialized with the given [capacity] + * using provided [builderAction] and then converting it to [String]. + */ +@SinceKotlin("1.1") +@kotlin.internal.InlineOnly +public inline fun buildString(capacity: Int, builderAction: StringBuilder.() -> Unit): String = + StringBuilder(capacity).apply(builderAction).toString() + +/** + * 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) + class StringBuilder private constructor ( private var array: CharArray ) : CharSequence, Appendable { @@ -110,6 +133,79 @@ class StringBuilder private constructor ( return this } + fun insert(index: Int, c: 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 + length++ + return this + } + + fun insert(index: Int, csq: CharSequence?): StringBuilder { + // TODO: how to treat nulls properly? + if (csq == null) return this + return insert(index, csq, 0, csq.length) + } + + fun insert(index: Int, csq: CharSequence?, start: Int, end: Int): StringBuilder { + // TODO: how to treat nulls properly? + if (csq == null) return this + if (start < 0 || end < start || start > csq.length) throw IndexOutOfBoundsException() + checkInsertIndex(index) + + val extraLength = end - start + ensureExtraCapacity(extraLength) + var from = lastIndex + var to = length + extraLength - 1 + while (from >= index) { + array[to--] = array[from--] + } + + from = start + to = index + while (from < end) { + array[to++] = csq[from++] + } + + length += extraLength + return this + } + + fun insert(index: Int, chars: CharArray): StringBuilder { + checkInsertIndex(index) + ensureExtraCapacity(chars.size) + + var from = lastIndex + var to = length + chars.size - 1 + while (from >= index) { + array[to--] = array[from--] + } + + var i = index + for (c in chars) { + array[i++] = c + } + + length += chars.size + return this + } + + fun insert(index: Int, string: String): StringBuilder = insert(index, toCharArray(string)) + + 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. override fun append(c: Char) : StringBuilder { ensureExtraCapacity(1) @@ -126,6 +222,7 @@ class StringBuilder private constructor ( override fun append(csq: CharSequence?, start: Int, end: Int): StringBuilder { // TODO: how to treat nulls properly? if (csq == null) return this + if (start < 0 || end < start || start > csq.length) throw IndexOutOfBoundsException() ensureExtraCapacity(end - start) var index = start while (index < end) @@ -162,6 +259,11 @@ class StringBuilder private constructor ( --length } + fun setCharAt(index: Int, value: Char) { + checkIndex(index) + array[index] = value + } + // ---------------------------- private ---------------------------- private fun ensureExtraCapacity(n: Int) { @@ -179,4 +281,4 @@ class StringBuilder private constructor ( private fun checkInsertIndexFrom(index: Int, fromIndex: Int) { if (index < fromIndex || index > length) throw IndexOutOfBoundsException() } -} \ No newline at end of file +}