Commonize Char.titlecaseChar() and Char.titlecase() #KT-44369

This commit is contained in:
Abduqodiri Qurbonzoda
2021-03-01 21:49:11 +03:00
parent 0c9205423f
commit 2266cd4631
15 changed files with 195 additions and 42 deletions
@@ -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
+8
View File
@@ -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
@@ -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()
}
@@ -27,6 +27,7 @@ val commonMainSources by task<Sync> {
"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",
@@ -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 <Lu, Lt, Ll> 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()
}
@@ -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 <Lu, Lt, Ll> 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()
}
@@ -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).
*/
@@ -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].
+27
View File
@@ -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.
*
+13
View File
@@ -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.
@@ -92,10 +92,8 @@ fun main(args: Array<String>) {
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<String>) {
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) {
@@ -11,7 +11,7 @@ import generators.unicode.hexToInt
import java.util.TreeMap
internal abstract class OneToManyMappingsBuilder(bmpUnicodeDataLines: List<UnicodeDataLine>) {
private val bmpUnicodeDataLines = bmpUnicodeDataLines.associateBy { it.char.hexToInt() }
protected val bmpUnicodeDataLines = bmpUnicodeDataLines.associateBy { it.char.hexToInt() }
private val mappings = TreeMap<Int, List<String>>()
fun append(line: SpecialCasingLine) {
@@ -33,7 +33,7 @@ internal abstract class OneToManyMappingsBuilder(bmpUnicodeDataLines: List<Unico
}
private fun mapping(charCode: Int, line: SpecialCasingLine): List<String>? {
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<Unico
return mapping
}
abstract fun SpecialCasingLine.mapping(): List<String>
abstract fun SpecialCasingLine.mapping(): List<String>?
abstract fun UnicodeDataLine.mapping(): String
}
@@ -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<UnicodeDataLine>) : OneToManyMappingsBuilder(bmpUnicodeDataLines) {
override fun SpecialCasingLine.mapping(): List<String> = titlecaseMapping
private fun lower(char: Int): Int {
return bmpUnicodeDataLines[char]?.lowercaseMapping?.takeIf { it.isNotEmpty() }?.hexToInt() ?: char
}
override fun SpecialCasingLine.mapping(): List<String>? {
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
}
@@ -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<Int, List<String>>, 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()
}
@@ -22,15 +22,18 @@ internal class TitlecaseMappingsWriter : MappingsWriter {
writer.append(
"""
@OptIn(ExperimentalStdlibApi::class)
internal fun Char.titlecaseCharImpl(): Char {
val code = this.toInt()
// Letters repeating <Lu, Lt, Ll> 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()
)