Equivalize isLowerCase and isUpperCase behavior in all platforms #KT-46184
This commit is contained in:
@@ -19,8 +19,9 @@ internal fun FileWriter.writeHeader(file: File, pkg: String) {
|
||||
appendLine(autoGeneratedWarning("GenerateUnicodeData.kt"))
|
||||
}
|
||||
|
||||
internal fun FileWriter.writeIntArray(name: String, elements: List<Int>, strategy: RangesWritingStrategy) {
|
||||
writeCollection(name, "intArrayOf", elements.map { it.toHexIntLiteral() }, strategy)
|
||||
internal fun FileWriter.writeIntArray(name: String, elements: List<Int>, strategy: RangesWritingStrategy, useHex: Boolean = true) {
|
||||
val toString = if (useHex) Int::toHexIntLiteral else Int::toString
|
||||
writeCollection(name, "intArrayOf", elements.map(toString), strategy)
|
||||
}
|
||||
|
||||
private fun FileWriter.writeCollection(
|
||||
@@ -98,3 +99,16 @@ internal fun String.hexToInt(): Int {
|
||||
internal fun List<String>.hexCharsToStringLiteral(): String {
|
||||
return "\"${joinToString(separator = "") { "\\u$it" }}\""
|
||||
}
|
||||
|
||||
internal fun IntRange.rangeCheck(ch: String, indent: String): String {
|
||||
val firstHex = first.toHexIntLiteral()
|
||||
val lastHex = last.toHexIntLiteral()
|
||||
return when (first) {
|
||||
last ->
|
||||
"$ch == $firstHex"
|
||||
last - 1 ->
|
||||
"$ch == $firstHex\n$indent|| $ch == $lastHex"
|
||||
else ->
|
||||
"$ch in $firstHex..$lastHex"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ import generators.unicode.ranges.RangesGenerator
|
||||
import generators.unicode.mappings.string.StringCasingTestGenerator
|
||||
import generators.unicode.mappings.string.StringLowercaseGenerator
|
||||
import generators.unicode.mappings.string.StringUppercaseGenerator
|
||||
import generators.unicode.ranges.OtherLowercaseRangesGenerator
|
||||
import generators.unicode.ranges.OtherUppercaseRangesGenerator
|
||||
import templates.COPYRIGHT_NOTICE
|
||||
import templates.KotlinTarget
|
||||
import templates.readCopyrightNoticeFromProfile
|
||||
@@ -61,6 +63,8 @@ fun main(args: Array<String>) {
|
||||
}
|
||||
|
||||
val categoryRangesGenerators = mutableListOf<RangesGenerator>()
|
||||
val otherLowercaseGenerators = mutableListOf<OtherLowercaseRangesGenerator>()
|
||||
val otherUppercaseGenerators = mutableListOf<OtherUppercaseRangesGenerator>()
|
||||
|
||||
fun addRangesGenerators(generatedDir: File, target: KotlinTarget) {
|
||||
val category = RangesGenerator.forCharCategory(generatedDir.resolve("_CharCategories.kt"), target)
|
||||
@@ -71,6 +75,9 @@ fun main(args: Array<String>) {
|
||||
categoryRangesGenerators.add(digit)
|
||||
categoryRangesGenerators.add(letter)
|
||||
categoryRangesGenerators.add(whitespace)
|
||||
|
||||
otherLowercaseGenerators.add(OtherLowercaseRangesGenerator(generatedDir.resolve("_OtherLowercaseChars.kt"), target))
|
||||
otherUppercaseGenerators.add(OtherUppercaseRangesGenerator(generatedDir.resolve("_OtherUppercaseChars.kt"), target))
|
||||
}
|
||||
|
||||
val oneToOneMappingsGenerators = mutableListOf<MappingsGenerator>()
|
||||
@@ -154,9 +161,18 @@ fun main(args: Array<String>) {
|
||||
bmpUnicodeDataLines.forEach { line -> it.appendLine(line) }
|
||||
it.generate()
|
||||
}
|
||||
otherLowercaseGenerators.forEach {
|
||||
propListLines.forEach { line -> it.appendLine(line) }
|
||||
it.generate()
|
||||
}
|
||||
otherUppercaseGenerators.forEach {
|
||||
propListLines.forEach { line -> it.appendLine(line) }
|
||||
it.generate()
|
||||
}
|
||||
|
||||
categoryTestGenerator.let {
|
||||
bmpUnicodeDataLines.forEach { line -> it.appendLine(line) }
|
||||
propListLines.forEach { line -> it.appendPropertyLine(line) }
|
||||
it.generate()
|
||||
}
|
||||
|
||||
@@ -176,7 +192,6 @@ fun main(args: Array<String>) {
|
||||
}
|
||||
stringLowercaseGenerator.let {
|
||||
specialCasingLines.forEach { line -> it.appendSpecialCasingLine(line) }
|
||||
propListLines.forEach { line -> it.appendPropListLine(line) }
|
||||
wordBreakPropertyLines.forEach { line -> it.appendWordBreakPropertyLine(line) }
|
||||
it.generate()
|
||||
}
|
||||
|
||||
@@ -14,6 +14,14 @@ internal class PropertyLine(properties: List<String>) {
|
||||
val rangeEnd: String = properties[0].split("..").last()
|
||||
val property: String = properties[1].takeWhile { it != ' ' }
|
||||
|
||||
fun intRange(): IntRange {
|
||||
return rangeStart.hexToInt()..rangeEnd.hexToInt()
|
||||
}
|
||||
|
||||
fun hexIntRangeLiteral(): String {
|
||||
return "${rangeStart.hexToInt().toHexIntLiteral()}..${rangeEnd.hexToInt().toHexIntLiteral()}"
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "PropertyLine{rangeStart=$rangeStart" +
|
||||
", rangeEnd=$rangeEnd" +
|
||||
|
||||
+1
-3
@@ -57,9 +57,7 @@ internal class StringCasingTestGenerator(private val outputDir: File) {
|
||||
writer.appendLine("@SharedImmutable")
|
||||
writer.appendLine("private val $rangesArrayName = arrayOf<IntRange>(")
|
||||
ranges.forEach {
|
||||
val start = it.rangeStart.hexToInt().toHexIntLiteral()
|
||||
val end = it.rangeEnd.hexToInt().toHexIntLiteral()
|
||||
writer.appendLine(" $start..$end,")
|
||||
writer.appendLine(" ${it.hexIntRangeLiteral()},")
|
||||
}
|
||||
writer.appendLine(")")
|
||||
writer.appendLine()
|
||||
|
||||
+4
-8
@@ -44,18 +44,11 @@ internal class StringLowercaseGenerator(
|
||||
|
||||
override fun UnicodeDataLine.mapping(): String = lowercaseMapping
|
||||
|
||||
fun appendPropListLine(line: PropertyLine) {
|
||||
when (line.property) {
|
||||
"Other_Lowercase",
|
||||
"Other_Uppercase" -> casedRanges.add(line.rangeStart.hexToInt()..line.rangeEnd.hexToInt())
|
||||
}
|
||||
}
|
||||
|
||||
fun appendWordBreakPropertyLine(line: PropertyLine) {
|
||||
when (line.property) {
|
||||
"MidLetter",
|
||||
"MidNumLet",
|
||||
"Single_Quote" -> caseIgnorableRanges.add(line.rangeStart.hexToInt()..line.rangeEnd.hexToInt())
|
||||
"Single_Quote" -> caseIgnorableRanges.add(line.intRange())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,6 +94,9 @@ internal class StringLowercaseGenerator(
|
||||
CharCategory.TITLECASE_LETTER.value -> return true
|
||||
}
|
||||
}
|
||||
if (isOtherUppercase() || isOtherLowercase()) {
|
||||
return true
|
||||
}
|
||||
val index = binarySearchRange(casedStart, this)
|
||||
return index >= 0 && this <= casedEnd[index]
|
||||
}
|
||||
|
||||
+25
-6
@@ -5,6 +5,7 @@
|
||||
|
||||
package generators.unicode.ranges
|
||||
|
||||
import generators.unicode.PropertyLine
|
||||
import generators.unicode.UnicodeDataLine
|
||||
import generators.unicode.writeHeader
|
||||
import java.io.File
|
||||
@@ -38,6 +39,16 @@ internal class CharCategoryTestGenerator(private val outputFile: File) {
|
||||
}
|
||||
}
|
||||
|
||||
private val otherLowercaseRanges = mutableListOf<PropertyLine>()
|
||||
private val otherUppercaseRanges = mutableListOf<PropertyLine>()
|
||||
|
||||
fun appendPropertyLine(line: PropertyLine) {
|
||||
when (line.property) {
|
||||
"Other_Lowercase" -> otherLowercaseRanges.add(line)
|
||||
"Other_Uppercase" -> otherUppercaseRanges.add(line)
|
||||
}
|
||||
}
|
||||
|
||||
fun generate() {
|
||||
writer?.appendLine(")")
|
||||
writer?.close()
|
||||
@@ -111,10 +122,10 @@ class CharCategoryTest {
|
||||
val expectedIsLetterOrDigit = expectedIsLetter || expectedIsDigit
|
||||
test(expectedIsLetterOrDigit, char.isLetterOrDigit(), "isLetterOrDigit()")
|
||||
|
||||
val expectedIsLowerCase = isLowerCase(expectedCategoryCode)
|
||||
val expectedIsLowerCase = isLowerCase(char, expectedCategoryCode)
|
||||
test(expectedIsLowerCase, char.isLowerCase(), "isLowerCase()")
|
||||
|
||||
val expectedIsUpperCase = isUpperCase(expectedCategoryCode)
|
||||
val expectedIsUpperCase = isUpperCase(char, expectedCategoryCode)
|
||||
test(expectedIsUpperCase, char.isUpperCase(), "isUpperCase()")
|
||||
|
||||
val expectedIsWhitespace = isWhitespace(char, expectedCategoryCode)
|
||||
@@ -136,12 +147,20 @@ class CharCategoryTest {
|
||||
).map { it.code }
|
||||
}
|
||||
|
||||
private fun isLowerCase(categoryCode: String): Boolean {
|
||||
return categoryCode == CharCategory.LOWERCASE_LETTER.code
|
||||
private val otherLowerChars = listOf<IntRange>(
|
||||
${otherLowercaseRanges.joinToString { it.hexIntRangeLiteral() }}
|
||||
).flatten().toHashSet()
|
||||
|
||||
private fun isLowerCase(char: Char, categoryCode: String): Boolean {
|
||||
return categoryCode == CharCategory.LOWERCASE_LETTER.code || otherLowerChars.contains(char.code)
|
||||
}
|
||||
|
||||
private fun isUpperCase(categoryCode: String): Boolean {
|
||||
return categoryCode == CharCategory.UPPERCASE_LETTER.code
|
||||
private val otherUpperChars = listOf<IntRange>(
|
||||
${otherUppercaseRanges.joinToString { it.hexIntRangeLiteral() }}
|
||||
).flatten().toHashSet()
|
||||
|
||||
private fun isUpperCase(char: Char, categoryCode: String): Boolean {
|
||||
return categoryCode == CharCategory.UPPERCASE_LETTER.code || otherUpperChars.contains(char.code)
|
||||
}
|
||||
|
||||
private fun isWhitespace(char: Char, categoryCode: String): Boolean {
|
||||
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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 generators.unicode.ranges
|
||||
|
||||
import generators.unicode.PropertyLine
|
||||
import generators.unicode.hexToInt
|
||||
import generators.unicode.writeHeader
|
||||
import generators.unicode.writeIntArray
|
||||
import templates.KotlinTarget
|
||||
import java.io.File
|
||||
import java.io.FileWriter
|
||||
|
||||
internal class OtherLowercaseRangesGenerator(
|
||||
private val outputFile: File,
|
||||
private val target: KotlinTarget
|
||||
) {
|
||||
private val otherLowerRanges = mutableListOf<IntRange>()
|
||||
|
||||
fun appendLine(line: PropertyLine) {
|
||||
// In Native the Other_Lowercase code points are also used to perform String.lowercase()
|
||||
if (target != KotlinTarget.Native && line.rangeStart.hexToInt() > 0xFFFF) return
|
||||
|
||||
if (line.property == "Other_Lowercase") {
|
||||
otherLowerRanges.add(line.intRange())
|
||||
}
|
||||
}
|
||||
|
||||
fun generate() {
|
||||
val strategy = RangesWritingStrategy.of(target, "OtherLowercase")
|
||||
|
||||
FileWriter(outputFile).use { writer ->
|
||||
writer.writeHeader(outputFile, "kotlin.text")
|
||||
writer.appendLine()
|
||||
strategy.beforeWritingRanges(writer)
|
||||
writer.writeIntArray("otherLowerStart", otherLowerRanges.map { it.first }, strategy)
|
||||
writer.writeIntArray("otherLowerLength", otherLowerRanges.map { it.last - it.first + 1 }, strategy, useHex = false)
|
||||
strategy.afterWritingRanges(writer)
|
||||
writer.appendLine()
|
||||
writer.appendLine(isOtherLowercaseImpl(strategy))
|
||||
}
|
||||
}
|
||||
|
||||
fun isOtherLowercaseImpl(strategy: RangesWritingStrategy) = """
|
||||
internal fun Int.isOtherLowercase(): Boolean {
|
||||
val index = binarySearchRange(${strategy.rangeRef("otherLowerStart")}, this)
|
||||
return index >= 0 && this < ${strategy.rangeRef("otherLowerStart")}[index] + ${strategy.rangeRef("otherLowerLength")}[index]
|
||||
}
|
||||
""".trimIndent()
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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 generators.unicode.ranges
|
||||
|
||||
import generators.unicode.PropertyLine
|
||||
import generators.unicode.hexToInt
|
||||
import generators.unicode.rangeCheck
|
||||
import generators.unicode.writeHeader
|
||||
import templates.KotlinTarget
|
||||
import java.io.File
|
||||
import java.io.FileWriter
|
||||
|
||||
internal class OtherUppercaseRangesGenerator(
|
||||
private val outputFile: File,
|
||||
private val target: KotlinTarget
|
||||
) {
|
||||
private val otherUpperRanges = mutableListOf<IntRange>()
|
||||
|
||||
fun appendLine(line: PropertyLine) {
|
||||
// In Native the Other_Uppercase code points are also used to perform String.lowercase()
|
||||
if (target != KotlinTarget.Native && line.rangeStart.hexToInt() > 0xFFFF) return
|
||||
|
||||
if (line.property == "Other_Uppercase") {
|
||||
otherUpperRanges.add(line.intRange())
|
||||
}
|
||||
}
|
||||
|
||||
fun generate() {
|
||||
FileWriter(outputFile).use { writer ->
|
||||
writer.writeHeader(outputFile, "kotlin.text")
|
||||
writer.appendLine()
|
||||
writer.appendLine(isOtherUppercaseImpl())
|
||||
}
|
||||
}
|
||||
|
||||
fun isOtherUppercaseImpl(): String {
|
||||
val indent = " ".repeat(5)
|
||||
val builder = StringBuilder()
|
||||
|
||||
for (i in otherUpperRanges.indices) {
|
||||
if (i != 0) {
|
||||
builder.appendLine().append(indent).append("|| ")
|
||||
}
|
||||
builder.append(otherUpperRanges[i].rangeCheck("this", indent))
|
||||
}
|
||||
|
||||
return """
|
||||
internal fun Int.isOtherUppercase(): Boolean {
|
||||
return $builder
|
||||
}
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -60,17 +60,17 @@ internal open class LetterRangesWriter(protected val strategy: RangesWritingStra
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if this character is a lower case letter.
|
||||
* Returns `true` if this character is a lower case letter, or it has contributory property Other_Lowercase.
|
||||
*/
|
||||
internal fun Char.isLowerCaseImpl(): Boolean {
|
||||
return getLetterType() == 1
|
||||
return getLetterType() == 1 || code.isOtherLowercase()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if this character is an upper case letter.
|
||||
* Returns `true` if this character is an upper case letter, or it has contributory property Other_Uppercase.
|
||||
*/
|
||||
internal fun Char.isUpperCaseImpl(): Boolean {
|
||||
return getLetterType() == 2
|
||||
return getLetterType() == 2 || code.isOtherUppercase()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+6
-17
@@ -5,6 +5,7 @@
|
||||
|
||||
package generators.unicode.ranges.writers
|
||||
|
||||
import generators.unicode.rangeCheck
|
||||
import generators.unicode.toHexIntLiteral
|
||||
import java.io.FileWriter
|
||||
|
||||
@@ -38,24 +39,12 @@ internal class WhitespaceRangesWriter : RangesWriter {
|
||||
|
||||
val start = rangeStart[i]
|
||||
val end = rangeEnd[i]
|
||||
when (start) {
|
||||
end -> {
|
||||
if (start > 0x1000 && tabCount == 5) {
|
||||
builder.appendLine("$ch > 0x1000 && (")
|
||||
tabCount = 6
|
||||
builder.append(tab.repeat(tabCount))
|
||||
}
|
||||
builder.appendLine("$ch == ${start.toHexIntLiteral()}")
|
||||
}
|
||||
end - 1 -> {
|
||||
builder.appendLine("$ch == ${start.toHexIntLiteral()}")
|
||||
builder.append(tab.repeat(tabCount)).append("|| ")
|
||||
builder.appendLine("$ch == ${end.toHexIntLiteral()}")
|
||||
}
|
||||
else -> {
|
||||
builder.appendLine("$ch in ${start.toHexIntLiteral()}..${end.toHexIntLiteral()}")
|
||||
}
|
||||
if (start > 0x1000 && tabCount == 5) {
|
||||
builder.appendLine("$ch > 0x1000 && (")
|
||||
tabCount = 6
|
||||
builder.append(tab.repeat(tabCount))
|
||||
}
|
||||
builder.appendLine((start..end).rangeCheck(ch, tab.repeat(tabCount)))
|
||||
}
|
||||
|
||||
return builder.append(tab.repeat(5)).append(")").toString()
|
||||
|
||||
Reference in New Issue
Block a user