From 2266cd4631df509ada8f505c08bb8059012d486c Mon Sep 17 00:00:00 2001 From: Abduqodiri Qurbonzoda Date: Mon, 1 Mar 2021 21:49:11 +0300 Subject: [PATCH] Commonize Char.titlecaseChar() and Char.titlecase() #KT-44369 --- libraries/stdlib/api/js-v1/kotlin.text.kt | 8 +++++ libraries/stdlib/api/js/kotlin.text.kt | 8 +++++ .../generated/_OneToManyTitlecaseMappings.kt | 21 ++++++++++++ .../js-ir-minimal-for-test/build.gradle.kts | 1 + .../js-ir/src/generated/_TitlecaseMappings.kt | 26 +++++++++++++++ .../js/src/generated/_TitlecaseMappings.kt | 26 +++++++++++++++ libraries/stdlib/js/src/kotlin/text/char.kt | 13 ++++++++ .../stdlib/jvm/src/kotlin/text/CharJVM.kt | 22 +------------ libraries/stdlib/src/kotlin/text/Char.kt | 27 ++++++++++++++++ libraries/stdlib/wasm/src/kotlin/Text.kt | 13 ++++++++ .../generators/unicode/GenerateUnicodeData.kt | 7 ++-- .../builders/OneToManyMappingsBuilder.kt | 6 ++-- .../OneToManyTitlecaseMappingsBuilder.kt | 22 ++++++++++++- .../OneToManyTitlecaseMappingsWriter.kt | 32 +++++++++++-------- .../writers/TitlecaseMappingsWriter.kt | 5 ++- 15 files changed, 195 insertions(+), 42 deletions(-) create mode 100644 libraries/stdlib/common/src/generated/_OneToManyTitlecaseMappings.kt create mode 100644 libraries/stdlib/js-ir/src/generated/_TitlecaseMappings.kt create mode 100644 libraries/stdlib/js/src/generated/_TitlecaseMappings.kt diff --git a/libraries/stdlib/api/js-v1/kotlin.text.kt b/libraries/stdlib/api/js-v1/kotlin.text.kt index 7b8c42eb0a2..96e9a2687dc 100644 --- a/libraries/stdlib/api/js-v1/kotlin.text.kt +++ b/libraries/stdlib/api/js-v1/kotlin.text.kt @@ -912,6 +912,14 @@ public inline fun kotlin.CharSequence.takeWhile(predicate: (kotlin.Char) -> kotl public inline fun kotlin.String.takeWhile(predicate: (kotlin.Char) -> kotlin.Boolean): kotlin.String +@kotlin.SinceKotlin(version = "1.4") +@kotlin.ExperimentalStdlibApi +public fun kotlin.Char.titlecase(): kotlin.String + +@kotlin.SinceKotlin(version = "1.4") +@kotlin.ExperimentalStdlibApi +public fun kotlin.Char.titlecaseChar(): kotlin.Char + @kotlin.Deprecated(message = "Use Kotlin compiler 1.4 to avoid deprecation warning.") @kotlin.DeprecatedSinceKotlin(hiddenSince = "1.4") @kotlin.internal.InlineOnly diff --git a/libraries/stdlib/api/js/kotlin.text.kt b/libraries/stdlib/api/js/kotlin.text.kt index 7b8c42eb0a2..96e9a2687dc 100644 --- a/libraries/stdlib/api/js/kotlin.text.kt +++ b/libraries/stdlib/api/js/kotlin.text.kt @@ -912,6 +912,14 @@ public inline fun kotlin.CharSequence.takeWhile(predicate: (kotlin.Char) -> kotl public inline fun kotlin.String.takeWhile(predicate: (kotlin.Char) -> kotlin.Boolean): kotlin.String +@kotlin.SinceKotlin(version = "1.4") +@kotlin.ExperimentalStdlibApi +public fun kotlin.Char.titlecase(): kotlin.String + +@kotlin.SinceKotlin(version = "1.4") +@kotlin.ExperimentalStdlibApi +public fun kotlin.Char.titlecaseChar(): kotlin.Char + @kotlin.Deprecated(message = "Use Kotlin compiler 1.4 to avoid deprecation warning.") @kotlin.DeprecatedSinceKotlin(hiddenSince = "1.4") @kotlin.internal.InlineOnly diff --git a/libraries/stdlib/common/src/generated/_OneToManyTitlecaseMappings.kt b/libraries/stdlib/common/src/generated/_OneToManyTitlecaseMappings.kt new file mode 100644 index 00000000000..c347962bb9c --- /dev/null +++ b/libraries/stdlib/common/src/generated/_OneToManyTitlecaseMappings.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2010-2021 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.text + +// +// NOTE: THIS FILE IS AUTO-GENERATED by the GenerateUnicodeData.kt +// See: https://github.com/JetBrains/kotlin/tree/master/libraries/stdlib +// + +// 10 mappings totally +@OptIn(ExperimentalStdlibApi::class) +internal fun Char.titlecaseImpl(): String { + val uppercase = uppercase() + if (uppercase.length > 1) { + return if (this == '\u0149') uppercase else uppercase[0] + uppercase.substring(1).lowercase() + } + return titlecaseChar().toString() +} 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 c6ec3258978..b2f59c74311 100644 --- a/libraries/stdlib/js-ir-minimal-for-test/build.gradle.kts +++ b/libraries/stdlib/js-ir-minimal-for-test/build.gradle.kts @@ -27,6 +27,7 @@ val commonMainSources by task { "libraries/stdlib/common/src/generated/_Collections.kt", "libraries/stdlib/common/src/generated/_Comparisons.kt", "libraries/stdlib/common/src/generated/_Maps.kt", + "libraries/stdlib/common/src/generated/_OneToManyTitlecaseMappings.kt", "libraries/stdlib/common/src/generated/_Sequences.kt", "libraries/stdlib/common/src/generated/_Sets.kt", "libraries/stdlib/common/src/generated/_Strings.kt", diff --git a/libraries/stdlib/js-ir/src/generated/_TitlecaseMappings.kt b/libraries/stdlib/js-ir/src/generated/_TitlecaseMappings.kt new file mode 100644 index 00000000000..f5c60fa79bc --- /dev/null +++ b/libraries/stdlib/js-ir/src/generated/_TitlecaseMappings.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2010-2021 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.text + +// +// NOTE: THIS FILE IS AUTO-GENERATED by the GenerateUnicodeData.kt +// See: https://github.com/JetBrains/kotlin/tree/master/libraries/stdlib +// + +// 4 ranges totally +@OptIn(ExperimentalStdlibApi::class) +internal fun Char.titlecaseCharImpl(): Char { + val code = this.toInt() + // Letters repeating sequence and code of the Lt is a multiple of 3, e.g. <DŽ, Dž, dž> + if (code in 0x01c4..0x01cc || code in 0x01f1..0x01f3) { + return (3 * ((code + 1) / 3)).toChar() + } + // Lower case letters whose title case mapping equivalent is equal to the original letter + if (code in 0x10d0..0x10fa || code in 0x10fd..0x10ff) { + return this + } + return uppercaseChar() +} \ No newline at end of file diff --git a/libraries/stdlib/js/src/generated/_TitlecaseMappings.kt b/libraries/stdlib/js/src/generated/_TitlecaseMappings.kt new file mode 100644 index 00000000000..f5c60fa79bc --- /dev/null +++ b/libraries/stdlib/js/src/generated/_TitlecaseMappings.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2010-2021 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.text + +// +// NOTE: THIS FILE IS AUTO-GENERATED by the GenerateUnicodeData.kt +// See: https://github.com/JetBrains/kotlin/tree/master/libraries/stdlib +// + +// 4 ranges totally +@OptIn(ExperimentalStdlibApi::class) +internal fun Char.titlecaseCharImpl(): Char { + val code = this.toInt() + // Letters repeating sequence and code of the Lt is a multiple of 3, e.g. <DŽ, Dž, dž> + if (code in 0x01c4..0x01cc || code in 0x01f1..0x01f3) { + return (3 * ((code + 1) / 3)).toChar() + } + // Lower case letters whose title case mapping equivalent is equal to the original letter + if (code in 0x10d0..0x10fa || code in 0x10fd..0x10ff) { + return this + } + return uppercaseChar() +} \ No newline at end of file diff --git a/libraries/stdlib/js/src/kotlin/text/char.kt b/libraries/stdlib/js/src/kotlin/text/char.kt index 442e0f178cf..f90bd4b817e 100644 --- a/libraries/stdlib/js/src/kotlin/text/char.kt +++ b/libraries/stdlib/js/src/kotlin/text/char.kt @@ -79,6 +79,19 @@ public actual fun Char.uppercaseChar(): Char { @kotlin.internal.InlineOnly public actual inline fun Char.uppercase(): String = toString().asDynamic().toUpperCase() as String +/** + * Converts this character to title case using Unicode mapping rules of the invariant locale. + * + * This function performs one-to-one character mapping. + * To support one-to-many character mapping use the [titlecase] function. + * If this character has no mapping equivalent, the result of calling [uppercaseChar] is returned. + * + * @sample samples.text.Chars.titlecase + */ +@SinceKotlin("1.4") +@ExperimentalStdlibApi +public actual fun Char.titlecaseChar(): Char = titlecaseCharImpl() + /** * Returns `true` if this character is a Unicode high-surrogate code unit (also known as leading-surrogate code unit). */ diff --git a/libraries/stdlib/jvm/src/kotlin/text/CharJVM.kt b/libraries/stdlib/jvm/src/kotlin/text/CharJVM.kt index 309616a0e4d..98cfbefd36d 100644 --- a/libraries/stdlib/jvm/src/kotlin/text/CharJVM.kt +++ b/libraries/stdlib/jvm/src/kotlin/text/CharJVM.kt @@ -238,27 +238,7 @@ public inline fun Char.toTitleCase(): Char = titlecaseChar() @SinceKotlin("1.4") @ExperimentalStdlibApi @kotlin.internal.InlineOnly -public inline fun Char.titlecaseChar(): Char = Character.toTitleCase(this) - -/** - * Converts this character to title case using Unicode mapping rules of the invariant locale. - * - * This function supports one-to-many character mapping, thus the length of the returned string can be greater than one. - * For example, `'\uFB00'.titlecase()` returns `"\u0046\u0066"`, - * where `'\uFB00'` is the LATIN SMALL LIGATURE FF character (`ff`). - * If this character has no title case mapping, the result of [uppercase] is returned instead. - * - * @sample samples.text.Chars.titlecase - */ -@SinceKotlin("1.4") -@ExperimentalStdlibApi -public fun Char.titlecase(): String { - val uppercase = uppercase() - if (uppercase.length > 1) { - return if (this == '\u0149') uppercase else uppercase[0] + uppercase.substring(1).lowercase() - } - return titlecaseChar().toString() -} +public actual inline fun Char.titlecaseChar(): Char = Character.toTitleCase(this) /** * Converts this character to title case using Unicode mapping rules of the specified [locale]. diff --git a/libraries/stdlib/src/kotlin/text/Char.kt b/libraries/stdlib/src/kotlin/text/Char.kt index 6cf68a28206..5e5f37a0e78 100644 --- a/libraries/stdlib/src/kotlin/text/Char.kt +++ b/libraries/stdlib/src/kotlin/text/Char.kt @@ -193,6 +193,33 @@ public expect fun Char.uppercaseChar(): Char @ExperimentalStdlibApi public expect fun Char.uppercase(): String +/** + * Converts this character to title case using Unicode mapping rules of the invariant locale. + * + * This function performs one-to-one character mapping. + * To support one-to-many character mapping use the [titlecase] function. + * If this character has no mapping equivalent, the result of calling [uppercaseChar] is returned. + * + * @sample samples.text.Chars.titlecase + */ +@SinceKotlin("1.4") +@ExperimentalStdlibApi +public expect fun Char.titlecaseChar(): Char + +/** + * Converts this character to title case using Unicode mapping rules of the invariant locale. + * + * This function supports one-to-many character mapping, thus the length of the returned string can be greater than one. + * For example, `'\uFB00'.titlecase()` returns `"\u0046\u0066"`, + * where `'\uFB00'` is the LATIN SMALL LIGATURE FF character (`ff`). + * If this character has no title case mapping, the result of [uppercase] is returned instead. + * + * @sample samples.text.Chars.titlecase + */ +@SinceKotlin("1.4") +@ExperimentalStdlibApi +public fun Char.titlecase(): String = titlecaseImpl() + /** * Concatenates this Char and a String. * diff --git a/libraries/stdlib/wasm/src/kotlin/Text.kt b/libraries/stdlib/wasm/src/kotlin/Text.kt index ff024aa5d81..7a38fcee45e 100644 --- a/libraries/stdlib/wasm/src/kotlin/Text.kt +++ b/libraries/stdlib/wasm/src/kotlin/Text.kt @@ -130,6 +130,19 @@ public actual fun Char.uppercaseChar(): Char = TODO("Wasm stdlib: Text") @ExperimentalStdlibApi public actual fun Char.uppercase(): String = TODO("Wasm stdlib: Text") +/** + * Converts this character to title case using Unicode mapping rules of the invariant locale. + * + * This function performs one-to-one character mapping. + * To support one-to-many character mapping use the [titlecase] function. + * If this character has no mapping equivalent, the result of calling [uppercaseChar] is returned. + * + * @sample samples.text.Chars.titlecase + */ +@SinceKotlin("1.4") +@ExperimentalStdlibApi +public actual fun Char.titlecaseChar(): Char = TODO("Wasm stdlib: Text") + /** * Returns the Unicode general category of this character. diff --git a/libraries/tools/kotlin-stdlib-gen/src/generators/unicode/GenerateUnicodeData.kt b/libraries/tools/kotlin-stdlib-gen/src/generators/unicode/GenerateUnicodeData.kt index 116b2bec487..e6670d635a5 100644 --- a/libraries/tools/kotlin-stdlib-gen/src/generators/unicode/GenerateUnicodeData.kt +++ b/libraries/tools/kotlin-stdlib-gen/src/generators/unicode/GenerateUnicodeData.kt @@ -92,10 +92,8 @@ fun main(args: Array) { fun addOneToManyMappingsGenerators(generatedDir: File, target: KotlinTarget) { val uppercase = OneToManyMappingsGenerator.forUppercase(generatedDir.resolve("_OneToManyUppercaseMappings.kt"), target, bmpUnicodeDataLines) val lowercase = OneToManyMappingsGenerator.forLowercase(generatedDir.resolve("_OneToManyLowercaseMappings.kt"), target, bmpUnicodeDataLines) - val titlecase = OneToManyMappingsGenerator.forTitlecase(generatedDir.resolve("_OneToManyTitlecaseMappings.kt"), target, bmpUnicodeDataLines) oneToManyMappingsGenerators.add(uppercase) oneToManyMappingsGenerators.add(lowercase) - oneToManyMappingsGenerators.add(titlecase) } var categoryTestGenerator: CharCategoryTestGenerator? = null @@ -112,11 +110,16 @@ fun main(args: Array) { val categoryTestFile = baseDir.resolve("libraries/stdlib/js/test/text/unicodeData/_CharCategoryTest.kt") categoryTestGenerator = CharCategoryTestGenerator(categoryTestFile) + val commonGeneratedDir = baseDir.resolve("libraries/stdlib/common/src/generated") + oneToManyMappingsGenerators.add(OneToManyMappingsGenerator.forTitlecase(commonGeneratedDir.resolve("_OneToManyTitlecaseMappings.kt"), bmpUnicodeDataLines)) + val jsGeneratedDir = baseDir.resolve("libraries/stdlib/js/src/generated/") addRangesGenerators(jsGeneratedDir, KotlinTarget.JS) + oneToOneMappingsGenerators.add(MappingsGenerator.forTitlecase(jsGeneratedDir.resolve("_TitlecaseMappings.kt"))) val jsIrGeneratedDir = baseDir.resolve("libraries/stdlib/js-ir/src/generated/") addRangesGenerators(jsIrGeneratedDir, KotlinTarget.JS_IR) + oneToOneMappingsGenerators.add(MappingsGenerator.forTitlecase(jsIrGeneratedDir.resolve("_TitlecaseMappings.kt"))) // For debugging. To see the file content fun downloadFile(fromUrl: String) { diff --git a/libraries/tools/kotlin-stdlib-gen/src/generators/unicode/mappings/oneToMany/builders/OneToManyMappingsBuilder.kt b/libraries/tools/kotlin-stdlib-gen/src/generators/unicode/mappings/oneToMany/builders/OneToManyMappingsBuilder.kt index 9f22e55a6e2..883e99b1bd5 100644 --- a/libraries/tools/kotlin-stdlib-gen/src/generators/unicode/mappings/oneToMany/builders/OneToManyMappingsBuilder.kt +++ b/libraries/tools/kotlin-stdlib-gen/src/generators/unicode/mappings/oneToMany/builders/OneToManyMappingsBuilder.kt @@ -11,7 +11,7 @@ import generators.unicode.hexToInt import java.util.TreeMap internal abstract class OneToManyMappingsBuilder(bmpUnicodeDataLines: List) { - private val bmpUnicodeDataLines = bmpUnicodeDataLines.associateBy { it.char.hexToInt() } + protected val bmpUnicodeDataLines = bmpUnicodeDataLines.associateBy { it.char.hexToInt() } private val mappings = TreeMap>() fun append(line: SpecialCasingLine) { @@ -33,7 +33,7 @@ internal abstract class OneToManyMappingsBuilder(bmpUnicodeDataLines: List? { - val mapping = line.mapping() + val mapping = line.mapping() ?: return null check(mapping.isNotEmpty() && mapping.all { it.isNotEmpty() }) @@ -53,6 +53,6 @@ internal abstract class OneToManyMappingsBuilder(bmpUnicodeDataLines: List + abstract fun SpecialCasingLine.mapping(): List? abstract fun UnicodeDataLine.mapping(): String } \ No newline at end of file diff --git a/libraries/tools/kotlin-stdlib-gen/src/generators/unicode/mappings/oneToMany/builders/OneToManyTitlecaseMappingsBuilder.kt b/libraries/tools/kotlin-stdlib-gen/src/generators/unicode/mappings/oneToMany/builders/OneToManyTitlecaseMappingsBuilder.kt index 8652e382d5e..42c2a13e3ab 100644 --- a/libraries/tools/kotlin-stdlib-gen/src/generators/unicode/mappings/oneToMany/builders/OneToManyTitlecaseMappingsBuilder.kt +++ b/libraries/tools/kotlin-stdlib-gen/src/generators/unicode/mappings/oneToMany/builders/OneToManyTitlecaseMappingsBuilder.kt @@ -7,8 +7,28 @@ package generators.unicode.mappings.oneToMany.builders import generators.unicode.SpecialCasingLine import generators.unicode.UnicodeDataLine +import generators.unicode.hexToInt internal class OneToManyTitlecaseMappingsBuilder(bmpUnicodeDataLines: List) : OneToManyMappingsBuilder(bmpUnicodeDataLines) { - override fun SpecialCasingLine.mapping(): List = titlecaseMapping + private fun lower(char: Int): Int { + return bmpUnicodeDataLines[char]?.lowercaseMapping?.takeIf { it.isNotEmpty() }?.hexToInt() ?: char + } + + override fun SpecialCasingLine.mapping(): List? { + val title = titlecaseMapping.map { it.hexToInt().toChar() }.joinToString(separator = "") + val upperFirst = uppercaseMapping[0].hexToInt().toChar() + + // The version of Unicode in JVM executing this code is likely different than the version of the Unicode being parsed. + // Thus use the Unicode data being parsed to convert chars to lowercase. + val lowercasedTail = uppercaseMapping.drop(1).map { lower(it.hexToInt()).toChar() }.joinToString(separator = "") + + // Skip titlecase mappings that can be derived by lowercasing tail characters + if (title == upperFirst + lowercasedTail) { + return null + } + + return titlecaseMapping + } + override fun UnicodeDataLine.mapping(): String = titlecaseMapping } diff --git a/libraries/tools/kotlin-stdlib-gen/src/generators/unicode/mappings/oneToMany/writers/OneToManyTitlecaseMappingsWriter.kt b/libraries/tools/kotlin-stdlib-gen/src/generators/unicode/mappings/oneToMany/writers/OneToManyTitlecaseMappingsWriter.kt index 72a02c1eb5c..e40e914061c 100644 --- a/libraries/tools/kotlin-stdlib-gen/src/generators/unicode/mappings/oneToMany/writers/OneToManyTitlecaseMappingsWriter.kt +++ b/libraries/tools/kotlin-stdlib-gen/src/generators/unicode/mappings/oneToMany/writers/OneToManyTitlecaseMappingsWriter.kt @@ -5,27 +5,31 @@ package generators.unicode.mappings.oneToMany.writers -import generators.unicode.ranges.RangesWritingStrategy -import generators.unicode.writeMappings +import generators.unicode.toHexCharLiteral import java.io.FileWriter -internal class OneToManyTitlecaseMappingsWriter(private val strategy: RangesWritingStrategy) : OneToManyMappingsWriter { +internal class OneToManyTitlecaseMappingsWriter : OneToManyMappingsWriter { override fun write(mappings: Map>, writer: FileWriter) { - strategy.beforeWritingRanges(writer) - writer.writeMappings(mappings, strategy) - strategy.afterWritingRanges(writer) - writer.appendLine() - writer.appendLine(titlecaseImpl()) + // We have decided to ignore GREEK EXTENDED block due to their rare usage. + // It also leads to decreased js code size and simplified implementation. + val nonGreekExtended = mappings.filterNot { it.key in 0x1f00..0x1fff } + + check(nonGreekExtended.size == 1) + val n = nonGreekExtended.keys.single() + check(n == 0x0149) // LATIN SMALL LETTER N PRECEDED BY APOSTROPHE + + // one-to-many titlecase equivalent of the char is "\u02BC\u004E", the same as the uppercase equivalent + writer.appendLine(titlecaseImpl(n.toHexCharLiteral())) } - private fun titlecaseImpl(): String = """ + private fun titlecaseImpl(apostropheN: String): String = """ + @OptIn(ExperimentalStdlibApi::class) internal fun Char.titlecaseImpl(): String { - val code = this.toInt() - val index = binarySearchRange(keys, code) - if (index >= 0 && keys[index] == code) { - return values[index] + val uppercase = uppercase() + if (uppercase.length > 1) { + return if (this == $apostropheN) uppercase else uppercase[0] + uppercase.substring(1).lowercase() } - return titlecaseCharImpl().toString() + return titlecaseChar().toString() } """.trimIndent() } diff --git a/libraries/tools/kotlin-stdlib-gen/src/generators/unicode/mappings/oneToOne/writers/TitlecaseMappingsWriter.kt b/libraries/tools/kotlin-stdlib-gen/src/generators/unicode/mappings/oneToOne/writers/TitlecaseMappingsWriter.kt index 87cecc2f5f4..f76407f2c77 100644 --- a/libraries/tools/kotlin-stdlib-gen/src/generators/unicode/mappings/oneToOne/writers/TitlecaseMappingsWriter.kt +++ b/libraries/tools/kotlin-stdlib-gen/src/generators/unicode/mappings/oneToOne/writers/TitlecaseMappingsWriter.kt @@ -22,15 +22,18 @@ internal class TitlecaseMappingsWriter : MappingsWriter { writer.append( """ + @OptIn(ExperimentalStdlibApi::class) internal fun Char.titlecaseCharImpl(): Char { val code = this.toInt() + // Letters repeating sequence and code of the Lt is a multiple of 3, e.g. <DŽ, Dž, dž> if (${rangeChecks(LuLtLlMappings, "code")}) { return (3 * ((code + 1) / 3)).toChar() } + // Lower case letters whose title case mapping equivalent is equal to the original letter if (${rangeChecks(zeroMappings, "code")}) { return this } - return uppercaseCharImpl() + return uppercaseChar() } """.trimIndent() )