diff --git a/kotlin-native/runtime/src/main/cpp/KString.cpp b/kotlin-native/runtime/src/main/cpp/KString.cpp index b0e16395114..23cbf3ea286 100644 --- a/kotlin-native/runtime/src/main/cpp/KString.cpp +++ b/kotlin-native/runtime/src/main/cpp/KString.cpp @@ -78,17 +78,6 @@ OBJ_GETTER(utf8ToUtf16, const char* rawString, size_t rawStringLength) { RETURN_RESULT_OF(utf8ToUtf16Impl, rawString, end, charCount); } -constexpr KChar digitKeys[] = { - 0x30, 0x41, 0x61, 0x660, 0x6f0, 0x966, 0x9e6, 0xa66, 0xae6, 0xb66, 0xbe7, 0xc66, 0xce6, 0xd66, 0xe50, 0xed0, 0xf20, 0x1040, 0x1369, 0x17e0, - 0x1810, 0xff10, 0xff21, 0xff41 -}; - -constexpr KChar digitValues[] = { - 0x39, 0x30, 0x5a, 0x37, 0x7a, 0x57, 0x669, 0x660, 0x6f9, 0x6f0, 0x96f, 0x966, 0x9ef, 0x9e6, 0xa6f, 0xa66, 0xaef, 0xae6, 0xb6f, 0xb66, - 0xbef, 0xbe6, 0xc6f, 0xc66, 0xcef, 0xce6, 0xd6f, 0xd66, 0xe59, 0xe50, 0xed9, 0xed0, 0xf29, 0xf20, 0x1049, 0x1040, 0x1371, 0x1368, 0x17e9, 0x17e0, - 0x1819, 0x1810, 0xff19, 0xff10, 0xff3a, 0xff17, 0xff5a, 0xff37 -}; - } // namespace extern "C" { @@ -319,36 +308,6 @@ KBoolean Kotlin_Char_isLowSurrogate(KChar ch) { return ((ch & 0xfc00) == 0xdc00); } -constexpr KInt digits[] = { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, - -1, -1, -1, -1, -1, -1, -1, - 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, - 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, - 30, 31, 32, 33, 34, 35, - -1, -1, -1, -1, -1, -1, - 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, - 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, - 30, 31, 32, 33, 34, 35 -}; - -// Based on Apache Harmony implementation. -// Radix check is performed on the Kotlin side. -KInt Kotlin_Char_digitOfChecked(KChar ch, KInt radix) { - - KInt result = -1; - if (ch >= 0x30 /* 0 */ && ch <= 0x7a /* z */) { - result = digits[ch - 0x30]; - } else { - int index = -1; - index = binarySearchRange(digitKeys, ARRAY_SIZE(digitKeys), ch); - if (index >= 0 && ch <= digitValues[index * 2]) { - result = ch - digitValues[index * 2 + 1]; - } - } - if (result >= radix) return -1; - return result; -} - KInt Kotlin_String_indexOfChar(KString thiz, KChar ch, KInt fromIndex) { if (fromIndex < 0) { fromIndex = 0; diff --git a/kotlin-native/runtime/src/main/kotlin/generated/_DigitChars.kt b/kotlin-native/runtime/src/main/kotlin/generated/_DigitChars.kt index 729b79f15d4..609e3f37910 100644 --- a/kotlin-native/runtime/src/main/kotlin/generated/_DigitChars.kt +++ b/kotlin-native/runtime/src/main/kotlin/generated/_DigitChars.kt @@ -17,6 +17,10 @@ private val rangeStart = intArrayOf( 0x1810, 0x1946, 0x19d0, 0x1a80, 0x1a90, 0x1b50, 0x1bb0, 0x1c40, 0x1c50, 0xa620, 0xa8d0, 0xa900, 0xa9d0, 0xa9f0, 0xaa50, 0xabf0, 0xff10, ) +/** + * Returns the index of the largest element in [array] smaller or equal to the specified [needle], + * or -1 if [needle] is smaller than the smallest element in [array]. + */ internal fun binarySearchRange(array: IntArray, needle: Int): Int { var bottom = 0 var top = array.size - 1 @@ -35,12 +39,20 @@ internal fun binarySearchRange(array: IntArray, needle: Int): Int { return middle - (if (needle < value) 1 else 0) } +/** + * Returns an integer from 0..9 indicating the digit this character represents, + * or -1 if this character is not a digit. + */ +internal fun Char.digitToIntImpl(): Int { + val ch = this.code + val index = binarySearchRange(rangeStart, ch) + val diff = ch - rangeStart[index] + return if (diff < 10) diff else -1 +} + /** * Returns `true` if this character is a digit. */ internal fun Char.isDigitImpl(): Boolean { - val ch = this.code - val index = binarySearchRange(rangeStart, ch) - val high = rangeStart[index] + 9 - return ch <= high + return digitToIntImpl() >= 0 } diff --git a/kotlin-native/runtime/src/main/kotlin/kotlin/text/Char.kt b/kotlin-native/runtime/src/main/kotlin/kotlin/text/Char.kt index 70f6fc3c4dc..b671caa9d58 100644 --- a/kotlin-native/runtime/src/main/kotlin/kotlin/text/Char.kt +++ b/kotlin-native/runtime/src/main/kotlin/kotlin/text/Char.kt @@ -241,11 +241,26 @@ external public actual fun Char.isHighSurrogate(): Boolean @GCUnsafeCall("Kotlin_Char_isLowSurrogate") external public actual fun Char.isLowSurrogate(): Boolean +@SharedImmutable +private val digits = intArrayOf( + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + -1, -1, -1, -1, -1, -1, -1, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, + -1, -1, -1, -1, -1, -1, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35 +) -internal actual fun digitOf(char: Char, radix: Int): Int = digitOfChecked(char, checkRadix(radix)) - -@GCUnsafeCall("Kotlin_Char_digitOfChecked") -external internal fun digitOfChecked(char: Char, radix: Int): Int +internal actual fun digitOf(char: Char, radix: Int): Int = when { + char >= '0' && char <= 'z' -> digits[char - '0'] + char < '\u0080' -> -1 + char >= '\uFF21' && char <= '\uFF3A' -> char - '\uFF21' + 10 // full-width latin capital letter + char >= '\uFF41' && char <= '\uFF5A' -> char - '\uFF41' + 10 // full-width latin small letter + else -> char.digitToIntImpl() +}.let { if (it >= radix) -1 else it } /** * Returns the Unicode general category of this character. diff --git a/libraries/stdlib/js-ir/src/generated/_DigitChars.kt b/libraries/stdlib/js-ir/src/generated/_DigitChars.kt index 1e50f993c0a..8e92a193847 100644 --- a/libraries/stdlib/js-ir/src/generated/_DigitChars.kt +++ b/libraries/stdlib/js-ir/src/generated/_DigitChars.kt @@ -18,6 +18,10 @@ private object Digit { ) } +/** + * Returns the index of the largest element in [array] smaller or equal to the specified [needle], + * or -1 if [needle] is smaller than the smallest element in [array]. + */ internal fun binarySearchRange(array: IntArray, needle: Int): Int { var bottom = 0 var top = array.size - 1 @@ -36,12 +40,20 @@ internal fun binarySearchRange(array: IntArray, needle: Int): Int { return middle - (if (needle < value) 1 else 0) } +/** + * Returns an integer from 0..9 indicating the digit this character represents, + * or -1 if this character is not a digit. + */ +internal fun Char.digitToIntImpl(): Int { + val ch = this.code + val index = binarySearchRange(Digit.rangeStart, ch) + val diff = ch - Digit.rangeStart[index] + return if (diff < 10) diff else -1 +} + /** * Returns `true` if this character is a digit. */ internal fun Char.isDigitImpl(): Boolean { - val ch = this.code - val index = binarySearchRange(Digit.rangeStart, ch) - val high = Digit.rangeStart[index] + 9 - return ch <= high + return digitToIntImpl() >= 0 } diff --git a/libraries/stdlib/js/src/generated/_DigitChars.kt b/libraries/stdlib/js/src/generated/_DigitChars.kt index 1e50f993c0a..8e92a193847 100644 --- a/libraries/stdlib/js/src/generated/_DigitChars.kt +++ b/libraries/stdlib/js/src/generated/_DigitChars.kt @@ -18,6 +18,10 @@ private object Digit { ) } +/** + * Returns the index of the largest element in [array] smaller or equal to the specified [needle], + * or -1 if [needle] is smaller than the smallest element in [array]. + */ internal fun binarySearchRange(array: IntArray, needle: Int): Int { var bottom = 0 var top = array.size - 1 @@ -36,12 +40,20 @@ internal fun binarySearchRange(array: IntArray, needle: Int): Int { return middle - (if (needle < value) 1 else 0) } +/** + * Returns an integer from 0..9 indicating the digit this character represents, + * or -1 if this character is not a digit. + */ +internal fun Char.digitToIntImpl(): Int { + val ch = this.code + val index = binarySearchRange(Digit.rangeStart, ch) + val diff = ch - Digit.rangeStart[index] + return if (diff < 10) diff else -1 +} + /** * Returns `true` if this character is a digit. */ internal fun Char.isDigitImpl(): Boolean { - val ch = this.code - val index = binarySearchRange(Digit.rangeStart, ch) - val high = Digit.rangeStart[index] + 9 - return ch <= high + return digitToIntImpl() >= 0 } diff --git a/libraries/stdlib/js/src/kotlin/text/numberConversions.kt b/libraries/stdlib/js/src/kotlin/text/numberConversions.kt index b6d32d838c5..dfc4b16dd4e 100644 --- a/libraries/stdlib/js/src/kotlin/text/numberConversions.kt +++ b/libraries/stdlib/js/src/kotlin/text/numberConversions.kt @@ -152,5 +152,8 @@ internal actual fun digitOf(char: Char, radix: Int): Int = when { char >= '0' && char <= '9' -> char - '0' char >= 'A' && char <= 'Z' -> char - 'A' + 10 char >= 'a' && char <= 'z' -> char - 'a' + 10 - else -> -1 + char < '\u0080' -> -1 + char >= '\uFF21' && char <= '\uFF3A' -> char - '\uFF21' + 10 // full-width latin capital letter + char >= '\uFF41' && char <= '\uFF5A' -> char - '\uFF41' + 10 // full-width latin small letter + else -> char.digitToIntImpl() }.let { if (it >= radix) -1 else it } diff --git a/libraries/stdlib/src/kotlin/text/Char.kt b/libraries/stdlib/src/kotlin/text/Char.kt index 4b2ac865efa..86befea97d6 100644 --- a/libraries/stdlib/src/kotlin/text/Char.kt +++ b/libraries/stdlib/src/kotlin/text/Char.kt @@ -33,6 +33,8 @@ public fun Char.digitToInt(): Int { * - [isDigit] is `true` for the Char and the Unicode decimal digit value of the character is less than the specified [radix]. In this case the decimal digit value is returned. * - The Char is one of the uppercase Latin letters 'A' through 'Z' and its [code] is less than `radix + 'A'.code - 10`. In this case, `this.code - 'A'.code + 10` is returned. * - The Char is one of the lowercase Latin letters 'a' through 'z' and its [code] is less than `radix + 'a'.code - 10`. In this case, `this.code - 'a'.code + 10` is returned. + * - The Char is one of the fullwidth Latin capital letters '\uFF21' through '\uFF3A' and its [code] is less than `radix + 0xFF21 - 10`. In this case, `this.code - 0xFF21 + 10` is returned. + * - The Char is one of the fullwidth Latin small letters '\uFF41' through '\uFF5A' and its [code] is less than `radix + 0xFF41 - 10`. In this case, `this.code - 0xFF41 + 10` is returned. * * @sample samples.text.Chars.digitToInt */ @@ -65,6 +67,8 @@ public fun Char.digitToIntOrNull(): Int? { * - [isDigit] is `true` for the Char and the Unicode decimal digit value of the character is less than the specified [radix]. In this case the decimal digit value is returned. * - The Char is one of the uppercase Latin letters 'A' through 'Z' and its [code] is less than `radix + 'A'.code - 10`. In this case, `this.code - 'A'.code + 10` is returned. * - The Char is one of the lowercase Latin letters 'a' through 'z' and its [code] is less than `radix + 'a'.code - 10`. In this case, `this.code - 'a'.code + 10` is returned. + * - The Char is one of the fullwidth Latin capital letters '\uFF21' through '\uFF3A' and its [code] is less than `radix + 0xFF21 - 10`. In this case, `this.code - 0xFF21 + 10` is returned. + * - The Char is one of the fullwidth Latin small letters '\uFF41' through '\uFF5A' and its [code] is less than `radix + 0xFF41 - 10`. In this case, `this.code - 0xFF41 + 10` is returned. * * @sample samples.text.Chars.digitToIntOrNull */ diff --git a/libraries/stdlib/test/text/CharTest.kt b/libraries/stdlib/test/text/CharTest.kt index 38324600e4d..1c485ef6ac5 100644 --- a/libraries/stdlib/test/text/CharTest.kt +++ b/libraries/stdlib/test/text/CharTest.kt @@ -80,17 +80,21 @@ class CharTest { } } - for (char in 'A'..'Z') { - val digit = 10 + (char - 'A') - val lower = char.lowercaseChar() + val letterRanges = listOf('A'..'Z', '\uFF21'..'\uFF3A') - for (radix in digit + 1..36) { - testEquals(digit, char, radix) - testEquals(digit, lower, radix) - } - for (radix in 2..digit) { - testFails(char, radix) - testFails(lower, radix) + for (range in letterRanges) { + for (char in range) { + val digit = 10 + (char - range.first) + val lower = char.lowercaseChar() + + for (radix in digit + 1..36) { + testEquals(digit, char, radix) + testEquals(digit, lower, radix) + } + for (radix in 2..digit) { + testFails(char, radix) + testFails(lower, radix) + } } } diff --git a/libraries/tools/kotlin-stdlib-gen/src/generators/unicode/ranges/writers/DigitRangesWriter.kt b/libraries/tools/kotlin-stdlib-gen/src/generators/unicode/ranges/writers/DigitRangesWriter.kt index 37732c41a65..5fc717d9c54 100644 --- a/libraries/tools/kotlin-stdlib-gen/src/generators/unicode/ranges/writers/DigitRangesWriter.kt +++ b/libraries/tools/kotlin-stdlib-gen/src/generators/unicode/ranges/writers/DigitRangesWriter.kt @@ -21,10 +21,16 @@ internal class DigitRangesWriter(private val strategy: RangesWritingStrategy) : writer.appendLine() writer.appendLine(binarySearchRange()) writer.appendLine() + writer.appendLine(digitToIntImpl()) + writer.appendLine() writer.appendLine(isDigitImpl()) } private fun binarySearchRange(): String = """ + /** + * Returns the index of the largest element in [array] smaller or equal to the specified [needle], + * or -1 if [needle] is smaller than the smallest element in [array]. + */ internal fun binarySearchRange(array: IntArray, needle: Int): Int { var bottom = 0 var top = array.size - 1 @@ -44,17 +50,29 @@ internal class DigitRangesWriter(private val strategy: RangesWritingStrategy) : } """.trimIndent() - private fun isDigitImpl(): String { + private fun digitToIntImpl(): String { val rangeStart = strategy.rangeRef("rangeStart") + return """ + /** + * Returns an integer from 0..9 indicating the digit this character represents, + * or -1 if this character is not a digit. + */ + internal fun Char.digitToIntImpl(): Int { + val ch = this.code + val index = binarySearchRange($rangeStart, ch) + val diff = ch - $rangeStart[index] + return if (diff < 10) diff else -1 + } + """.trimIndent() + } + + private fun isDigitImpl(): String { return """ /** * Returns `true` if this character is a digit. */ internal fun Char.isDigitImpl(): Boolean { - val ch = this.code - val index = binarySearchRange($rangeStart, ch) - val high = $rangeStart[index] + 9 - return ch <= high + return digitToIntImpl() >= 0 } """.trimIndent() }