diff --git a/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Utils.kt b/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Utils.kt index 75f544ef09a..ec2acd311f9 100644 --- a/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Utils.kt +++ b/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Utils.kt @@ -392,6 +392,12 @@ private class CString(val bytes: ByteArray): CValues() { public val String.cstr: CValues get() = CString(encodeToUtf8(this)) +/** + * @return the value of zero-terminated UTF-8-encoded C string constructed from given [kotlin.String]. + */ +public val String.utf8: CValues + get() = CString(encodeToUtf8(this)) + /** * Convert this list of Kotlin strings to C array of C strings, * allocating memory for the array and C strings with given [AutofreeScope]. @@ -407,7 +413,7 @@ public fun Array.toCStringArray(autofreeScope: AutofreeScope): CPointer< autofreeScope.allocArrayOf(this.map { it.cstr.getPointer(autofreeScope) }) -private class WCString(val chars: CharArray): CValues() { +private class U16CString(val chars: CharArray): CValues() { override val size get() = 2 * (chars.size + 1) override val align get() = 2 @@ -425,16 +431,61 @@ private class WCString(val chars: CharArray): CValues() { } } +/** + * @return the value of zero-terminated UTF-16-encoded C string constructed from given [kotlin.String]. + */ public val String.wcstr: CValues - get() = WCString(this.toCharArray()) + get() = U16CString(this.toCharArray()) /** - * TODO: should the name of the function reflect the encoding? - * + * @return the value of zero-terminated UTF-16-encoded C string constructed from given [kotlin.String]. + */ +public val String.utf16: CValues + get() = U16CString(this.toCharArray()) + +private class U32CString(val chars: CharArray): CValues() { + override val size get() = 4 * (chars.size + 1) + + override val align get() = 4 + + // Optimization to avoid unneeded virtual calls in base class implementation. + override fun getPointer(scope: AutofreeScope): CPointer { + return place(interpretCPointer(scope.alloc(size, align).rawPtr)!!) + } + + override fun place(placement: CPointer): CPointer { + var indexIn = 0 + var indexOut = 0 + while (indexIn < chars.size) { + var value = chars[indexIn++].toInt() + if (value >= 0xd800 && value < 0xdc00) { + // Surrogate pair. + if (indexIn >= chars.size - 1) throw IllegalArgumentException() + indexIn++ + val next = chars[indexIn].toInt() + if (next < 0xdc00 || next >= 0xe000) throw IllegalArgumentException() + value = 0x10000 + ((value and 0x3ff) shl 10) + (next and 0x3ff) + } + nativeMemUtils.putInt((placement + indexOut)!!.pointed, value) + indexOut++ + } + nativeMemUtils.putInt((placement + indexOut)!!.pointed, 0) + return placement + } +} + +/** + * @return the value of zero-terminated UTF-32-encoded C string constructed from given [kotlin.String]. + */ +public val String.utf32: CValues + get() = U32CString(this.toCharArray()) + + +// TODO: optimize +/** * @return the [kotlin.String] decoded from given zero-terminated UTF-8-encoded C string. */ -// TODO: optimize -public fun CPointer.toKString(): String { +public fun CPointer.toKStringFromUtf8(): String { val nativeBytes = this var length = 0 @@ -447,6 +498,62 @@ public fun CPointer.toKString(): String { return decodeFromUtf8(bytes) } +/** + * @return the [kotlin.String] decoded from given zero-terminated UTF-8-encoded C string. + */ +public fun CPointer.toKString(): String = this.toKStringFromUtf8() + +/** + * @return the [kotlin.String] decoded from given zero-terminated UTF-16-encoded C string. + */ +public fun CPointer.toKStringFromUtf16(): String { + val nativeBytes = this + + var length = 0 + while (nativeBytes[length] != 0.toShort()) { + ++length + } + val chars = CharArray(length) + var index = 0 + while (index < length) { + chars[index] = nativeBytes[index].toChar() + ++index + } + return String(chars) +} + +/** + * @return the [kotlin.String] decoded from given zero-terminated UTF-32-encoded C string. + */ +public fun CPointer.toKStringFromUtf32(): String { + val nativeBytes = this + + var fromIndex = 0 + var toIndex = 0 + while (true) { + val value = nativeBytes[fromIndex++] + toIndex++ + if (value == 0) break + if (value >= 0x10000 && value <= 0x10ffff) { + toIndex++ + } + } + val length = toIndex + val chars = CharArray(length) + fromIndex = 0 + toIndex = 0 + while (toIndex < length) { + var value = nativeBytes[fromIndex++] + if (value >= 0x10000 && value <= 0x10ffff) { + chars[toIndex++] = (((value - 0x10000) shr 10) or 0xd800).toChar() + chars[toIndex++] = (((value - 0x10000) and 0x3ff) or 0xdc00).toChar() + } else { + chars[toIndex++] = value.toChar() + } + } + return String(chars) +} + public class MemScope : ArenaBase() { val memScope: MemScope diff --git a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/NativeMem.kt b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/NativeMem.kt index f9d73548f35..4478e87d34c 100644 --- a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/NativeMem.kt +++ b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/NativeMem.kt @@ -127,38 +127,26 @@ internal object nativeMemUtils { } } -fun CPointer.toKString(): String { - val nativeBytes = this - - var length = 0 - while (nativeBytes[length] != 0.toShort()) { - ++length - } - val bytes = CharArray(length) - var index = 0 - while (index < length) { - bytes[index] = nativeBytes[index].toChar() - ++index - } - return String(bytes) -} - -fun CPointer.toKString(): String { +public fun CPointer.toKStringFromUtf16(): String { val nativeBytes = this var length = 0 while (nativeBytes[length] != 0.toUShort()) { ++length } - val bytes = CharArray(length) + val chars = kotlin.CharArray(length) var index = 0 while (index < length) { - bytes[index] = nativeBytes[index].toShort().toChar() + chars[index] = nativeBytes[index].toShort().toChar() ++index } - return String(bytes) + return String(chars) } +public fun CPointer.toKString(): String = this.toKStringFromUtf16() + +public fun CPointer.toKString(): String = this.toKStringFromUtf16() + @SymbolName("Kotlin_interop_malloc") private external fun malloc(size: Long, align: Int): NativePtr