From 662787b12bbdbc6945db059f567f2a26745d6276 Mon Sep 17 00:00:00 2001 From: Abduqodiri Qurbonzoda Date: Tue, 8 Dec 2020 11:51:16 +0300 Subject: [PATCH] Straighten Char-to-code and Char-to-digit conversions out #KT-23451 --- libraries/stdlib/api/js-v1/kotlin.kt | 15 ++ libraries/stdlib/api/js-v1/kotlin.text.kt | 24 +++ libraries/stdlib/api/js/kotlin.kt | 14 ++ libraries/stdlib/api/js/kotlin.text.kt | 24 +++ .../js-ir-minimal-for-test/build.gradle.kts | 9 +- .../src/smallRuntimeMissingDeclarations.kt | 21 ++- libraries/stdlib/js-ir/builtins/Char.kt | 7 +- .../stdlib/js-v1/src/kotlin/charCode_js-v1.kt | 19 +++ .../stdlib/jvm/src/kotlin/CharCodeJVM.kt | 19 +++ .../stdlib/samples/test/samples/text/char.kt | 75 +++++++++ libraries/stdlib/src/kotlin/CharCode.kt | 50 ++++++ libraries/stdlib/src/kotlin/text/Char.kt | 121 ++++++++++++++ libraries/stdlib/test/text/CharTest.kt | 149 ++++++++++++++++++ libraries/stdlib/test/text/StringTest.kt | 18 +-- libraries/stdlib/wasm/src/kotlin/CharCode.kt | 16 ++ .../kotlin-stdlib-runtime-merged.txt | 6 + nj2k/testData/newJ2k/issues/kt-836.kt | 2 +- 17 files changed, 575 insertions(+), 14 deletions(-) create mode 100644 libraries/stdlib/js-v1/src/kotlin/charCode_js-v1.kt create mode 100644 libraries/stdlib/jvm/src/kotlin/CharCodeJVM.kt create mode 100644 libraries/stdlib/src/kotlin/CharCode.kt create mode 100644 libraries/stdlib/test/text/CharTest.kt create mode 100644 libraries/stdlib/wasm/src/kotlin/CharCode.kt diff --git a/libraries/stdlib/api/js-v1/kotlin.kt b/libraries/stdlib/api/js-v1/kotlin.kt index 81cbf3de16f..8590a2ab5bc 100644 --- a/libraries/stdlib/api/js-v1/kotlin.kt +++ b/libraries/stdlib/api/js-v1/kotlin.kt @@ -1,3 +1,8 @@ +@kotlin.ExperimentalStdlibApi +@kotlin.SinceKotlin(version = "1.4") +@kotlin.internal.InlineOnly +public val kotlin.Char.code: kotlin.Int { get; } + @kotlin.SinceKotlin(version = "1.2") @kotlin.internal.InlineOnly public val kotlin.reflect.KProperty0<*>.isInitialized: kotlin.Boolean { get; } @@ -5,7 +10,17 @@ public val kotlin.reflect.KProperty0<*>.isInitialized: kotlin.Boolean { get; } @kotlin.SinceKotlin(version = "1.4") public val kotlin.Throwable.suppressedExceptions: kotlin.collections.List { get; } +@kotlin.ExperimentalStdlibApi +@kotlin.SinceKotlin(version = "1.4") @kotlin.internal.InlineOnly +public inline fun Char(code: kotlin.Int): kotlin.Char + +/*∆*/ @kotlin.ExperimentalStdlibApi +/*∆*/ @kotlin.SinceKotlin(version = "1.4") +@kotlin.internal.InlineOnly +/*∆*/ public inline fun Char(code: kotlin.UShort): kotlin.Char +/*∆*/ +/*∆*/ @kotlin.internal.InlineOnly public inline fun TODO(): kotlin.Nothing @kotlin.internal.InlineOnly diff --git a/libraries/stdlib/api/js-v1/kotlin.text.kt b/libraries/stdlib/api/js-v1/kotlin.text.kt index dd3c70a7674..902a3261170 100644 --- a/libraries/stdlib/api/js-v1/kotlin.text.kt +++ b/libraries/stdlib/api/js-v1/kotlin.text.kt @@ -183,6 +183,30 @@ public inline fun kotlin.text.StringBuilder.deleteAt(index: kotlin.Int): kotlin. @kotlin.WasExperimental(markerClass = {kotlin.ExperimentalStdlibApi::class}) public inline fun kotlin.text.StringBuilder.deleteRange(startIndex: kotlin.Int, endIndex: kotlin.Int): kotlin.text.StringBuilder +@kotlin.ExperimentalStdlibApi +@kotlin.SinceKotlin(version = "1.4") +public fun kotlin.Int.digitToChar(): kotlin.Char + +@kotlin.ExperimentalStdlibApi +@kotlin.SinceKotlin(version = "1.4") +public fun kotlin.Int.digitToChar(radix: kotlin.Int): kotlin.Char + +@kotlin.ExperimentalStdlibApi +@kotlin.SinceKotlin(version = "1.4") +public fun kotlin.Char.digitToInt(): kotlin.Int + +@kotlin.ExperimentalStdlibApi +@kotlin.SinceKotlin(version = "1.4") +public fun kotlin.Char.digitToInt(radix: kotlin.Int): kotlin.Int + +@kotlin.ExperimentalStdlibApi +@kotlin.SinceKotlin(version = "1.4") +public fun kotlin.Char.digitToIntOrNull(): kotlin.Int? + +@kotlin.ExperimentalStdlibApi +@kotlin.SinceKotlin(version = "1.4") +public fun kotlin.Char.digitToIntOrNull(radix: kotlin.Int): kotlin.Int? + public fun kotlin.CharSequence.drop(n: kotlin.Int): kotlin.CharSequence public fun kotlin.String.drop(n: kotlin.Int): kotlin.String diff --git a/libraries/stdlib/api/js/kotlin.kt b/libraries/stdlib/api/js/kotlin.kt index 3c0cc877ba5..bf897984b45 100644 --- a/libraries/stdlib/api/js/kotlin.kt +++ b/libraries/stdlib/api/js/kotlin.kt @@ -1,3 +1,8 @@ +@kotlin.ExperimentalStdlibApi +@kotlin.SinceKotlin(version = "1.4") +@kotlin.internal.InlineOnly +public val kotlin.Char.code: kotlin.Int { get; } + @kotlin.SinceKotlin(version = "1.2") @kotlin.internal.InlineOnly public val kotlin.reflect.KProperty0<*>.isInitialized: kotlin.Boolean { get; } @@ -5,6 +10,11 @@ public val kotlin.reflect.KProperty0<*>.isInitialized: kotlin.Boolean { get; } @kotlin.SinceKotlin(version = "1.4") public val kotlin.Throwable.suppressedExceptions: kotlin.collections.List { get; } +@kotlin.ExperimentalStdlibApi +@kotlin.SinceKotlin(version = "1.4") +@kotlin.internal.InlineOnly +public inline fun Char(code: kotlin.Int): kotlin.Char + @kotlin.internal.InlineOnly public inline fun TODO(): kotlin.Nothing @@ -899,6 +909,10 @@ public final class ByteArray { } public final class Char : kotlin.Comparable { +/*∆*/ @kotlin.ExperimentalStdlibApi +/*∆*/ @kotlin.SinceKotlin(version = "1.4") +/*∆*/ public constructor Char(code: kotlin.UShort) +/*∆*/ public open override operator fun compareTo(other: kotlin.Char): kotlin.Int public final operator fun dec(): kotlin.Char diff --git a/libraries/stdlib/api/js/kotlin.text.kt b/libraries/stdlib/api/js/kotlin.text.kt index dd3c70a7674..902a3261170 100644 --- a/libraries/stdlib/api/js/kotlin.text.kt +++ b/libraries/stdlib/api/js/kotlin.text.kt @@ -183,6 +183,30 @@ public inline fun kotlin.text.StringBuilder.deleteAt(index: kotlin.Int): kotlin. @kotlin.WasExperimental(markerClass = {kotlin.ExperimentalStdlibApi::class}) public inline fun kotlin.text.StringBuilder.deleteRange(startIndex: kotlin.Int, endIndex: kotlin.Int): kotlin.text.StringBuilder +@kotlin.ExperimentalStdlibApi +@kotlin.SinceKotlin(version = "1.4") +public fun kotlin.Int.digitToChar(): kotlin.Char + +@kotlin.ExperimentalStdlibApi +@kotlin.SinceKotlin(version = "1.4") +public fun kotlin.Int.digitToChar(radix: kotlin.Int): kotlin.Char + +@kotlin.ExperimentalStdlibApi +@kotlin.SinceKotlin(version = "1.4") +public fun kotlin.Char.digitToInt(): kotlin.Int + +@kotlin.ExperimentalStdlibApi +@kotlin.SinceKotlin(version = "1.4") +public fun kotlin.Char.digitToInt(radix: kotlin.Int): kotlin.Int + +@kotlin.ExperimentalStdlibApi +@kotlin.SinceKotlin(version = "1.4") +public fun kotlin.Char.digitToIntOrNull(): kotlin.Int? + +@kotlin.ExperimentalStdlibApi +@kotlin.SinceKotlin(version = "1.4") +public fun kotlin.Char.digitToIntOrNull(radix: kotlin.Int): kotlin.Int? + public fun kotlin.CharSequence.drop(n: kotlin.Int): kotlin.CharSequence public fun kotlin.String.drop(n: kotlin.Int): kotlin.String diff --git a/libraries/stdlib/js-ir-minimal-for-test/build.gradle.kts b/libraries/stdlib/js-ir-minimal-for-test/build.gradle.kts index 62fc169279d..9084fcb3aee 100644 --- a/libraries/stdlib/js-ir-minimal-for-test/build.gradle.kts +++ b/libraries/stdlib/js-ir-minimal-for-test/build.gradle.kts @@ -16,7 +16,13 @@ val commonMainSources by task { val fullCommonMainSources = tasks.getByPath(":kotlin-stdlib-js-ir:commonMainSources") exclude( listOf( - "libraries/stdlib/unsigned/**", + "libraries/stdlib/unsigned/src/kotlin/UByteArray.kt", + "libraries/stdlib/unsigned/src/kotlin/UIntArray.kt", + "libraries/stdlib/unsigned/src/kotlin/ULongArray.kt", + "libraries/stdlib/unsigned/src/kotlin/UMath.kt", + "libraries/stdlib/unsigned/src/kotlin/UNumbers.kt", + "libraries/stdlib/unsigned/src/kotlin/UShortArray.kt", + "libraries/stdlib/unsigned/src/kotlin/UStrings.kt", "libraries/stdlib/common/src/generated/_Arrays.kt", "libraries/stdlib/common/src/generated/_Collections.kt", "libraries/stdlib/common/src/generated/_Comparisons.kt", @@ -35,7 +41,6 @@ val commonMainSources by task { "libraries/stdlib/common/src/kotlin/collections/**", "libraries/stdlib/common/src/kotlin/ioH.kt", "libraries/stdlib/src/kotlin/collections/**", - "libraries/stdlib/src/kotlin/experimental/bitwiseOperations.kt", "libraries/stdlib/src/kotlin/properties/Delegates.kt", "libraries/stdlib/src/kotlin/random/URandom.kt", "libraries/stdlib/src/kotlin/text/**", diff --git a/libraries/stdlib/js-ir-minimal-for-test/src/smallRuntimeMissingDeclarations.kt b/libraries/stdlib/js-ir-minimal-for-test/src/smallRuntimeMissingDeclarations.kt index da299aa19bc..dc796d2dc96 100644 --- a/libraries/stdlib/js-ir-minimal-for-test/src/smallRuntimeMissingDeclarations.kt +++ b/libraries/stdlib/js-ir-minimal-for-test/src/smallRuntimeMissingDeclarations.kt @@ -32,4 +32,23 @@ public actual fun Throwable.addSuppressed(exception: Throwable) { } public actual val Throwable.suppressedExceptions: List - get() = TODO("Not implemented in reduced runtime") \ No newline at end of file + get() = TODO("Not implemented in reduced runtime") + +/** + * Returns a string representation of this [Long] value in the specified [radix]. + * + * @throws IllegalArgumentException when [radix] is not a valid radix for number to string conversion. + */ +@SinceKotlin("1.2") +public fun Long.toString(radix: Int): String = + this.toStringImpl(checkRadix(radix)) + +/** + * Checks whether the given [radix] is valid radix for string to number and number to string conversion. + */ +internal fun checkRadix(radix: Int): Int { + if (radix !in 2..36) { + throw IllegalArgumentException("radix $radix was not in valid range 2..36") + } + return radix +} \ No newline at end of file diff --git a/libraries/stdlib/js-ir/builtins/Char.kt b/libraries/stdlib/js-ir/builtins/Char.kt index cadd8e8ddbb..73681f6eadc 100644 --- a/libraries/stdlib/js-ir/builtins/Char.kt +++ b/libraries/stdlib/js-ir/builtins/Char.kt @@ -12,7 +12,12 @@ package kotlin // TODO: KT-35100 //@Suppress("NON_PUBLIC_PRIMARY_CONSTRUCTOR_OF_INLINE_CLASS") //public inline class Char internal constructor (val value: Int) : Comparable { -public class Char internal constructor(private val value: Int) : Comparable { +public class Char +@OptIn(ExperimentalUnsignedTypes::class) +@ExperimentalStdlibApi +@SinceKotlin("1.4") +constructor(code: UShort) : Comparable { + private val value: Int = code.toInt() /** * Compares this value with the specified value for order. diff --git a/libraries/stdlib/js-v1/src/kotlin/charCode_js-v1.kt b/libraries/stdlib/js-v1/src/kotlin/charCode_js-v1.kt new file mode 100644 index 00000000000..fbd42636080 --- /dev/null +++ b/libraries/stdlib/js-v1/src/kotlin/charCode_js-v1.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package kotlin + +/** + * Creates a Char with the specified [code]. + * + * @sample samples.text.Chars.charFromCode + */ +@OptIn(ExperimentalUnsignedTypes::class) +@ExperimentalStdlibApi +@SinceKotlin("1.4") +@kotlin.internal.InlineOnly +public actual inline fun Char(code: UShort): Char { + return code.toInt().toChar() +} diff --git a/libraries/stdlib/jvm/src/kotlin/CharCodeJVM.kt b/libraries/stdlib/jvm/src/kotlin/CharCodeJVM.kt new file mode 100644 index 00000000000..fbd42636080 --- /dev/null +++ b/libraries/stdlib/jvm/src/kotlin/CharCodeJVM.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package kotlin + +/** + * Creates a Char with the specified [code]. + * + * @sample samples.text.Chars.charFromCode + */ +@OptIn(ExperimentalUnsignedTypes::class) +@ExperimentalStdlibApi +@SinceKotlin("1.4") +@kotlin.internal.InlineOnly +public actual inline fun Char(code: UShort): Char { + return code.toInt().toChar() +} diff --git a/libraries/stdlib/samples/test/samples/text/char.kt b/libraries/stdlib/samples/test/samples/text/char.kt index 493cf8815a6..777c320858f 100644 --- a/libraries/stdlib/samples/test/samples/text/char.kt +++ b/libraries/stdlib/samples/test/samples/text/char.kt @@ -160,4 +160,79 @@ class Chars { assertTrue('a'.equals('A', true)) } + @Sample + fun charFromCode() { + val codes = listOf(48, 65, 122, 946) + assertPrints(codes.map { Char(it) }, "[0, A, z, β]") + assertPrints(codes.map { Char(it.toUShort()) }, "[0, A, z, β]") + + assertFails { Char(-1) } + assertPrints(Char(UShort.MIN_VALUE), "\u0000") + assertFails { Char(1_000_000) } + assertPrints(Char(UShort.MAX_VALUE), "\uFFFF") + } + + @Sample + fun code() { + val string = "0Azβ" + assertPrints(string.map { it.code }, "[48, 65, 122, 946]") + } + + @Sample + fun digitToInt() { + assertPrints('5'.digitToInt(), "5") + assertPrints('3'.digitToInt(radix = 8), "3") + assertPrints('A'.digitToInt(radix = 16), "10") + assertPrints('k'.digitToInt(radix = 36), "20") + + // radix argument should be in 2..36 + assertFails { '0'.digitToInt(radix = 1) } + assertFails { '1'.digitToInt(radix = 100) } + // only 0 and 1 digits are valid for binary numbers + assertFails { '5'.digitToInt(radix = 2) } + // radix = 10 is used by default + assertFails { 'A'.digitToInt() } + // symbol '+' is not a digit in any radix + assertFails { '+'.digitToInt() } + // Only Latin letters are valid for digits greater than 9. + assertFails { 'β'.digitToInt(radix = 36) } + } + + @Sample + fun digitToIntOrNull() { + assertPrints('5'.digitToIntOrNull(), "5") + assertPrints('3'.digitToIntOrNull(radix = 8), "3") + assertPrints('A'.digitToIntOrNull(radix = 16), "10") + assertPrints('K'.digitToIntOrNull(radix = 36), "20") + + // radix argument should be in 2..36 + assertFails { '0'.digitToIntOrNull(radix = 1) } + assertFails { '1'.digitToIntOrNull(radix = 100) } + // only 0 and 1 digits are valid for binary numbers + assertPrints('5'.digitToIntOrNull(radix = 2), "null") + // radix = 10 is used by default + assertPrints('A'.digitToIntOrNull(), "null") + // symbol '+' is not a digit in any radix + assertPrints('+'.digitToIntOrNull(), "null") + // Only Latin letters are valid for digits greater than 9. + assertPrints('β'.digitToIntOrNull(radix = 36), "null") + } + + @Sample + fun digitToChar() { + assertPrints(5.digitToChar(), "5") + assertPrints(3.digitToChar(radix = 8), "3") + assertPrints(10.digitToChar(radix = 16), "A") + assertPrints(20.digitToChar(radix = 36), "K") + + // radix argument should be in 2..36 + assertFails { 0.digitToChar(radix = 1) } + assertFails { 1.digitToChar(radix = 100) } + // only 0 and 1 digits are valid for binary numbers + assertFails { 5.digitToChar(radix = 2) } + // radix = 10 is used by default + assertFails { 10.digitToChar() } + // a negative integer is not a digit in any radix + assertFails { (-1).digitToChar() } + } } \ No newline at end of file diff --git a/libraries/stdlib/src/kotlin/CharCode.kt b/libraries/stdlib/src/kotlin/CharCode.kt new file mode 100644 index 00000000000..71370cc05b3 --- /dev/null +++ b/libraries/stdlib/src/kotlin/CharCode.kt @@ -0,0 +1,50 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package kotlin + + +/** + * Creates a Char with the specified [code], or throws an exception if the [code] is out of `Char.MIN_VALUE.code..Char.MAX_VALUE.code`. + * + * If the program that calls this function is written in a way that only valid [code] is passed as the argument, + * using the overload that takes a [UShort] argument is preferable (`Char(intValue.toUShort())`). + * That overload doesn't check validity of the argument, and may improve program performance when the function is called routinely inside a loop. + * + * @sample samples.text.Chars.charFromCode + */ +@OptIn(ExperimentalUnsignedTypes::class) +@ExperimentalStdlibApi +@SinceKotlin("1.4") +@kotlin.internal.InlineOnly +public inline fun Char(code: Int): Char { + if (code < Char.MIN_VALUE.code || code > Char.MAX_VALUE.code) { + throw IllegalArgumentException("Invalid Char code: $code") + } + return Char(code.toUShort()) +} + +/** + * Creates a Char with the specified [code]. + * + * @sample samples.text.Chars.charFromCode + */ +@OptIn(ExperimentalUnsignedTypes::class) +@ExperimentalStdlibApi +@SinceKotlin("1.4") +@Suppress("NO_ACTUAL_FOR_EXPECT") +public expect fun Char(code: UShort): Char + +/** + * Returns the code of this Char. + * + * Code of a Char is the value it was constructed with, and the UTF-16 code unit corresponding to this Char. + * + * @sample samples.text.Chars.code + */ +@ExperimentalStdlibApi +@SinceKotlin("1.4") +@kotlin.internal.InlineOnly +public inline val Char.code: Int get() = this.toInt() diff --git a/libraries/stdlib/src/kotlin/text/Char.kt b/libraries/stdlib/src/kotlin/text/Char.kt index e8de0008915..a71cf8999f2 100644 --- a/libraries/stdlib/src/kotlin/text/Char.kt +++ b/libraries/stdlib/src/kotlin/text/Char.kt @@ -8,6 +8,127 @@ package kotlin.text +/** + * Returns the numeric value of the decimal digit that this Char represents. + * Throws an exception if this Char is not a valid decimal digit. + * + * A Char is considered to represent a decimal digit if the Char is one of the ASCII decimal digits '0' through '9'. + * In this case, `this.code - '0'.code` is returned. + * + * @sample samples.text.Chars.digitToInt + */ +@ExperimentalStdlibApi +@SinceKotlin("1.4") +public fun Char.digitToInt(): Int { + if (this in '0'..'9') { + return this - '0' + } + throw IllegalArgumentException("Char $this is not a decimal digit") +} + +/** + * Returns the numeric value of the digit that this Char represents in the specified [radix]. + * Throws an exception if the [radix] is not in the range `2..36` or if this Char is not a valid digit in the specified [radix]. + * + * A Char is considered to represent a digit in the specified [radix] if at least one of the following is true: + * - The Char is one of the ASCII decimal digits '0' through '9' and its [code] is less than `radix + '0'.code`. In this case, `this.code - '0'.code` 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. + * + * @sample samples.text.Chars.digitToInt + */ +@ExperimentalStdlibApi +@SinceKotlin("1.4") +public fun Char.digitToInt(radix: Int): Int { + return digitToIntOrNull(radix) ?: throw IllegalArgumentException("Char $this is not a digit in the given radix=$radix") +} + +/** + * + * Returns the numeric value of the decimal digit that this Char represents, or `null` if this Char is not a valid decimal digit. + * + * A Char is considered to represent a decimal digit if the Char is one of the ASCII decimal digits '0' through '9'. + * In this case, `this.code - '0'.code` is returned. + * + * @sample samples.text.Chars.digitToIntOrNull + */ +@ExperimentalStdlibApi +@SinceKotlin("1.4") +public fun Char.digitToIntOrNull(): Int? { + if (this in '0'..'9') { + return this - '0' + } + return null +} + +/** + * Returns the numeric value of the digit that this Char represents in the specified [radix], or `null` if this Char is not a valid digit in the specified [radix]. + * Throws an exception if the [radix] is not in the range `2..36`. + * + * A Char is considered to represent a digit in the specified [radix] if at least one of the following is true: + * - The Char is one of the ASCII decimal digits '0' through '9' and its [code] is less than `radix + '0'.code`. In this case, `this.code - '0'.code` 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. + * + * @sample samples.text.Chars.digitToIntOrNull + */ +@ExperimentalStdlibApi +@SinceKotlin("1.4") +public fun Char.digitToIntOrNull(radix: Int): Int? { + if (radix !in 2..36) { + throw IllegalArgumentException("Invalid radix: $radix. Valid radix values are in range 2..36") + } + if (this in '0'..'9') { + val digit = this - '0' + return if (digit < radix) digit else null + } + val a = if (this <= 'Z') 'A' else 'a' + val digit = 10 + (this - a) + return if (digit in 10 until radix) digit else null +} + +/** + * Returns the Char that represents this decimal digit. + * Throws an exception if this value is not in the range `0..9`. + * + * If this value is in `0..9`, the decimal digit Char with code `'0'.code + this` is returned. + * + * @sample samples.text.Chars.digitToChar + */ +@ExperimentalStdlibApi +@SinceKotlin("1.4") +public fun Int.digitToChar(): Char { + if (this in 0..9) { + return '0' + this + } + throw IllegalArgumentException("Int $this is not a decimal digit") +} + +/** + * Returns the Char that represents this numeric digit value in the specified [radix]. + * Throws an exception if the [radix] is not in the range `2..36` or if this value is not in the range `0 until radix`. + * + * If this value is less than `10`, the decimal digit Char with code `'0'.code + this` is returned. + * Otherwise, the uppercase Latin letter with code `'A'.code + this - 10` is returned. + * + * @sample samples.text.Chars.digitToChar + */ +@ExperimentalStdlibApi +@SinceKotlin("1.4") +public fun Int.digitToChar(radix: Int): Char { + if (radix !in 2..36) { + throw IllegalArgumentException("Invalid radix: $radix. Valid radix values are in range 2..36") + } + if (this < 0 || this >= radix) { + throw IllegalArgumentException("Digit $this does not represent a valid digit in radix $radix") + } + return if (this < 10) { + '0' + this + } else { + 'A' + this - 10 + } +} + /** * Converts this character to lower case using Unicode mapping rules of the invariant locale. */ diff --git a/libraries/stdlib/test/text/CharTest.kt b/libraries/stdlib/test/text/CharTest.kt new file mode 100644 index 00000000000..d3c4cfe9765 --- /dev/null +++ b/libraries/stdlib/test/text/CharTest.kt @@ -0,0 +1,149 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package test.text + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFails + +class CharTest { + + @Test + fun charFromIntCode() { + val codes = listOf(48, 65, 122, 946, '+'.code, 'Ö'.code, 0xFFFC) + assertEquals("0Azβ+Ö\uFFFC", codes.map { Char(it) }.joinToString(separator = "")) + + assertFails { Char(-1) } + assertFails { Char(0x1_0000) } + assertFails { Char(1_000_000) } + } + + @Test + fun charFromUShortCode() { + val codes = listOf(48, 65, 122, 946, '+'.code, 'Ö'.code, 0xFFFC) + assertEquals("0Azβ+Ö\uFFFC", codes.map { Char(it.toUShort()) }.joinToString(separator = "")) + + assertEquals('\u0000', Char(UShort.MIN_VALUE)) + assertEquals('\uFFFF', Char(UShort.MAX_VALUE)) + assertEquals('\uFFFF', Char((-1).toUShort())) + assertEquals('\u0000', Char(0x1_0000.toUShort())) + } + + @Test + fun code() { + val codes = listOf(48, 65, 122, 946, '+'.code, 'Ö'.code, 0xFFFC) + val chars = "0Azβ+Ö\uFFFC" + assertEquals(codes, chars.map { it.code }) + assertEquals(0, Char.MIN_VALUE.code) + assertEquals(0xFFFF, Char.MAX_VALUE.code) + } + + @Test + fun digitToInt() { + fun testEquals(expected: Int, receiver: Char, radix: Int) { + if (radix == 10) { + assertEquals(expected, receiver.digitToInt()) + assertEquals(expected, receiver.digitToIntOrNull()) + } + assertEquals(expected, receiver.digitToInt(radix)) + assertEquals(expected, receiver.digitToIntOrNull(radix)) + } + + fun testFails(receiver: Char, radix: Int) { + if (radix == 10) { + assertFails { receiver.digitToInt() } + assertEquals(null, receiver.digitToIntOrNull()) + } + assertFails { receiver.digitToInt(radix) } + assertEquals(null, receiver.digitToIntOrNull(radix)) + } + + for (char in '0'..'9') { + val digit = char - '0' + + for (radix in (digit + 1).coerceAtLeast(2)..36) { + testEquals(digit, char, radix) + } + for (radix in 2..digit) { + testFails(char, radix) + } + } + + for (char in 'A'..'Z') { + val digit = 10 + (char - 'A') + val lower = char.toLowerCase() + + 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) + } + } + + assertFails { '0'.digitToInt(radix = 37) } + assertFails { '0'.digitToIntOrNull(radix = 37) } + assertFails { '0'.digitToInt(radix = 1) } + assertFails { '0'.digitToIntOrNull(radix = 1) } + + testFails('0' - 1, radix = 10) + testFails('9' + 1, radix = 10) + testFails('β', radix = 36) + testFails('+', radix = 36) + } + + @Test + fun digitToChar() { + fun testEquals(expected: Char, receiver: Int, radix: Int) { + if (radix == 10) { + assertEquals(expected, receiver.digitToChar()) + } + assertEquals(expected, receiver.digitToChar(radix)) + } + + fun testFails(receiver: Int, radix: Int) { + if (radix == 10) { + assertFails { receiver.digitToChar() } + } + assertFails { receiver.digitToChar(radix) } + } + + for (int in 0..9) { + val digit = '0' + int + + for (radix in (int + 1).coerceAtLeast(2)..36) { + testEquals(digit, int, radix) + } + for (radix in 2..int) { + testFails(int, radix) + } + + testFails(int, radix = 37) + } + + for (int in 10..35) { + val digit = 'A' + (int - 10) + + for (radix in int + 1..36) { + testEquals(digit, int, radix) + } + for (radix in 2..int) { + testFails(int, radix) + } + + testFails(int, radix = 37) + } + + assertFails { 0.digitToChar(radix = 37) } + assertFails { 0.digitToChar(radix = 1) } + + testFails(-1, radix = 10) + testFails(100, radix = 36) + testFails(100, radix = 110) + } +} diff --git a/libraries/stdlib/test/text/StringTest.kt b/libraries/stdlib/test/text/StringTest.kt index ba26d28d94c..9c828503da1 100644 --- a/libraries/stdlib/test/text/StringTest.kt +++ b/libraries/stdlib/test/text/StringTest.kt @@ -908,7 +908,7 @@ class StringTest { fun testIgnoreCase(chars: String) { for ((i, c) in chars.withIndex()) { - val message = "Char: $c (${c.toInt()})" + val message = "Char: $c (${c.code})" val expectOneReplaced = chars.replaceRange(i..i, "_") val expectAllReplaced = "_".repeat(chars.length) assertEquals(expectOneReplaced, chars.replace(c, '_'), message) @@ -1238,7 +1238,7 @@ class StringTest { assertEquals(listOf(), arg1("").map { it.isAsciiUpperCase() }) - assertEquals(listOf(97, 98, 99), arg1("abc").map { it.toInt() }) + assertEquals(listOf(97, 98, 99), arg1("abc").map { it.code }) } @Test fun mapTo() = withOneCharSequenceArg { arg1 -> @@ -1258,7 +1258,7 @@ class StringTest { assertEquals(arrayListOf(), result3) val result4 = arrayListOf() - val return4 = arg1("abc").mapTo(result4, { it.toInt() }) + val return4 = arg1("abc").mapTo(result4, { it.code }) assertEquals(result4, return4) assertEquals(arrayListOf(97, 98, 99), result4) } @@ -1416,18 +1416,18 @@ class StringTest { @Test fun runningReduce() = withOneCharSequenceArg { arg1 -> for (size in 0 until 4) { - val expected = listOf(0, 1, 3, 6).take(size).map { it.toChar() } - val source = arg1((0.toChar() until size.toChar()).joinToString(separator = "")) - assertEquals(expected, source.runningReduce { acc, e -> acc + e.toInt() }) + val expected = listOf(0, 1, 3, 6).take(size).map { Char(it) } + val source = arg1((Char(0) until Char(size)).joinToString(separator = "")) + assertEquals(expected, source.runningReduce { acc, e -> acc + e.code }) } } @Test fun runningReduceIndexed() = withOneCharSequenceArg { arg1 -> for (size in 0 until 4) { - val expected = listOf(0, 1, 6, 27).take(size).map { it.toChar() } - val source = arg1((0.toChar() until size.toChar()).joinToString(separator = "")) - assertEquals(expected, source.runningReduceIndexed { index, acc, e -> (index * (acc.toInt() + e.toInt())).toChar() }) + val expected = listOf(0, 1, 6, 27).take(size).map { Char(it) } + val source = arg1((Char(0) until Char(size)).joinToString(separator = "")) + assertEquals(expected, source.runningReduceIndexed { index, acc, e -> Char(index * (acc.code + e.code)) }) } } diff --git a/libraries/stdlib/wasm/src/kotlin/CharCode.kt b/libraries/stdlib/wasm/src/kotlin/CharCode.kt new file mode 100644 index 00000000000..4ab00680387 --- /dev/null +++ b/libraries/stdlib/wasm/src/kotlin/CharCode.kt @@ -0,0 +1,16 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package kotlin + +/** + * Creates a Char with the specified [code]. + * + * @sample samples.text.Chars.charFromCode + */ +@OptIn(ExperimentalUnsignedTypes::class) +@ExperimentalStdlibApi +@SinceKotlin("1.4") +public actual fun Char(code: UShort): Char = TODO("Wasm stdlib: CharCode") diff --git a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt index ea7934ff5cb..2499ffd1cc3 100644 --- a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt +++ b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt @@ -5107,6 +5107,12 @@ public final class kotlin/text/CharDirectionality$Companion { public final class kotlin/text/CharsKt { public static final fun checkRadix (I)I + public static final fun digitToChar (I)C + public static final fun digitToChar (II)C + public static final fun digitToInt (C)I + public static final fun digitToInt (CI)I + public static final fun digitToIntOrNull (C)Ljava/lang/Integer; + public static final fun digitToIntOrNull (CI)Ljava/lang/Integer; public static final fun equals (CCZ)Z public static synthetic fun equals$default (CCZILjava/lang/Object;)Z public static final fun getCategory (C)Lkotlin/text/CharCategory; diff --git a/nj2k/testData/newJ2k/issues/kt-836.kt b/nj2k/testData/newJ2k/issues/kt-836.kt index bf68d38f78d..f0403447028 100644 --- a/nj2k/testData/newJ2k/issues/kt-836.kt +++ b/nj2k/testData/newJ2k/issues/kt-836.kt @@ -4,7 +4,7 @@ import java.io.Serializable class Language(protected var code: String) : Serializable { override fun toString(): String { - return code + return this.code } }