Support all digit chars on JS and Native #KT-46002

This commit is contained in:
Abduqodiri Qurbonzoda
2021-04-12 19:26:17 +03:00
parent 8481f4a9d2
commit 54994a289f
9 changed files with 112 additions and 73 deletions
@@ -78,17 +78,6 @@ OBJ_GETTER(utf8ToUtf16, const char* rawString, size_t rawStringLength) {
RETURN_RESULT_OF(utf8ToUtf16Impl<utf8::with_replacement::utf8to16>, 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;
@@ -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
}
@@ -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.
@@ -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
}
@@ -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
}
@@ -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 }
+4
View File
@@ -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
*/
+14 -10
View File
@@ -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)
}
}
}
@@ -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()
}