New zero-terminated utf8 to String conversion API

This commit is contained in:
Abduqodiri Qurbonzoda
2019-07-12 20:38:36 +03:00
committed by GitHub
parent eb1221f465
commit c348873773
12 changed files with 392 additions and 215 deletions
@@ -554,6 +554,60 @@ public fun CPointer<IntVar>.toKStringFromUtf32(): String {
return String(chars) return String(chars)
} }
/**
* Decodes a string from the bytes in UTF-8 encoding in this array.
* Bytes following the first occurrence of `0` byte, if it occurs, are not decoded.
*
* Malformed byte sequences are replaced by the replacement char `\uFFFD`.
*/
@UseExperimental(ExperimentalStdlibApi::class)
@SinceKotlin("1.3")
public fun ByteArray.toKString() : String {
val realEndIndex = realEndIndex(this, 0, this.size)
return decodeToString(0, realEndIndex)
}
/**
* Decodes a string from the bytes in UTF-8 encoding in this array or its subrange.
* Bytes following the first occurrence of `0` byte, if it occurs, are not decoded.
*
* @param startIndex the beginning (inclusive) of the subrange to decode, 0 by default.
* @param endIndex the end (exclusive) of the subrange to decode, size of this array by default.
* @param throwOnInvalidSequence specifies whether to throw an exception on malformed byte sequence or replace it by the replacement char `\uFFFD`.
*
* @throws IndexOutOfBoundsException if [startIndex] is less than zero or [endIndex] is greater than the size of this array.
* @throws IllegalArgumentException if [startIndex] is greater than [endIndex].
* @throws CharacterCodingException if the byte array contains malformed UTF-8 byte sequence and [throwOnInvalidSequence] is true.
*/
@UseExperimental(ExperimentalStdlibApi::class)
@SinceKotlin("1.3")
public fun ByteArray.toKString(
startIndex: Int = 0,
endIndex: Int = this.size,
throwOnInvalidSequence: Boolean = false
) : String {
checkBoundsIndexes(startIndex, endIndex, this.size)
val realEndIndex = realEndIndex(this, startIndex, endIndex)
return decodeToString(startIndex, realEndIndex, throwOnInvalidSequence)
}
private fun realEndIndex(byteArray: ByteArray, startIndex: Int, endIndex: Int): Int {
var index = startIndex
while (index < endIndex && byteArray[index] != 0.toByte()) {
index++
}
return index
}
private fun checkBoundsIndexes(startIndex: Int, endIndex: Int, size: Int) {
if (startIndex < 0 || endIndex > size) {
throw IndexOutOfBoundsException("startIndex: $startIndex, endIndex: $endIndex, size: $size")
}
if (startIndex > endIndex) {
throw IllegalArgumentException("startIndex: $startIndex > endIndex: $endIndex")
}
}
public class MemScope : ArenaBase() { public class MemScope : ArenaBase() {
val memScope: MemScope val memScope: MemScope
@@ -21,8 +21,7 @@ import kotlin.native.internal.TypedIntrinsic
import kotlin.native.internal.IntrinsicType import kotlin.native.internal.IntrinsicType
internal fun decodeFromUtf8(bytes: ByteArray): String = bytes.decodeToString() internal fun decodeFromUtf8(bytes: ByteArray): String = bytes.decodeToString()
internal fun encodeToUtf8(str: String): ByteArray = str.encodeToByteArray()
fun encodeToUtf8(str: String): ByteArray = str.encodeToByteArray()
@TypedIntrinsic(IntrinsicType.INTEROP_BITS_TO_FLOAT) @TypedIntrinsic(IntrinsicType.INTEROP_BITS_TO_FLOAT)
external fun bitsToFloat(bits: Int): Float external fun bitsToFloat(bits: Int): Float
+1 -1
View File
@@ -2159,7 +2159,7 @@ task indexof(type: KonanLocalTest) {
task utf8(type: KonanLocalTest) { task utf8(type: KonanLocalTest) {
expectedFail = (project.testTarget == 'wasm32') // Uses exceptions. expectedFail = (project.testTarget == 'wasm32') // Uses exceptions.
goldValue = "Hello\nПривет\n\uD800\uDC00\n\n\uFFFD\uFFFD\n\uFFFD12\n\uFFFD12\n12\uFFFD\n\uD83D\uDE25\n" goldValue = "Hello\nПривет\n\uD800\uDC00\n\n\uFFFD\uFFFD\n\uFFFD12\n\uFFFD12\n12\uFFFD\n\uD83D\uDE25\n\uD83D\uDE25\n\uD83D\uDE25\n"
source = "runtime/text/utf8.kt" source = "runtime/text/utf8.kt"
} }
@@ -21,7 +21,7 @@ fun main(args: Array<String>) = memScoped {
}.ptr }.ptr
if (inflateInit2(z, -15) == Z_OK && inflate(z, Z_FINISH) == Z_STREAM_END && inflateEnd(z) == Z_OK) if (inflateInit2(z, -15) == Z_OK && inflate(z, Z_FINISH) == Z_STREAM_END && inflateEnd(z) == Z_OK)
println(buffer.stringFromUtf8()) println(buffer.toKString())
} }
println(golden.toKString()) println(golden.toKString())
} }
+268 -147
View File
@@ -7,11 +7,13 @@ package runtime.text.utf8
import kotlin.test.* import kotlin.test.*
import kotlin.reflect.KClass import kotlin.reflect.KClass
import kotlinx.cinterop.toKString
// region Util // -------------------------------------- Utils --------------------------------------
fun assertEquals(expected: ByteArray, actual: ByteArray, message: String) = fun assertEquals(expected: ByteArray, actual: ByteArray, message: String) =
assertTrue(expected.contentEquals(actual), message) assertTrue(expected.contentEquals(actual), message)
fun checkUtf16to8(string: String, expected: IntArray, conversion: String.() -> ByteArray) { fun checkUtf16to8(string: String, expected: IntArray, conversion: String.() -> ByteArray) {
expected.forEach { expected.forEach {
assertTrue(it in Byte.MIN_VALUE..Byte.MAX_VALUE, "Expected array contains illegal values") assertTrue(it in Byte.MIN_VALUE..Byte.MAX_VALUE, "Expected array contains illegal values")
@@ -25,15 +27,35 @@ fun checkUtf16to8(string: String, expected: IntArray, conversion: String.() -> B
""".trimIndent()) """.trimIndent())
} }
fun checkUtf16to8Replacing(string: String, expected: IntArray) = checkUtf16to8(string, expected) { toUtf8() } // Utils for checking successful UTF-16 to UTF-8 conversion.
fun checkUtf16to8Throwing(string: String, expected: IntArray) = checkUtf16to8(string, expected) { toUtf8OrThrow() } fun checkUtf16to8Replacing(string: String, expected: IntArray) {
checkUtf16to8(string, expected) { toUtf8() }
checkUtf16to8(string, expected) { encodeToByteArray() }
}
fun checkUtf16to8Throwing(string: String, expected: IntArray) {
checkUtf16to8(string, expected) { toUtf8OrThrow() }
checkUtf16to8(string, expected) { encodeToByteArray(throwOnInvalidSequence = true) }
}
fun checkValidUtf16to8(string: String, expected: IntArray) {
checkUtf16to8Replacing(string, expected)
checkUtf16to8Throwing(string, expected)
}
fun checkUtf16to8Replacing(string: String, expected: IntArray, start: Int, size: Int) = fun checkUtf16to8Replacing(string: String, expected: IntArray, start: Int, size: Int) {
checkUtf16to8(string, expected) { toUtf8(start, size) } checkUtf16to8(string, expected) { toUtf8(start, size) }
checkUtf16to8(string, expected) { encodeToByteArray(start, start + size) }
}
fun checkUtf16to8Throwing(string: String, expected: IntArray, start: Int, size: Int) {
checkUtf16to8(string, expected) { toUtf8OrThrow(start, size) }
checkUtf16to8(string, expected) { encodeToByteArray(start, start + size, true) }
}
fun checkValidUtf16to8(string: String, expected: IntArray, start: Int, size: Int) {
checkUtf16to8Replacing(string, expected, start, size)
checkUtf16to8Throwing(string, expected, start, size)
}
fun checkUtf16to8Throwing(string: String, expected: IntArray, start: Int, size: Int) =
checkUtf16to8(string, expected) { toUtf8OrThrow(start, size) }
// Utils for checking successful UTF-8 to UTF-16 conversion.
fun checkUtf8to16(expected: String, array: IntArray, conversion: ByteArray.() -> String) { fun checkUtf8to16(expected: String, array: IntArray, conversion: ByteArray.() -> String) {
array.forEach { array.forEach {
assertTrue(it in Byte.MIN_VALUE..Byte.MAX_VALUE, "Expected array contains illegal values") assertTrue(it in Byte.MIN_VALUE..Byte.MAX_VALUE, "Expected array contains illegal values")
@@ -47,14 +69,62 @@ fun checkUtf8to16(expected: String, array: IntArray, conversion: ByteArray.() ->
""".trimIndent()) """.trimIndent())
} }
fun checkUtf8to16Replacing(expected: String, array: IntArray) = checkUtf8to16(expected, array) { stringFromUtf8() } fun checkZeroTerminatedUtf8to16Replacing(expected: String, array: IntArray) {
fun checkUtf8to16Throwing(expected: String, array: IntArray) = checkUtf8to16(expected, array) { stringFromUtf8OrThrow() } checkUtf8to16(expected, array) { stringFromUtf8() }
checkUtf8to16(expected, array) { toKString() }
}
fun checkUtf8to16Replacing(expected: String, array: IntArray) {
checkUtf8to16(expected, array) { stringFromUtf8() }
checkZeroTerminatedUtf8to16Replacing(expected, array)
checkZeroTerminatedUtf8to16Replacing(expected, array.copyOf(array.size + 1))
}
fun checkZeroTerminatedUtf8to16Throwing(expected: String, array: IntArray) {
checkUtf8to16(expected, array) { stringFromUtf8OrThrow() }
checkUtf8to16(expected, array) { toKString(throwOnInvalidSequence = true) }
}
fun checkUtf8to16Throwing(expected: String, array: IntArray) {
checkUtf8to16(expected, array) { decodeToString(throwOnInvalidSequence = true) }
checkZeroTerminatedUtf8to16Throwing(expected, array)
checkZeroTerminatedUtf8to16Throwing(expected, array.copyOf(array.size + 1))
}
fun checkValidUtf8to16(expected: String, array: IntArray) {
checkUtf8to16Replacing(expected, array)
checkUtf8to16Throwing(expected, array)
}
fun checkValidZeroTerminatedUtf8to16(expected: String, array: IntArray) {
checkZeroTerminatedUtf8to16Replacing(expected, array)
checkZeroTerminatedUtf8to16Throwing(expected, array)
}
fun checkUtf8to16Replacing(expected: String, array: IntArray, start: Int, size: Int) = fun checkZeroTerminatedUtf8to16Replacing(expected: String, array: IntArray, start: Int, size: Int) {
checkUtf8to16(expected, array) { stringFromUtf8(start, size) } checkUtf8to16(expected, array) { stringFromUtf8(start, size) }
fun checkUtf8to16Throwing(expected: String, array: IntArray, start: Int, size: Int) = checkUtf8to16(expected, array) { toKString(start, start + size) }
checkUtf8to16(expected, array) { stringFromUtf8OrThrow(start, size) } }
fun checkUtf8to16Replacing(expected: String, array: IntArray, start: Int, size: Int) {
checkUtf8to16(expected, array) { decodeToString(start, start + size) }
checkZeroTerminatedUtf8to16Replacing(expected, array, start, size)
checkZeroTerminatedUtf8to16Replacing(expected, array.copyOf(array.size + 1), start, size)
}
fun checkZeroTerminatedUtf8to16Throwing(expected: String, array: IntArray, start: Int, size: Int) {
checkUtf8to16(expected, array) { stringFromUtf8OrThrow(start, size) }
checkUtf8to16(expected, array) { toKString(start, start + size, true) }
}
fun checkUtf8to16Throwing(expected: String, array: IntArray, start: Int, size: Int) {
checkUtf8to16(expected, array) { decodeToString(start, start + size, true) }
checkZeroTerminatedUtf8to16Throwing(expected, array, start, size)
checkZeroTerminatedUtf8to16Throwing(expected, array.copyOf(array.size + 1), start, size)
}
fun checkValidUtf8to16(expected: String, array: IntArray, start: Int, size: Int) {
checkUtf8to16Replacing(expected, array, start, size)
checkUtf8to16Throwing(expected, array, start, size)
}
fun checkValidZeroTerminatedUtf8to16(expected: String, array: IntArray, start: Int, size: Int) {
checkZeroTerminatedUtf8to16Replacing(expected, array, start, size)
checkZeroTerminatedUtf8to16Throwing(expected, array, start, size)
}
// Utils for checking malformed UTF-16 to UTF-8 conversion.
fun <T: Any> checkThrows(e: KClass<T>, string: String, action: () -> Unit) { fun <T: Any> checkThrows(e: KClass<T>, string: String, action: () -> Unit) {
var exception: Throwable? = null var exception: Throwable? = null
try { try {
@@ -62,7 +132,7 @@ fun <T: Any> checkThrows(e: KClass<T>, string: String, action: () -> Unit) {
} catch (e: Throwable) { } catch (e: Throwable) {
exception = e exception = e
} }
assertNotNull(exception, "No excpetion was thrown for string: $string") assertNotNull(exception, "No exception was thrown for string: $string")
assertTrue(e.isInstance(exception),""" assertTrue(e.isInstance(exception),"""
Wrong exception was thrown for string: $string Wrong exception was thrown for string: $string
Expected: ${e.qualifiedName} Expected: ${e.qualifiedName}
@@ -70,51 +140,114 @@ fun <T: Any> checkThrows(e: KClass<T>, string: String, action: () -> Unit) {
""".trimIndent()) """.trimIndent())
} }
fun checkUtf16to8Throws(string: String) = checkThrows(IllegalCharacterConversionException::class, string) { fun checkUtf16to8Throws(string: String) {
string.toUtf8OrThrow() checkThrows(IllegalCharacterConversionException::class, string) { string.toUtf8OrThrow() }
checkThrows(CharacterCodingException::class, string) { string.encodeToByteArray(throwOnInvalidSequence = true) }
}
fun checkUtf16to8Throws(string: String, start: Int, size: Int) {
checkThrows(IllegalCharacterConversionException::class, string) { string.toUtf8OrThrow(start, size) }
checkThrows(CharacterCodingException::class, string) { string.encodeToByteArray(start, start + size, true) }
} }
fun checkUtf16to8Throws(string: String, start: Int, size: Int) =
checkThrows(IllegalCharacterConversionException::class, string) {
string.toUtf8OrThrow(start, size)
}
fun checkUtf8to16Throws(string: String, array: IntArray) // Utils for checking malformed UTF-8 to UTF-16 conversion.
= checkThrows(IllegalCharacterConversionException::class, string) { fun <T: Any> checkUtf8to16Throws(e: KClass<T>, string: String, array: IntArray, conversion: ByteArray.() -> String)
= checkThrows(e, string) {
array.forEach { array.forEach {
assertTrue(it in Byte.MIN_VALUE..Byte.MAX_VALUE, "Expected array contains illegal values") assertTrue(it in Byte.MIN_VALUE..Byte.MAX_VALUE, "Expected array contains illegal values")
} }
val arrayBytes = ByteArray(array.size) { i -> array[i].toByte() } val arrayBytes = ByteArray(array.size) { i -> array[i].toByte() }
arrayBytes.stringFromUtf8OrThrow() arrayBytes.conversion()
} }
fun checkUtf8to16Throws(string: String, array: IntArray, start: Int, size: Int) fun checkZeroTerminatedUtf8to16Throws(string: String, array: IntArray) {
= checkThrows(IllegalCharacterConversionException::class, string) { checkUtf8to16Throws(IllegalCharacterConversionException::class, string, array) { stringFromUtf8OrThrow() }
array.forEach { checkUtf8to16Throws(CharacterCodingException::class, string, array) { toKString(throwOnInvalidSequence = true) }
assertTrue(it in Byte.MIN_VALUE..Byte.MAX_VALUE, "Expected array contains illegal values") }
} fun checkUtf8to16Throws(string: String, array: IntArray) {
val arrayBytes = ByteArray(array.size) { i -> array[i].toByte() } checkUtf8to16Throws(CharacterCodingException::class, string, array) { decodeToString(throwOnInvalidSequence = true) }
arrayBytes.stringFromUtf8OrThrow(start, size) checkZeroTerminatedUtf8to16Throws(string, array)
checkZeroTerminatedUtf8to16Throws(string, array.copyOf(array.size + 1))
}
fun checkZeroTerminatedUtf8to16Throws(string: String, array: IntArray, start: Int, size: Int) {
checkUtf8to16Throws(IllegalCharacterConversionException::class, string, array) { stringFromUtf8OrThrow(start, size) }
checkUtf8to16Throws(CharacterCodingException::class, string, array) { toKString(start, start + size, true) }
}
fun checkUtf8to16Throws(string: String, array: IntArray, start: Int, size: Int) {
checkUtf8to16Throws(CharacterCodingException::class, string, array) { decodeToString(start, start + size, true) }
checkZeroTerminatedUtf8to16Throws(string, array, start, size)
checkZeroTerminatedUtf8to16Throws(string, array.copyOf(array.size + 1), start, size)
} }
fun checkDobuleConversionThrows(string: String) = checkThrows(NumberFormatException::class, string) { // Utils for checking String to floating-point number conversion.
fun checkDoubleConversionThrows(string: String) = checkThrows(NumberFormatException::class, string) {
string.toDouble() string.toDouble()
} }
fun checkFloatConversionThrows(string: String) = checkThrows(NumberFormatException::class, string) { fun checkFloatConversionThrows(string: String) = checkThrows(NumberFormatException::class, string) {
string.toFloat() string.toFloat()
} }
// endregion
// region UTF-16 -> UTF-8 conversion
// Utils for checking invalid-bounds-exception thrown by UTF-16 to UTF-8 conversion.
fun <T: Any> checkOutOfBoundsUtf16to8Replacing(e: KClass<T>, string: String, start: Int, size: Int) {
checkThrows(e, string) { string.toUtf8(start, size) }
checkThrows(e, string) { string.encodeToByteArray(start, start + size) }
}
fun <T: Any> checkOutOfBoundsUtf16to8Throwing(e: KClass<T>, string: String, start: Int, size: Int) {
checkThrows(e, string) { string.toUtf8OrThrow(start, size) }
checkThrows(e, string) { string.encodeToByteArray(start, start + size, true) }
}
fun <T: Any> checkOutOfBoundsUtf16to8(e: KClass<T>, string: String, start: Int, size: Int) {
checkOutOfBoundsUtf16to8Replacing(e, string, start, size)
checkOutOfBoundsUtf16to8Throwing(e, string, start, size)
}
// Utils for checking invalid-bounds-exception thrown by UTF-8 to UTF-16 conversion.
fun <T: Any> checkOutOfBoundsZeroTerminatedUtf8to16Replacing(e: KClass<T>, string: String, byteArray: ByteArray, start: Int, size: Int) {
checkThrows(e, string) { byteArray.stringFromUtf8(start, size) }
checkThrows(e, string) { byteArray.toKString(start, start + size) }
}
fun <T: Any> checkOutOfBoundsUtf8to16Replacing(e: KClass<T>, string: String, byteArray: ByteArray, start: Int, size: Int) {
checkThrows(e, string) { byteArray.decodeToString(start, start + size) }
checkOutOfBoundsZeroTerminatedUtf8to16Replacing(e, string, byteArray, start, size)
}
fun <T: Any> checkOutOfBoundsZeroTerminatedUtf8to16Throwing(e: KClass<T>, string: String, byteArray: ByteArray, start: Int, size: Int) {
checkThrows(e, string) { byteArray.stringFromUtf8OrThrow(start, size) }
checkThrows(e, string) { byteArray.toKString(start, start + size, true) }
}
fun <T: Any> checkOutOfBoundsUtf8to16Throwing(e: KClass<T>, string: String, byteArray: ByteArray, start: Int, size: Int) {
checkThrows(e, string) { byteArray.decodeToString(start, start + size, true) }
checkOutOfBoundsZeroTerminatedUtf8to16Throwing(e, string, byteArray, start, size)
}
fun <T: Any> checkOutOfBoundsUtf8to16(e: KClass<T>, string: String, byteArray: ByteArray, start: Int, size: Int) {
checkOutOfBoundsUtf8to16Replacing(e, string, byteArray, start, size)
checkOutOfBoundsUtf8to16Throwing(e, string, byteArray, start, size)
}
fun <T: Any> checkOutOfBoundsZeroTerminatedUtf8to16(e: KClass<T>, string: String, byteArray: ByteArray, start: Int, size: Int) {
checkOutOfBoundsZeroTerminatedUtf8to16Replacing(e, string, byteArray, start, size)
checkOutOfBoundsZeroTerminatedUtf8to16Throwing(e, string, byteArray, start, size)
}
// Util for performing action on result of UTF-8 to UTF-16 conversion.
fun convertUtf8to16(byteArray: ByteArray, action: (String) -> Unit) {
byteArray.stringFromUtf8().let { action(it) }
byteArray.decodeToString().let { action(it) }
byteArray.toKString().let { action(it) }
}
// ------------------------- Test UTF-16 to UTF-8 conversion -------------------------
fun test16to8() { fun test16to8() {
// Test manual conversion with replacement.
// Valid strings. // Valid strings.
checkUtf16to8Replacing("Hello", intArrayOf('H'.toInt(), 'e'.toInt(), 'l'.toInt(), 'l'.toInt(), 'o'.toInt())) checkValidUtf16to8("Hello", intArrayOf('H'.toInt(), 'e'.toInt(), 'l'.toInt(), 'l'.toInt(), 'o'.toInt()))
checkUtf16to8Replacing("Привет", intArrayOf(-48, -97, -47, -128, -48, -72, -48, -78, -48, -75, -47, -126)) checkValidUtf16to8("Привет", intArrayOf(-48, -97, -47, -128, -48, -72, -48, -78, -48, -75, -47, -126))
checkUtf16to8Replacing("\uD800\uDC00", intArrayOf(-16, -112, -128, -128)) checkValidUtf16to8("\uD800\uDC00", intArrayOf(-16, -112, -128, -128))
checkUtf16to8Replacing("", intArrayOf()) checkValidUtf16to8("", intArrayOf())
// Test manual conversion with replacement.
// Illegal surrogate pair -> replace with default // Illegal surrogate pair -> replace with default
checkUtf16to8Replacing("\uDC00\uD800", intArrayOf(-17, -65, -67, -17, -65, -67)) checkUtf16to8Replacing("\uDC00\uD800", intArrayOf(-17, -65, -67, -17, -65, -67))
// Different kinds of input // Different kinds of input
@@ -125,14 +258,7 @@ fun test16to8() {
checkUtf16to8Replacing("\uDC0012", intArrayOf(-17, -65, -67, '1'.toInt(), '2'.toInt())) checkUtf16to8Replacing("\uDC0012", intArrayOf(-17, -65, -67, '1'.toInt(), '2'.toInt()))
checkUtf16to8Replacing("12\uD800", intArrayOf('1'.toInt(), '2'.toInt(), -17, -65, -67)) checkUtf16to8Replacing("12\uD800", intArrayOf('1'.toInt(), '2'.toInt(), -17, -65, -67))
// Test manual conversion with an exception if an input is invalid. // Test manual conversion with an exception if an input is invalid.
// Valid strings.
checkUtf16to8Throwing("Hello", intArrayOf('H'.toInt(), 'e'.toInt(), 'l'.toInt(), 'l'.toInt(), 'o'.toInt()))
checkUtf16to8Throwing("Привет", intArrayOf(-48, -97, -47, -128, -48, -72, -48, -78, -48, -75, -47, -126))
checkUtf16to8Throwing("\uD800\uDC00", intArrayOf(-16, -112, -128, -128))
checkUtf16to8Replacing("", intArrayOf())
// Illegal surrogate pair -> throw // Illegal surrogate pair -> throw
checkUtf16to8Throws("\uDC00\uD800") checkUtf16to8Throws("\uDC00\uD800")
// Different kinds of input (including illegal one) -> throw // Different kinds of input (including illegal one) -> throw
@@ -145,13 +271,13 @@ fun test16to8() {
// Test double parsing. // Test double parsing.
assertEquals(4.2, "4.2".toDouble()) assertEquals(4.2, "4.2".toDouble())
// Illegal surrogate pair -> throw // Illegal surrogate pair -> throw
checkDobuleConversionThrows("\uDC00\uD800") checkDoubleConversionThrows("\uDC00\uD800")
// Different kinds of input (including illegal one) -> throw // Different kinds of input (including illegal one) -> throw
checkDobuleConversionThrows("\uD800\uDC001\uDC00\uD800") checkDoubleConversionThrows("\uD800\uDC001\uDC00\uD800")
// Lone surrogate - throw // Lone surrogate - throw
checkDobuleConversionThrows("\uD80012") checkDoubleConversionThrows("\uD80012")
checkDobuleConversionThrows("\uDC0012") checkDoubleConversionThrows("\uDC0012")
checkDobuleConversionThrows("12\uD800") checkDoubleConversionThrows("12\uD800")
// Test float parsing. // Test float parsing.
assertEquals(4.2F, "4.2".toFloat()) assertEquals(4.2F, "4.2".toFloat())
@@ -166,21 +292,27 @@ fun test16to8() {
} }
fun test16to8CustomBorders() { fun test16to8CustomBorders() {
// Test manual conversion with replacement and custom borders.
// Valid strings. // Valid strings.
checkUtf16to8Replacing("Hello!", intArrayOf('H'.toInt(), 'e'.toInt()), 0, 2) checkValidUtf16to8("Hello!", intArrayOf('H'.toInt(), 'e'.toInt()), 0, 2)
checkUtf16to8Replacing("Hello!", intArrayOf('e'.toInt(), 'l'.toInt()), 1, 2) checkValidUtf16to8("Hello!", intArrayOf('e'.toInt(), 'l'.toInt()), 1, 2)
checkUtf16to8Replacing("Hello!", intArrayOf('o'.toInt(), '!'.toInt()), 4, 2) checkValidUtf16to8("Hello!", intArrayOf('o'.toInt(), '!'.toInt()), 4, 2)
checkUtf16to8Replacing("Hello!", intArrayOf(), 0, 0) checkValidUtf16to8("Hello!", intArrayOf(), 0, 0)
checkUtf16to8Replacing("Hello!", intArrayOf(), 6, 0) checkValidUtf16to8("Hello!", intArrayOf(), 6, 0)
checkUtf16to8Replacing("\uD800\uDC00\uD800\uDC00\uD800\uDC00\uD800\uDC00", checkValidUtf16to8("\uD800\uDC00\uD800\uDC00\uD800\uDC00\uD800\uDC00",
intArrayOf(-16, -112, -128, -128, -16, -112, -128, -128), 0, 4) intArrayOf(-16, -112, -128, -128, -16, -112, -128, -128), 0, 4)
checkUtf16to8Replacing("\uD800\uDC00\uD800\uDC00\uD800\uDC00\uD800\uDC00", checkValidUtf16to8("\uD800\uDC00\uD800\uDC00\uD800\uDC00\uD800\uDC00",
intArrayOf(-16, -112, -128, -128, -16, -112, -128, -128), 2, 4) intArrayOf(-16, -112, -128, -128, -16, -112, -128, -128), 2, 4)
checkUtf16to8Replacing("\uD800\uDC00\uD800\uDC00\uD800\uDC00\uD800\uDC00", checkValidUtf16to8("\uD800\uDC00\uD800\uDC00\uD800\uDC00\uD800\uDC00",
intArrayOf(-16, -112, -128, -128, -16, -112, -128, -128), 4, 4) intArrayOf(-16, -112, -128, -128, -16, -112, -128, -128), 4, 4)
// Index out of bound
checkOutOfBoundsUtf16to8(IndexOutOfBoundsException::class, "Hello", -1, 4)
checkOutOfBoundsUtf16to8(IndexOutOfBoundsException::class, "Hello", 5, 10)
checkOutOfBoundsUtf16to8(IndexOutOfBoundsException::class, "Hello", 2, 10)
checkOutOfBoundsUtf16to8(IllegalArgumentException::class, "Hello", 3, -2)
// Test manual conversion with replacement and custom borders.
// Illegal surrogate pair -> replace with default // Illegal surrogate pair -> replace with default
checkUtf16to8Replacing("\uDC00\uD80012", checkUtf16to8Replacing("\uDC00\uD80012",
intArrayOf(-17, -65, -67, -17, -65, -67, '1'.toInt()), 0, 3) intArrayOf(-17, -65, -67, -17, -65, -67, '1'.toInt()), 0, 3)
@@ -193,28 +325,7 @@ fun test16to8CustomBorders() {
checkUtf16to8Replacing("1\uD800\uDC002", intArrayOf('1'.toInt(), -17, -65, -67), 0, 2) checkUtf16to8Replacing("1\uD800\uDC002", intArrayOf('1'.toInt(), -17, -65, -67), 0, 2)
checkUtf16to8Replacing("1\uD800\uDC002", intArrayOf(-17, -65, -67, '2'.toInt()), 2, 2) checkUtf16to8Replacing("1\uD800\uDC002", intArrayOf(-17, -65, -67, '2'.toInt()), 2, 2)
// Index out of bound
checkThrows(IndexOutOfBoundsException::class, "Hello") { "Hello".toUtf8(-1, 4)}
checkThrows(IndexOutOfBoundsException::class, "Hello") { "Hello".toUtf8(5, 10)}
checkThrows(IndexOutOfBoundsException::class, "Hello") { "Hello".toUtf8(2, 10)}
checkThrows(IllegalArgumentException::class, "Hello") { "Hello".toUtf8(3, -2)}
// Test manual conversion with an exception if an input is invalid and custom borders. // Test manual conversion with an exception if an input is invalid and custom borders.
// Valid strings.
checkUtf16to8Throwing("Hello!", intArrayOf('H'.toInt(), 'e'.toInt()), 0, 2)
checkUtf16to8Throwing("Hello!", intArrayOf('e'.toInt(), 'l'.toInt()), 1, 2)
checkUtf16to8Throwing("Hello!", intArrayOf('o'.toInt(), '!'.toInt()), 4, 2)
checkUtf16to8Throwing("Hello!", intArrayOf(), 0, 0)
checkUtf16to8Throwing("Hello!", intArrayOf(), 6, 0)
checkUtf16to8Throwing("\uD800\uDC00\uD800\uDC00\uD800\uDC00\uD800\uDC00",
intArrayOf(-16, -112, -128, -128, -16, -112, -128, -128), 0, 4)
checkUtf16to8Throwing("\uD800\uDC00\uD800\uDC00\uD800\uDC00\uD800\uDC00",
intArrayOf(-16, -112, -128, -128, -16, -112, -128, -128), 2, 4)
checkUtf16to8Throwing("\uD800\uDC00\uD800\uDC00\uD800\uDC00\uD800\uDC00",
intArrayOf(-16, -112, -128, -128, -16, -112, -128, -128), 4, 4)
// Illegal surrogate pair -> throw // Illegal surrogate pair -> throw
checkUtf16to8Throws("\uDC00\uD80012", 0, 3) checkUtf16to8Throws("\uDC00\uD80012", 0, 3)
checkUtf16to8Throws("1\uDC00\uD8002", 1, 3) checkUtf16to8Throws("1\uDC00\uD8002", 1, 3)
@@ -223,12 +334,6 @@ fun test16to8CustomBorders() {
// Lone surrogate -> throw // Lone surrogate -> throw
checkUtf16to8Throws("1\uD800\uDC002", 0, 2) checkUtf16to8Throws("1\uD800\uDC002", 0, 2)
checkUtf16to8Throws("1\uD800\uDC002", 2, 2) checkUtf16to8Throws("1\uD800\uDC002", 2, 2)
// Index out of bound
checkThrows(IndexOutOfBoundsException::class, "Hello") { "Hello".toUtf8OrThrow(-1, 4)}
checkThrows(IndexOutOfBoundsException::class, "Hello") { "Hello".toUtf8OrThrow(5, 10)}
checkThrows(IndexOutOfBoundsException::class, "Hello") { "Hello".toUtf8OrThrow(2, 10)}
checkThrows(IllegalArgumentException::class, "Hello") { "Hello".toUtf8OrThrow(3, -2)}
} }
fun testPrint() { fun testPrint() {
@@ -247,21 +352,21 @@ fun testPrint() {
// https://github.com/JetBrains/kotlin-native/issues/1091 // https://github.com/JetBrains/kotlin-native/issues/1091
val array = byteArrayOf(0xF0.toByte(), 0x9F.toByte(), 0x98.toByte(), 0xA5.toByte()) val array = byteArrayOf(0xF0.toByte(), 0x9F.toByte(), 0x98.toByte(), 0xA5.toByte())
val badStr = array.stringFromUtf8() convertUtf8to16(array) { badString ->
assertEquals(2, badStr.length) assertEquals(2, badString.length)
println(badStr) println(badString)
}
} }
// endregion
// region UTF-8 -> UTF-16 conversion // ------------------------- Test UTF-8 to UTF-16 conversion -------------------------
fun test8to16() { fun test8to16() {
// Test manual conversion with replacement.
// Valid strings. // Valid strings.
checkUtf8to16Replacing("Hello", intArrayOf('H'.toInt(), 'e'.toInt(), 'l'.toInt(), 'l'.toInt(), 'o'.toInt())) checkValidUtf8to16("Hello", intArrayOf('H'.toInt(), 'e'.toInt(), 'l'.toInt(), 'l'.toInt(), 'o'.toInt()))
checkUtf8to16Replacing("Привет", intArrayOf(-48, -97, -47, -128, -48, -72, -48, -78, -48, -75, -47, -126)) checkValidUtf8to16("Привет", intArrayOf(-48, -97, -47, -128, -48, -72, -48, -78, -48, -75, -47, -126))
checkUtf8to16Replacing("\uD800\uDC00", intArrayOf(-16, -112, -128, -128)) checkValidUtf8to16("\uD800\uDC00", intArrayOf(-16, -112, -128, -128))
checkUtf8to16Replacing("", intArrayOf()) checkValidUtf8to16("", intArrayOf())
// Test manual conversion with replacement.
// Incorrect UTF-8 lead character. // Incorrect UTF-8 lead character.
checkUtf8to16Replacing("\uFFFD1", intArrayOf(-1, '1'.toInt())) checkUtf8to16Replacing("\uFFFD1", intArrayOf(-1, '1'.toInt()))
@@ -270,12 +375,6 @@ fun test8to16() {
checkUtf8to16Replacing("\uFFFD1\uFFFD", intArrayOf(-16, -97, -104, '1'.toInt(), -16, -97, -104)) checkUtf8to16Replacing("\uFFFD1\uFFFD", intArrayOf(-16, -97, -104, '1'.toInt(), -16, -97, -104))
// Test manual conversion with exception throwing // Test manual conversion with exception throwing
// Valid strings.
checkUtf8to16Throwing("Hello", intArrayOf('H'.toInt(), 'e'.toInt(), 'l'.toInt(), 'l'.toInt(), 'o'.toInt()))
checkUtf8to16Throwing("Привет", intArrayOf(-48, -97, -47, -128, -48, -72, -48, -78, -48, -75, -47, -126))
checkUtf8to16Throwing("\uD800\uDC00", intArrayOf(-16, -112, -128, -128))
checkUtf8to16Throwing("", intArrayOf())
// Incorrect UTF-8 lead character -> throw. // Incorrect UTF-8 lead character -> throw.
checkUtf8to16Throws("\uFFFD1", intArrayOf(-1, '1'.toInt())) checkUtf8to16Throws("\uFFFD1", intArrayOf(-1, '1'.toInt()))
@@ -285,26 +384,35 @@ fun test8to16() {
} }
fun test8to16CustomBorders() { fun test8to16CustomBorders() {
// Conversion with replacement. // Valid strings.
checkUtf8to16Replacing("He", checkValidUtf8to16("He",
intArrayOf('H'.toInt(), 'e'.toInt(), 'l'.toInt(), 'l'.toInt(), 'o'.toInt()),0, 2) intArrayOf('H'.toInt(), 'e'.toInt(), 'l'.toInt(), 'l'.toInt(), 'o'.toInt()),0, 2)
checkUtf8to16Replacing("ll", checkValidUtf8to16("ll",
intArrayOf('H'.toInt(), 'e'.toInt(), 'l'.toInt(), 'l'.toInt(), 'o'.toInt()), 2, 2) intArrayOf('H'.toInt(), 'e'.toInt(), 'l'.toInt(), 'l'.toInt(), 'o'.toInt()), 2, 2)
checkUtf8to16Replacing("lo", checkValidUtf8to16("lo",
intArrayOf('H'.toInt(), 'e'.toInt(), 'l'.toInt(), 'l'.toInt(), 'o'.toInt()), 3, 2) intArrayOf('H'.toInt(), 'e'.toInt(), 'l'.toInt(), 'l'.toInt(), 'o'.toInt()), 3, 2)
checkUtf8to16Replacing("", checkValidUtf8to16("",
intArrayOf('H'.toInt(), 'e'.toInt(), 'l'.toInt(), 'l'.toInt(), 'o'.toInt()), 0, 0) intArrayOf('H'.toInt(), 'e'.toInt(), 'l'.toInt(), 'l'.toInt(), 'o'.toInt()), 0, 0)
checkUtf8to16Replacing("\uD800\uDC00\uD800\uDC00", checkValidUtf8to16("\uD800\uDC00\uD800\uDC00",
intArrayOf(-16, -112, -128, -128, -16, -112, -128, -128, -16, -112, -128, -128, -16, -112, -128, -128), intArrayOf(-16, -112, -128, -128, -16, -112, -128, -128, -16, -112, -128, -128, -16, -112, -128, -128),
0, 8) 0, 8)
checkUtf8to16Replacing("\uD800\uDC00\uD800\uDC00", checkValidUtf8to16("\uD800\uDC00\uD800\uDC00",
intArrayOf(-16, -112, -128, -128, -16, -112, -128, -128, -16, -112, -128, -128, -16, -112, -128, -128), intArrayOf(-16, -112, -128, -128, -16, -112, -128, -128, -16, -112, -128, -128, -16, -112, -128, -128),
4, 8) 4, 8)
checkUtf8to16Replacing("\uD800\uDC00\uD800\uDC00", checkValidUtf8to16("\uD800\uDC00\uD800\uDC00",
intArrayOf(-16, -112, -128, -128, -16, -112, -128, -128, -16, -112, -128, -128, -16, -112, -128, -128), intArrayOf(-16, -112, -128, -128, -16, -112, -128, -128, -16, -112, -128, -128, -16, -112, -128, -128),
8, 8) 8, 8)
// Index out of bound
val helloArray = byteArrayOf('H'.toByte(), 'e'.toByte(), 'l'.toByte(), 'l'.toByte(), 'o'.toByte())
checkOutOfBoundsUtf8to16(IndexOutOfBoundsException::class, "Hello", helloArray, -1, 4)
checkOutOfBoundsUtf8to16(IndexOutOfBoundsException::class, "Hello", helloArray, 5, 10)
checkOutOfBoundsUtf8to16(IndexOutOfBoundsException::class, "Hello", helloArray, 2, 10)
checkOutOfBoundsUtf8to16(IndexOutOfBoundsException::class, "Hello", helloArray, 10, 0)
checkOutOfBoundsUtf8to16(IllegalArgumentException::class, "Hello", helloArray, 3, -2)
// Test manual conversion with replacement and custom borders.
// Incorrect UTF-8 lead character. // Incorrect UTF-8 lead character.
checkUtf8to16Replacing("\uFFFD1", intArrayOf(-1, '1'.toInt(), '2'.toInt()), 0, 2) checkUtf8to16Replacing("\uFFFD1", intArrayOf(-1, '1'.toInt(), '2'.toInt()), 0, 2)
checkUtf8to16Replacing("\uFFFD2", intArrayOf('1'.toInt(), -1, '2'.toInt(), '3'.toInt()), 1, 2) checkUtf8to16Replacing("\uFFFD2", intArrayOf('1'.toInt(), -1, '2'.toInt(), '3'.toInt()), 1, 2)
@@ -315,35 +423,7 @@ fun test8to16CustomBorders() {
checkUtf8to16Replacing("\uFFFD2", intArrayOf('1'.toInt(), -16, -97, -104, '2'.toInt(), '3'.toInt()), 1, 4) checkUtf8to16Replacing("\uFFFD2", intArrayOf('1'.toInt(), -16, -97, -104, '2'.toInt(), '3'.toInt()), 1, 4)
checkUtf8to16Replacing("2\uFFFD", intArrayOf('1'.toInt(), '2'.toInt(), -16, -97, -104), 1, 4) checkUtf8to16Replacing("2\uFFFD", intArrayOf('1'.toInt(), '2'.toInt(), -16, -97, -104), 1, 4)
// Test manual conversion with an exception if an input is invalid and custom borders.
// Index out of bound
val helloArray = byteArrayOf('H'.toByte(), 'e'.toByte(), 'l'.toByte(), 'l'.toByte(), 'o'.toByte())
checkThrows(IndexOutOfBoundsException::class, "Hello") { helloArray.stringFromUtf8(-1, 4)}
checkThrows(IndexOutOfBoundsException::class, "Hello") { helloArray.stringFromUtf8(5, 10)}
checkThrows(IndexOutOfBoundsException::class, "Hello") { helloArray.stringFromUtf8(2, 10)}
checkThrows(IndexOutOfBoundsException::class, "Hello") { helloArray.stringFromUtf8(10, 0)}
checkThrows(IllegalArgumentException::class, "Hello") { helloArray.stringFromUtf8(3, -2)}
// Conversion with throwing
checkUtf8to16Throwing("He",
intArrayOf('H'.toInt(), 'e'.toInt(), 'l'.toInt(), 'l'.toInt(), 'o'.toInt()),0, 2)
checkUtf8to16Throwing("ll",
intArrayOf('H'.toInt(), 'e'.toInt(), 'l'.toInt(), 'l'.toInt(), 'o'.toInt()), 2, 2)
checkUtf8to16Throwing("lo",
intArrayOf('H'.toInt(), 'e'.toInt(), 'l'.toInt(), 'l'.toInt(), 'o'.toInt()), 3, 2)
checkUtf8to16Throwing("",
intArrayOf('H'.toInt(), 'e'.toInt(), 'l'.toInt(), 'l'.toInt(), 'o'.toInt()), 0, 0)
checkUtf8to16Throwing("\uD800\uDC00\uD800\uDC00",
intArrayOf(-16, -112, -128, -128, -16, -112, -128, -128, -16, -112, -128, -128, -16, -112, -128, -128),
0, 8)
checkUtf8to16Throwing("\uD800\uDC00\uD800\uDC00",
intArrayOf(-16, -112, -128, -128, -16, -112, -128, -128, -16, -112, -128, -128, -16, -112, -128, -128),
4, 8)
checkUtf8to16Throwing("\uD800\uDC00\uD800\uDC00",
intArrayOf(-16, -112, -128, -128, -16, -112, -128, -128, -16, -112, -128, -128, -16, -112, -128, -128),
8, 8)
// Incorrect UTF-8 lead character. // Incorrect UTF-8 lead character.
checkUtf8to16Throws("\uFFFD1", intArrayOf(-1, '1'.toInt(), '2'.toInt()), 0, 2) checkUtf8to16Throws("\uFFFD1", intArrayOf(-1, '1'.toInt(), '2'.toInt()), 0, 2)
checkUtf8to16Throws("\uFFFD2", intArrayOf('1'.toInt(), -1, '2'.toInt(), '3'.toInt()), 1, 2) checkUtf8to16Throws("\uFFFD2", intArrayOf('1'.toInt(), -1, '2'.toInt(), '3'.toInt()), 1, 2)
@@ -353,20 +433,61 @@ fun test8to16CustomBorders() {
checkUtf8to16Throws("\uFFFD1", intArrayOf(-16, -97, -104, '1'.toInt(), '2'.toInt()), 0, 4) checkUtf8to16Throws("\uFFFD1", intArrayOf(-16, -97, -104, '1'.toInt(), '2'.toInt()), 0, 4)
checkUtf8to16Throws("\uFFFD2", intArrayOf('1'.toInt(), -16, -97, -104, '2'.toInt(), '3'.toInt()), 1, 4) checkUtf8to16Throws("\uFFFD2", intArrayOf('1'.toInt(), -16, -97, -104, '2'.toInt(), '3'.toInt()), 1, 4)
checkUtf8to16Throws("2\uFFFD", intArrayOf('1'.toInt(), '2'.toInt(), -16, -97, -104), 1, 4) checkUtf8to16Throws("2\uFFFD", intArrayOf('1'.toInt(), '2'.toInt(), -16, -97, -104), 1, 4)
// Index out of bound
checkThrows(IndexOutOfBoundsException::class, "Hello") { helloArray.stringFromUtf8OrThrow(-1, 4)}
checkThrows(IndexOutOfBoundsException::class, "Hello") { helloArray.stringFromUtf8OrThrow(5, 10)}
checkThrows(IndexOutOfBoundsException::class, "Hello") { helloArray.stringFromUtf8OrThrow(2, 10)}
checkThrows(IndexOutOfBoundsException::class, "Hello") { helloArray.stringFromUtf8OrThrow(10, 0)}
checkThrows(IllegalArgumentException::class, "Hello") { helloArray.stringFromUtf8OrThrow(3, -2)}
} }
// endregion
// ----------------- Test zero-terminated UTF-8 to UTF-16 conversion -----------------
fun testZeroTerminated8To16() {
// Valid strings.
checkValidZeroTerminatedUtf8to16("Hell", intArrayOf('H'.toInt(), 'e'.toInt(), 'l'.toInt(), 'l'.toInt(), 0, 'o'.toInt()))
checkValidZeroTerminatedUtf8to16("При", intArrayOf(-48, -97, -47, -128, -48, -72, 0, -48, -78, 0, -48, -75, -47, -126))
checkValidZeroTerminatedUtf8to16("\uD800\uDC00", intArrayOf(-16, -112, -128, -128, 0, -16, -112, -128, -128))
checkValidZeroTerminatedUtf8to16("", intArrayOf(0, 'H'.toInt()))
// Test manual conversion with replacement.
// Incorrect UTF-8 lead character.
checkZeroTerminatedUtf8to16Replacing("\uFFFD", intArrayOf(-1, 0, '1'.toInt()))
// Incomplete codepoint.
checkZeroTerminatedUtf8to16Replacing("\uFFFD", intArrayOf(-16, -97, -104, 0, '1'.toInt()))
checkZeroTerminatedUtf8to16Replacing("\uFFFD1", intArrayOf(-16, -97, -104, '1'.toInt(), 0, -16, -97, -104))
// Test manual conversion with exception throwing
// Incorrect UTF-8 lead character -> throw.
checkZeroTerminatedUtf8to16Throws("\uFFFD", intArrayOf(-1, 0, '1'.toInt()))
// Incomplete codepoint -> throw.
checkZeroTerminatedUtf8to16Throws("\uFFFD", intArrayOf(-16, -97, -104, 0, '1'.toInt()))
checkZeroTerminatedUtf8to16Throws("\uFFFD1", intArrayOf(-16, -97, -104, '1'.toInt(), 0, -16, -97, -104))
}
fun testZeroTerminated8To16CustomBorders() {
val array = intArrayOf('a'.toInt(), 'a'.toInt(), 'a'.toInt(), 0, 'b'.toInt(), 'b'.toInt(), 'b'.toInt(), 0)
checkValidZeroTerminatedUtf8to16("aaa", array, 0, 5)
checkValidZeroTerminatedUtf8to16("a", array, 2, 2)
checkValidZeroTerminatedUtf8to16("", array, 3, 2)
checkValidZeroTerminatedUtf8to16("bb", array, 4, 2)
checkValidZeroTerminatedUtf8to16("bbb", array, 4, 3)
checkValidZeroTerminatedUtf8to16("bbb", array, 4, 4)
checkValidZeroTerminatedUtf8to16("bb", array, 5, 3)
checkValidZeroTerminatedUtf8to16("b", array, 6, 2)
checkValidZeroTerminatedUtf8to16("", array, 7, 1)
checkValidZeroTerminatedUtf8to16("", array, 8, 0)
val byteArray = ByteArray(array.size) { array[it].toByte() }
checkOutOfBoundsZeroTerminatedUtf8to16(IndexOutOfBoundsException::class, "aaa0bbb0", byteArray, -1, 4)
checkOutOfBoundsZeroTerminatedUtf8to16(IndexOutOfBoundsException::class, "aaa0bbb0", byteArray, 8, 10)
checkOutOfBoundsZeroTerminatedUtf8to16(IndexOutOfBoundsException::class, "aaa0bbb0", byteArray, 2, 10)
checkOutOfBoundsZeroTerminatedUtf8to16(IndexOutOfBoundsException::class, "aaa0bbb0", byteArray, 10, 0)
checkOutOfBoundsZeroTerminatedUtf8to16(IllegalArgumentException::class, "aaa0bbb0", byteArray, 3, -2)
}
// ------------------------------------ Run tests ------------------------------------
@Test fun runTest() { @Test fun runTest() {
test16to8() test16to8()
test16to8CustomBorders() test16to8CustomBorders()
test8to16() test8to16()
test8to16CustomBorders() test8to16CustomBorders()
testZeroTerminated8To16()
testZeroTerminated8To16CustomBorders()
testPrint() testPrint()
} }
+11 -20
View File
@@ -49,11 +49,8 @@ OBJ_GETTER(utf8ToUtf16Impl, const char* rawString, const char* end, uint32_t cha
} }
template<utf16to8 conversion> template<utf16to8 conversion>
OBJ_GETTER(utf16ToUtf8Impl, KString thiz, KInt start, KInt size) { OBJ_GETTER(unsafeUtf16ToUtf8Impl, KString thiz, KInt start, KInt size) {
RuntimeAssert(thiz->type_info() == theStringTypeInfo, "Must use String"); RuntimeAssert(thiz->type_info() == theStringTypeInfo, "Must use String");
if (start < 0 || size < 0 || (size > static_cast<KInt>(thiz->count_) - start)) {
ThrowArrayIndexOutOfBoundsException();
}
const KChar* utf16 = CharArrayAddressOfElementAt(thiz, start); const KChar* utf16 = CharArrayAddressOfElementAt(thiz, start);
KStdString utf8; KStdString utf8;
conversion(utf16, utf16 + size, back_inserter(utf8)); conversion(utf16, utf16 + size, back_inserter(utf8));
@@ -791,12 +788,9 @@ OBJ_GETTER(Kotlin_String_toLowerCase, KString thiz) {
RETURN_OBJ(result->obj()); RETURN_OBJ(result->obj());
} }
OBJ_GETTER(Kotlin_String_fromCharArray, KConstRef thiz, KInt start, KInt size) { OBJ_GETTER(Kotlin_String_unsafeStringFromCharArray, KConstRef thiz, KInt start, KInt size) {
const ArrayHeader* array = thiz->array(); const ArrayHeader* array = thiz->array();
RuntimeAssert(array->type_info() == theCharArrayTypeInfo, "Must use a char array"); RuntimeAssert(array->type_info() == theCharArrayTypeInfo, "Must use a char array");
if (start < 0 || size < 0 || size > array->count_ - start) {
ThrowArrayIndexOutOfBoundsException();
}
if (size == 0) { if (size == 0) {
RETURN_RESULT_OF0(TheEmptyString); RETURN_RESULT_OF0(TheEmptyString);
@@ -878,37 +872,34 @@ KInt Kotlin_String_getStringLength(KString thiz) {
return thiz->count_; return thiz->count_;
} }
const char* byteArrayAsCString(KConstRef thiz, KInt start, KInt size) { const char* unsafeByteArrayAsCString(KConstRef thiz, KInt start, KInt size) {
const ArrayHeader* array = thiz->array(); const ArrayHeader* array = thiz->array();
RuntimeAssert(array->type_info() == theByteArrayTypeInfo, "Must use a byte array"); RuntimeAssert(array->type_info() == theByteArrayTypeInfo, "Must use a byte array");
if (start < 0 || size < 0 || size > array->count_ - start) {
ThrowArrayIndexOutOfBoundsException();
}
return reinterpret_cast<const char*>(ByteArrayAddressOfElementAt(array, start)); return reinterpret_cast<const char*>(ByteArrayAddressOfElementAt(array, start));
} }
OBJ_GETTER(Kotlin_ByteArray_stringFromUtf8OrThrow, KConstRef thiz, KInt start, KInt size) { OBJ_GETTER(Kotlin_ByteArray_unsafeStringFromUtf8OrThrow, KConstRef thiz, KInt start, KInt size) {
if (size == 0) { if (size == 0) {
RETURN_RESULT_OF0(TheEmptyString); RETURN_RESULT_OF0(TheEmptyString);
} }
const char* rawString = byteArrayAsCString(thiz, start, size); const char* rawString = unsafeByteArrayAsCString(thiz, start, size);
RETURN_RESULT_OF(utf8ToUtf16OrThrow, rawString, size); RETURN_RESULT_OF(utf8ToUtf16OrThrow, rawString, size);
} }
OBJ_GETTER(Kotlin_ByteArray_stringFromUtf8, KConstRef thiz, KInt start, KInt size) { OBJ_GETTER(Kotlin_ByteArray_unsafeStringFromUtf8, KConstRef thiz, KInt start, KInt size) {
if (size == 0) { if (size == 0) {
RETURN_RESULT_OF0(TheEmptyString); RETURN_RESULT_OF0(TheEmptyString);
} }
const char* rawString = byteArrayAsCString(thiz, start, size); const char* rawString = unsafeByteArrayAsCString(thiz, start, size);
RETURN_RESULT_OF(utf8ToUtf16, rawString, size); RETURN_RESULT_OF(utf8ToUtf16, rawString, size);
} }
OBJ_GETTER(Kotlin_String_toUtf8, KString thiz, KInt start, KInt size) { OBJ_GETTER(Kotlin_String_unsafeStringToUtf8, KString thiz, KInt start, KInt size) {
RETURN_RESULT_OF(utf16ToUtf8Impl<utf8::with_replacement::utf16to8>, thiz, start, size); RETURN_RESULT_OF(unsafeUtf16ToUtf8Impl<utf8::with_replacement::utf16to8>, thiz, start, size);
} }
OBJ_GETTER(Kotlin_String_toUtf8OrThrow, KString thiz, KInt start, KInt size) { OBJ_GETTER(Kotlin_String_unsafeStringToUtf8OrThrow, KString thiz, KInt start, KInt size) {
RETURN_RESULT_OF(utf16ToUtf8Impl<utf16toUtf8OrThrow>, thiz, start, size); RETURN_RESULT_OF(unsafeUtf16ToUtf8Impl<utf16toUtf8OrThrow>, thiz, start, size);
} }
KInt Kotlin_StringBuilder_insertString(KRef builder, KInt position, KString fromString) { KInt Kotlin_StringBuilder_insertString(KRef builder, KInt position, KString fromString) {
@@ -166,6 +166,7 @@ public actual open class NumberFormatException : IllegalArgumentException {
actual constructor(message: String?) : super(message) actual constructor(message: String?) : super(message)
} }
@Deprecated("Use CharacterCodingException instead", ReplaceWith("CharacterCodingException"))
public open class IllegalCharacterConversionException : IllegalArgumentException { public open class IllegalCharacterConversionException : IllegalArgumentException {
constructor(): super() constructor(): super()
+38 -27
View File
@@ -5,38 +5,41 @@
package kotlin.native package kotlin.native
import kotlinx.cinterop.toKString
/** /**
* Converts an UTF-8 array into a [String]. Replaces invalid input sequences with a default character. * Converts an UTF-8 array into a [String]. Replaces invalid input sequences with a default character.
*/ */
@Deprecated(
"Use toKString or decodeToString instead",
ReplaceWith("toKString()", "kotlinx.cinterop.toKString")
)
public fun ByteArray.stringFromUtf8() : String { public fun ByteArray.stringFromUtf8() : String {
@Suppress("DEPRECATION")
return this.stringFromUtf8(0, this.size) return this.stringFromUtf8(0, this.size)
} }
/** /**
* Converts an UTF-8 array into a [String]. Replaces invalid input sequences with a default character. * Converts an UTF-8 array into a [String]. Replaces invalid input sequences with a default character.
*/ */
@Deprecated(
"Use toKString or decodeToString instead",
ReplaceWith("toKString(start, start + size)", "kotlinx.cinterop.toKString")
)
public fun ByteArray.stringFromUtf8(start: Int = 0, size: Int = this.size) : String { public fun ByteArray.stringFromUtf8(start: Int = 0, size: Int = this.size) : String {
checkBoundsIndexes(start, start + size, this.size) return toKString(start, start + size)
return stringFromUtf8Impl(start, realSize(this, size))
}
@SymbolName("Kotlin_ByteArray_stringFromUtf8")
internal external fun ByteArray.stringFromUtf8Impl(start: Int, size: Int) : String
private fun realSize(byteArray: ByteArray, size: Int): Int {
var realSize = 0
while (realSize < size && byteArray[realSize] != 0.toByte()) {
realSize++
}
return realSize
} }
/** /**
* Converts an UTF-8 array into a [String]. * Converts an UTF-8 array into a [String].
* @throws [IllegalCharacterConversionException] if the input is invalid. * @throws [IllegalCharacterConversionException] if the input is invalid.
*/ */
@Deprecated(
"Use toKString or decodeToString instead",
ReplaceWith("toKString(throwOnInvalidSequence = true)", "kotlinx.cinterop.toKString")
)
public fun ByteArray.stringFromUtf8OrThrow() : String { public fun ByteArray.stringFromUtf8OrThrow() : String {
@Suppress("DEPRECATION")
return this.stringFromUtf8OrThrow(0, this.size) return this.stringFromUtf8OrThrow(0, this.size)
} }
@@ -44,18 +47,19 @@ public fun ByteArray.stringFromUtf8OrThrow() : String {
* Converts an UTF-8 array into a [String]. * Converts an UTF-8 array into a [String].
* @throws [IllegalCharacterConversionException] if the input is invalid. * @throws [IllegalCharacterConversionException] if the input is invalid.
*/ */
@Deprecated(
"Use toKString or decodeToString instead",
ReplaceWith("toKString(start, start + size, throwOnInvalidSequence = true)", "kotlinx.cinterop.toKString")
)
public fun ByteArray.stringFromUtf8OrThrow(start: Int = 0, size: Int = this.size) : String { public fun ByteArray.stringFromUtf8OrThrow(start: Int = 0, size: Int = this.size) : String {
checkBoundsIndexes(start, start + size, this.size)
try { try {
return stringFromUtf8OrThrowImpl(start, realSize(this, size)) return toKString(start, start + size, throwOnInvalidSequence = true)
} catch (e: CharacterCodingException) { } catch (e: CharacterCodingException) {
@Suppress("DEPRECATION")
throw IllegalCharacterConversionException() throw IllegalCharacterConversionException()
} }
} }
@SymbolName("Kotlin_ByteArray_stringFromUtf8OrThrow")
internal external fun ByteArray.stringFromUtf8OrThrowImpl(start: Int, size: Int) : String
/** /**
* Converts a [String] into an UTF-8 array. Replaces invalid input sequences with a default character. * Converts a [String] into an UTF-8 array. Replaces invalid input sequences with a default character.
*/ */
@@ -71,12 +75,9 @@ public fun String.toUtf8() : ByteArray {
@Deprecated("Use encodeToByteArray instead", ReplaceWith("encodeToByteArray(start, start + size)")) @Deprecated("Use encodeToByteArray instead", ReplaceWith("encodeToByteArray(start, start + size)"))
public fun String.toUtf8(start: Int = 0, size: Int = this.length) : ByteArray { public fun String.toUtf8(start: Int = 0, size: Int = this.length) : ByteArray {
checkBoundsIndexes(start, start + size, this.length) checkBoundsIndexes(start, start + size, this.length)
return toUtf8Impl(start, size) return unsafeStringToUtf8(start, size)
} }
@SymbolName("Kotlin_String_toUtf8")
internal external fun String.toUtf8Impl(start: Int, size: Int) : ByteArray
/** /**
* Converts a [String] into an UTF-8 array. * Converts a [String] into an UTF-8 array.
* @throws [IllegalCharacterConversionException] if the input is invalid. * @throws [IllegalCharacterConversionException] if the input is invalid.
@@ -95,8 +96,9 @@ public fun String.toUtf8OrThrow() : ByteArray {
public fun String.toUtf8OrThrow(start: Int = 0, size: Int = this.length) : ByteArray { public fun String.toUtf8OrThrow(start: Int = 0, size: Int = this.length) : ByteArray {
checkBoundsIndexes(start, start + size, this.length) checkBoundsIndexes(start, start + size, this.length)
try { try {
return toUtf8OrThrowImpl(start, size) return unsafeStringToUtf8OrThrow(start, size)
} catch (e: CharacterCodingException) { } catch (e: CharacterCodingException) {
@Suppress("DEPRECATION")
throw IllegalCharacterConversionException() throw IllegalCharacterConversionException()
} }
} }
@@ -110,11 +112,20 @@ internal fun checkBoundsIndexes(startIndex: Int, endIndex: Int, size: Int) {
} }
} }
@SymbolName("Kotlin_String_toUtf8OrThrow") @SymbolName("Kotlin_ByteArray_unsafeStringFromUtf8")
internal external fun String.toUtf8OrThrowImpl(start: Int, size: Int) : ByteArray internal external fun ByteArray.unsafeStringFromUtf8(start: Int, size: Int) : String
@SymbolName("Kotlin_String_fromCharArray") @SymbolName("Kotlin_ByteArray_unsafeStringFromUtf8OrThrow")
internal external fun fromCharArray(array: CharArray, start: Int, size: Int) : String internal external fun ByteArray.unsafeStringFromUtf8OrThrow(start: Int, size: Int) : String
@SymbolName("Kotlin_String_unsafeStringToUtf8")
internal external fun String.unsafeStringToUtf8(start: Int, size: Int) : ByteArray
@SymbolName("Kotlin_String_unsafeStringToUtf8OrThrow")
internal external fun String.unsafeStringToUtf8OrThrow(start: Int, size: Int) : ByteArray
@SymbolName("Kotlin_String_unsafeStringFromCharArray")
internal external fun unsafeStringFromCharArray(array: CharArray, start: Int, size: Int) : String
@SymbolName("Kotlin_StringBuilder_insertString") @SymbolName("Kotlin_StringBuilder_insertString")
internal external fun insertString(array: CharArray, start: Int, value: String): Int internal external fun insertString(array: CharArray, start: Int, value: String): Int
@@ -162,7 +162,7 @@ class NumberConverter {
if (k == expt - 1) if (k == expt - 1)
formattedDecimal[charPos++] = '0' formattedDecimal[charPos++] = '0'
formattedDecimal[charPos++] = 'E' formattedDecimal[charPos++] = 'E'
return fromCharArray(formattedDecimal, 0, charPos) + expt.toString() return unsafeStringFromCharArray(formattedDecimal, 0, charPos) + expt.toString()
} }
private fun freeFormat(): String { private fun freeFormat(): String {
@@ -192,7 +192,7 @@ class NumberConverter {
k-- k--
u = if (getCount < setCount) uArray[getCount++] else -1 u = if (getCount < setCount) uArray[getCount++] else -1
} while (u != -1 || k >= -1) } while (u != -1 || k >= -1)
return fromCharArray(formattedDecimal, 0, charPos) return unsafeStringFromCharArray(formattedDecimal, 0, charPos)
} }
private fun bigIntDigitGeneratorInstImpl(f: Long, e: Int, private fun bigIntDigitGeneratorInstImpl(f: Long, e: Int,
@@ -53,12 +53,12 @@ actual class StringBuilder private constructor (
actual override fun subSequence(startIndex: Int, endIndex: Int): CharSequence = substring(startIndex, endIndex) actual override fun subSequence(startIndex: Int, endIndex: Int): CharSequence = substring(startIndex, endIndex)
override fun toString(): String = fromCharArray(array, 0, _length) override fun toString(): String = unsafeStringFromCharArray(array, 0, _length)
fun substring(startIndex: Int, endIndex: Int): String { fun substring(startIndex: Int, endIndex: Int): String {
checkInsertIndex(startIndex) checkInsertIndex(startIndex)
checkInsertIndexFrom(endIndex, startIndex) checkInsertIndexFrom(endIndex, startIndex)
return fromCharArray(array, startIndex, endIndex - startIndex) return unsafeStringFromCharArray(array, startIndex, endIndex - startIndex)
} }
fun trimToSize() { fun trimToSize() {
+11 -11
View File
@@ -205,7 +205,7 @@ public actual fun CharSequence.repeat(n: Int): String {
else -> { else -> {
when (length) { when (length) {
0 -> "" 0 -> ""
1 -> this[0].let { char -> fromCharArray(CharArray(n) { char }, 0, n) } 1 -> this[0].let { char -> CharArray(n) { char }.concatToString() }
else -> { else -> {
val sb = StringBuilder(n * length) val sb = StringBuilder(n * length)
for (i in 1..n) { for (i in 1..n) {
@@ -221,7 +221,7 @@ public actual fun CharSequence.repeat(n: Int): String {
/** /**
* Converts the characters in the specified array to a string. * Converts the characters in the specified array to a string.
*/ */
public actual fun String(chars: CharArray): String = fromCharArray(chars, 0, chars.size) public actual fun String(chars: CharArray): String = chars.concatToString()
/** /**
* Converts the characters from a portion of the specified array to a string. * Converts the characters from a portion of the specified array to a string.
@@ -233,14 +233,14 @@ public actual fun String(chars: CharArray, offset: Int, length: Int): String {
if (offset < 0 || length < 0 || offset + length > chars.size) if (offset < 0 || length < 0 || offset + length > chars.size)
throw ArrayIndexOutOfBoundsException() throw ArrayIndexOutOfBoundsException()
return fromCharArray(chars, offset, length) return unsafeStringFromCharArray(chars, offset, length)
} }
/** /**
* Concatenates characters in this [CharArray] into a String. * Concatenates characters in this [CharArray] into a String.
*/ */
@SinceKotlin("1.3") @SinceKotlin("1.3")
public actual fun CharArray.concatToString(): String = fromCharArray(this, 0, size) public actual fun CharArray.concatToString(): String = unsafeStringFromCharArray(this, 0, size)
/** /**
* Concatenates characters in this [CharArray] or its subrange into a String. * Concatenates characters in this [CharArray] or its subrange into a String.
@@ -254,7 +254,7 @@ public actual fun CharArray.concatToString(): String = fromCharArray(this, 0, si
@SinceKotlin("1.3") @SinceKotlin("1.3")
public actual fun CharArray.concatToString(startIndex: Int, endIndex: Int): String { public actual fun CharArray.concatToString(startIndex: Int, endIndex: Int): String {
checkBoundsIndexes(startIndex, endIndex, size) checkBoundsIndexes(startIndex, endIndex, size)
return fromCharArray(this, startIndex, endIndex - startIndex) return unsafeStringFromCharArray(this, startIndex, endIndex - startIndex)
} }
/** /**
@@ -278,7 +278,7 @@ public actual fun String.toCharArray(startIndex: Int, endIndex: Int): CharArray
* Malformed byte sequences are replaced by the replacement char `\uFFFD`. * Malformed byte sequences are replaced by the replacement char `\uFFFD`.
*/ */
@SinceKotlin("1.3") @SinceKotlin("1.3")
public actual fun ByteArray.decodeToString(): String = stringFromUtf8Impl(0, size) public actual fun ByteArray.decodeToString(): String = unsafeStringFromUtf8(0, size)
/** /**
* Decodes a string from the bytes in UTF-8 encoding in this array or its subrange. * Decodes a string from the bytes in UTF-8 encoding in this array or its subrange.
@@ -295,9 +295,9 @@ public actual fun ByteArray.decodeToString(): String = stringFromUtf8Impl(0, siz
public actual fun ByteArray.decodeToString(startIndex: Int, endIndex: Int, throwOnInvalidSequence: Boolean): String { public actual fun ByteArray.decodeToString(startIndex: Int, endIndex: Int, throwOnInvalidSequence: Boolean): String {
checkBoundsIndexes(startIndex, endIndex, size) checkBoundsIndexes(startIndex, endIndex, size)
return if (throwOnInvalidSequence) return if (throwOnInvalidSequence)
stringFromUtf8OrThrowImpl(startIndex, endIndex - startIndex) unsafeStringFromUtf8OrThrow(startIndex, endIndex - startIndex)
else else
stringFromUtf8Impl(startIndex, endIndex - startIndex) unsafeStringFromUtf8(startIndex, endIndex - startIndex)
} }
/** /**
@@ -306,7 +306,7 @@ public actual fun ByteArray.decodeToString(startIndex: Int, endIndex: Int, throw
* Any malformed char sequence is replaced by the replacement byte sequence. * Any malformed char sequence is replaced by the replacement byte sequence.
*/ */
@SinceKotlin("1.3") @SinceKotlin("1.3")
public actual fun String.encodeToByteArray(): ByteArray = toUtf8Impl(0, length) public actual fun String.encodeToByteArray(): ByteArray = unsafeStringToUtf8(0, length)
/** /**
* Encodes this string or its substring to an array of bytes in UTF-8 encoding. * Encodes this string or its substring to an array of bytes in UTF-8 encoding.
@@ -323,9 +323,9 @@ public actual fun String.encodeToByteArray(): ByteArray = toUtf8Impl(0, length)
public actual fun String.encodeToByteArray(startIndex: Int, endIndex: Int, throwOnInvalidSequence: Boolean): ByteArray { public actual fun String.encodeToByteArray(startIndex: Int, endIndex: Int, throwOnInvalidSequence: Boolean): ByteArray {
checkBoundsIndexes(startIndex, endIndex, length) checkBoundsIndexes(startIndex, endIndex, length)
return if (throwOnInvalidSequence) return if (throwOnInvalidSequence)
toUtf8OrThrowImpl(startIndex, endIndex - startIndex) unsafeStringToUtf8OrThrow(startIndex, endIndex - startIndex)
else else
toUtf8Impl(startIndex, endIndex - startIndex) unsafeStringToUtf8(startIndex, endIndex - startIndex)
} }
@SymbolName("Kotlin_String_compareToIgnoreCase") @SymbolName("Kotlin_String_compareToIgnoreCase")
@@ -39,7 +39,7 @@ actual fun readFile(fileName: String): String {
actual fun Double.format(decimalNumber: Int): String { actual fun Double.format(decimalNumber: Int): String {
var buffer = ByteArray(1024) var buffer = ByteArray(1024)
snprintf(buffer.refTo(0), buffer.size.toULong(), "%.${decimalNumber}f", this) snprintf(buffer.refTo(0), buffer.size.toULong(), "%.${decimalNumber}f", this)
return buffer.stringFromUtf8() return buffer.toKString()
} }
actual fun writeToFile(fileName: String, text: String) { actual fun writeToFile(fileName: String, text: String) {
@@ -91,7 +91,7 @@ class CUrl(url: String, user: String? = null, password: String? = null, followLo
fun CPointer<ByteVar>.toKString(length: Int): String { fun CPointer<ByteVar>.toKString(length: Int): String {
val bytes = this.readBytes(length) val bytes = this.readBytes(length)
return bytes.stringFromUtf8() return bytes.toKString()
} }
fun collectResponse(buffer: CPointer<ByteVar>?, size: size_t, nitems: size_t, userdata: COpaquePointer?): size_t { fun collectResponse(buffer: CPointer<ByteVar>?, size: size_t, nitems: size_t, userdata: COpaquePointer?): size_t {