diff --git a/backend.native/tests/build.gradle b/backend.native/tests/build.gradle index 28a4aef367b..ecec21959fd 100644 --- a/backend.native/tests/build.gradle +++ b/backend.native/tests/build.gradle @@ -314,6 +314,11 @@ task tostring1(type: RunKonanTest) { source = "runtime/basic/tostring1.kt" } +task tostring2(type: RunKonanTest) { + goldValue = "H e l l o \nHello\n" + source = "runtime/basic/tostring2.kt" +} + task array0(type: RunKonanTest) { goldValue = "5\n6\n7\n8\n9\n10\n11\n12\n13\n" source = "runtime/basic/array0.kt" @@ -444,6 +449,11 @@ task moderately_large_array(type: RunKonanTest) { source = "runtime/collections/moderately_large_array.kt" } +task string_builder0(type: RunKonanTest) { + goldValue = "OK\n" + source = "runtime/text/string_builder0.kt" +} + task catch1(type: RunKonanTest) { goldValue = "Before\nCaught Throwable\nDone\n" source = "runtime/exceptions/catch1.kt" diff --git a/backend.native/tests/runtime/basic/tostring2.kt b/backend.native/tests/runtime/basic/tostring2.kt new file mode 100644 index 00000000000..aa17c92b735 --- /dev/null +++ b/backend.native/tests/runtime/basic/tostring2.kt @@ -0,0 +1,10 @@ +fun main(args : Array) { + val hello = "Hello" + val array = toCharArray(hello) + for (ch in array) { + print(ch) + print(" ") + } + println() + println(fromCharArray(array, 0, array.size)) +} \ No newline at end of file diff --git a/backend.native/tests/runtime/collections/array_list1.kt b/backend.native/tests/runtime/collections/array_list1.kt index 96037257306..ffc53ddb329 100644 --- a/backend.native/tests/runtime/collections/array_list1.kt +++ b/backend.native/tests/runtime/collections/array_list1.kt @@ -182,6 +182,12 @@ fun testToString() { assertTrue(a.toString() == makeList123().toString()) } +fun testToString2() { + val a = makeList123() + assertEquals(a.toString(), "[1, 2, 3]") +} + + fun testSubList() { val a0 = makeList01234() val a = a0.subList(1, 4) @@ -271,11 +277,12 @@ fun testIteratorAdd() { val next = it.next() if (i++ % 2 == 0) it.add("-" + next) - it.next() } //assertEquals(listOf("1", "2", "-2", "3", "4", "-4", "5"), a) } + + fun main(args : Array) { testBasic() testIterator() @@ -286,6 +293,7 @@ fun main(args : Array) { testEquals() testHashCode() testToString() + testToString2() testSubList() testResize() testSubListContains() diff --git a/backend.native/tests/runtime/text/string_builder0.kt b/backend.native/tests/runtime/text/string_builder0.kt new file mode 100644 index 00000000000..5af19eee715 --- /dev/null +++ b/backend.native/tests/runtime/text/string_builder0.kt @@ -0,0 +1,46 @@ +fun assertTrue(cond: Boolean) { + if (!cond) + println("FAIL") +} + +fun assertFalse(cond: Boolean) { + if (cond) + println("FAIL") +} + +fun assertEquals(value1: String, value2: String) { + if (value1 != value2) + println("FAIL: '" + value1 + "' != '" + value2 + "'") +} + +fun assertEquals(value1: Int, value2: Int) { + if (value1 != value2) + println("FAIL" + value1.toString() + " != " + value2.toString()) +} + +fun testBasic() { + val sb = StringBuilder() + assertEquals(0, sb.length) + assertEquals("", sb.toString()) + sb.append(1) + assertEquals(1, sb.length) + assertEquals("1", sb.toString()) + sb.append(", ") + assertEquals(3, sb.length) + assertEquals("1, ", sb.toString()) + sb.append(true) + assertEquals(7, sb.length) + assertEquals("1, true", sb.toString()) + sb.append(12345678L) + assertEquals(15, sb.length) + assertEquals("1, true12345678", sb.toString()) + + sb.length = 0 + assertEquals(0, sb.length) + assertEquals("", sb.toString()) +} + +fun main(args : Array) { + testBasic() + println("OK") +} \ No newline at end of file diff --git a/runtime/src/main/cpp/Arrays.cpp b/runtime/src/main/cpp/Arrays.cpp index 383d0443d3c..a661c8ae9aa 100644 --- a/runtime/src/main/cpp/Arrays.cpp +++ b/runtime/src/main/cpp/Arrays.cpp @@ -115,6 +115,17 @@ ArrayHeader* Kotlin_CharArray_clone(const ArrayHeader* array) { return result; } +ArrayHeader* Kotlin_CharArray_copyOf(const ArrayHeader* array, KInt newSize) { + ArrayHeader* result = ArrayContainer( + theCharArrayTypeInfo, newSize).GetPlace(); + KInt toCopy = array->count_ < newSize ? array->count_ : newSize; + memcpy( + PrimitiveArrayAddressOfElementAt(result, 0), + PrimitiveArrayAddressOfElementAt(array, 0), + toCopy * sizeof(KChar)); + return result; +} + KInt Kotlin_CharArray_getArrayLength(const ArrayHeader* array) { return array->count_; } diff --git a/runtime/src/main/cpp/Natives.cpp b/runtime/src/main/cpp/Natives.cpp index 5962aeaea0c..8775183d8be 100644 --- a/runtime/src/main/cpp/Natives.cpp +++ b/runtime/src/main/cpp/Natives.cpp @@ -79,18 +79,48 @@ KInt Kotlin_String_getStringLength(KString thiz) { return thiz->count_; } -KString Kotlin_String_fromUtf8Array(const ArrayHeader* array) { +KString Kotlin_String_fromUtf8Array( + const ArrayHeader* array, KInt start, KInt size) { RuntimeAssert(array->type_info() == theByteArrayTypeInfo, "Must use a byte array"); + if (start < 0 || start + size > array->count_) { + ThrowArrayIndexOutOfBoundsException(); + } // TODO: support full UTF-8. - ArrayHeader* result = ArrayContainer( - theStringTypeInfo, array->count_).GetPlace(); + ArrayHeader* result = ArrayContainer(theStringTypeInfo, size).GetPlace(); memcpy( ByteArrayAddressOfElementAt(result, 0), - ByteArrayAddressOfElementAt(array, 0), - ArrayDataSizeBytes(array)); + ByteArrayAddressOfElementAt(array, start), + size); return result; } +KString Kotlin_String_fromCharArray( + const ArrayHeader* array, KInt start, KInt size) { + RuntimeAssert(array->type_info() == theCharArrayTypeInfo, "Must use a byte array"); + if (start < 0 || start + size > array->count_) { + ThrowArrayIndexOutOfBoundsException(); + } + // TODO: support full UTF-8. + ArrayHeader* result = ArrayContainer(theStringTypeInfo, size).GetPlace(); + for (KInt index = 0; index < size; ++index) { + *ByteArrayAddressOfElementAt(result, index) = + *PrimitiveArrayAddressOfElementAt(array, start + index); + } + return result; +} + +ArrayHeader* Kotlin_String_toCharArray(KString string) { + // TODO: support full UTF-8. + ArrayHeader* result = ArrayContainer( + theCharArrayTypeInfo, string->count_).GetPlace(); + for (int index = 0; index < string->count_; ++index) { + *PrimitiveArrayAddressOfElementAt(result, index) = + *ByteArrayAddressOfElementAt(string, index); + } + return result; +} + + KString Kotlin_String_plusImpl(KString thiz, KString other) { // TODO: support UTF-8 RuntimeAssert(thiz != nullptr, "this cannot be null"); @@ -113,7 +143,7 @@ KString Kotlin_String_plusImpl(KString thiz, KString other) { KBoolean Kotlin_String_equals(KString thiz, KConstRef other) { if (other == nullptr || other->type_info() != theStringTypeInfo) return 0; - const ArrayHeader* otherString = reinterpret_cast(other); + KString otherString = reinterpret_cast(other); return thiz->count_ == otherString->count_ && memcmp(ByteArrayAddressOfElementAt(thiz, 0), ByteArrayAddressOfElementAt(otherString, 0), diff --git a/runtime/src/main/cpp/Natives.h b/runtime/src/main/cpp/Natives.h index 5f678554b9a..c19d7e055fa 100644 --- a/runtime/src/main/cpp/Natives.h +++ b/runtime/src/main/cpp/Natives.h @@ -80,7 +80,8 @@ KInt Kotlin_String_hashCode(KString thiz); KBoolean Kotlin_String_equals(KString thiz, KConstRef other); KInt Kotlin_String_compareTo(KString thiz, KString other); KChar Kotlin_String_get(KString thiz, KInt index); -KString Kotlin_String_fromUtf8Array(const ArrayHeader* array); +KString Kotlin_String_fromUtf8Array(const ArrayHeader* array, KInt start, KInt size); +KString Kotlin_String_fromCharArray(const ArrayHeader* array, KInt start, KInt size); KString Kotlin_String_plusImpl(KString thiz, KString other); KInt Kotlin_String_getStringLength(KString thiz); KString Kotlin_String_subSequence(KString thiz, KInt startIndex, KInt endIndex); diff --git a/runtime/src/main/kotlin/kotlin/Arrays.kt b/runtime/src/main/kotlin/kotlin/Arrays.kt index 8ce4170bd00..9b38edbffc9 100644 --- a/runtime/src/main/kotlin/kotlin/Arrays.kt +++ b/runtime/src/main/kotlin/kotlin/Arrays.kt @@ -66,6 +66,9 @@ public final class CharArray : Cloneable { @SymbolName("Kotlin_CharArray_clone") external public override fun clone(): Any + @SymbolName("Kotlin_CharArray_copyOf") + external public fun copyOf(newSize: Int): CharArray + @SymbolName("Kotlin_CharArray_getArrayLength") external private fun getArrayLength(): Int diff --git a/runtime/src/main/kotlin/kotlin/String.kt b/runtime/src/main/kotlin/kotlin/String.kt index 4609ed0c770..021c2874f12 100644 --- a/runtime/src/main/kotlin/kotlin/String.kt +++ b/runtime/src/main/kotlin/kotlin/String.kt @@ -1,7 +1,5 @@ package kotlin -@SymbolName("Kotlin_String_fromUtf8Array") -external fun fromUtf8Array(array: ByteArray) : String // TODO: enable, once global variables implemented. // @SymbolName("theEmptyString") diff --git a/runtime/src/main/kotlin/kotlin/collections/ArrayList.kt b/runtime/src/main/kotlin/kotlin/collections/ArrayList.kt index f829871cab7..af554fd91bd 100644 --- a/runtime/src/main/kotlin/kotlin/collections/ArrayList.kt +++ b/runtime/src/main/kotlin/kotlin/collections/ArrayList.kt @@ -167,15 +167,16 @@ class ArrayList private constructor( } override fun toString(): String { - var result = "[" + val sb = StringBuilder(2 + length * 3) + sb.append("[") var i = 0 while (i < length) { - if (i > 0) result += ", " - result += array[offset + i].toString() + if (i > 0) sb.append(", ") + sb.append(array[offset + i]) i++ } - result += "]" - return result + sb.append("]") + return sb.toString() } // ---------------------------- private ---------------------------- diff --git a/runtime/src/main/kotlin/kotlin/text/StringBuilder.kt b/runtime/src/main/kotlin/kotlin/text/StringBuilder.kt new file mode 100644 index 00000000000..fbc2dcb04ed --- /dev/null +++ b/runtime/src/main/kotlin/kotlin/text/StringBuilder.kt @@ -0,0 +1,103 @@ +package kotlin.text + +@SymbolName("Kotlin_String_fromUtf8Array") +external fun fromUtf8Array(array: ByteArray, start: Int, size: Int) : String + +// TODO: make it somewhat private? +@SymbolName("Kotlin_String_fromCharArray") +external fun fromCharArray(array: CharArray, start: Int, size: Int) : String + +@SymbolName("Kotlin_String_toCharArray") +external fun toCharArray(string: String) : CharArray + + +class StringBuilder private constructor ( + private var array: CharArray +) : CharSequence { + constructor() : this(10) + + constructor(capacity: Int) : this(CharArray(capacity)) + + constructor(string: String) : this(toCharArray(string)) { + length = array.size + } + + override var length: Int = 0 + set(capacity) { + ensureCapacity(capacity) + field = capacity + } + + override fun get(index: Int): Char { + checkIndex(index) + return array[index] + } + + override fun subSequence(startIndex: Int, endIndex: Int): CharSequence = substring(startIndex, endIndex) + + override fun toString(): String = fromCharArray(array, 0, length) + + fun substring(startIndex: Int, endIndex: Int): String { + checkInsertIndex(startIndex) + checkInsertIndexFrom(endIndex, startIndex) + return fromCharArray(array, startIndex, endIndex - startIndex) + } + + fun trimToSize() { + if (length < array.size) + array = array.copyOf(length) + } + + fun ensureCapacity(capacity: Int) { + if (capacity > array.size) { + var newSize = array.size * 3 / 2 + if (capacity > newSize) + newSize = capacity + array = array.copyOf(newSize) + } + } + + fun append(it: Char) { + ensureExtraCapacity(1) + array[length++] = it + } + + fun append(it: CharArray) { + ensureExtraCapacity(it.size) + for (c in it) + array[length++] = c + } + + fun append(it: String) { + ensureExtraCapacity(it.length) + for (c in toCharArray(it)) + array[length++] = c + } + + 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) = append(it.toString()) + fun append(it: Long) = append(it.toString()) + fun append(it: Float) = append(it.toString()) + fun append(it: Double) = append(it.toString()) + fun append(it: Any?) = append(it.toString()) + + // ---------------------------- private ---------------------------- + + private fun ensureExtraCapacity(n: Int) { + ensureCapacity(length + n) + } + + private fun checkIndex(index: Int) { + if (index < 0 || index >= length) throw IndexOutOfBoundsException() + } + + private fun checkInsertIndex(index: Int) { + if (index < 0 || index > length) throw IndexOutOfBoundsException() + } + + private fun checkInsertIndexFrom(index: Int, fromIndex: Int) { + if (index < fromIndex || index > length) throw IndexOutOfBoundsException() + } +} \ No newline at end of file