blob: 4aa05b75118cefde1df5ffa0a0c100bec4d84988 [file]
/*
* Copyright 2010-2025 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.text.unicode.*
import kotlin.test.*
@OptIn(ExperimentalUnicodeApi::class)
class CodePointIterationTest {
@Test
fun codePointAtBefore() {
fun <T> testCodePointAtBefore(
s: (String) -> T,
size: T.() -> Int,
codePointAt: T.(Int) -> CodePoint,
codePointBefore: T.(Int) -> CodePoint,
) {
val string = s("abc😀def")
val unpairedSurrogates = s(charArrayOf('a', Char.MIN_HIGH_SURROGATE, 'b', Char.MIN_LOW_SURROGATE).concatToString())
run {
val c2 = string.codePointAt(2)
val c3 = string.codePointAt(3)
val c4 = string.codePointAt(4)
assertEquals('c', c2.toSingleChar())
assertEquals(0x1F600, c3.code)
val [high, low] = c3.toSurrogatePair()
assertEquals(low, c4.toSingleChar())
assertFailsWith<IndexOutOfBoundsException> { string.codePointAt(-1) }
assertFailsWith<IndexOutOfBoundsException> { string.codePointAt(string.size()) }
assertEquals(Char.MIN_HIGH_SURROGATE, unpairedSurrogates.codePointAt(1).toSingleChar())
assertEquals(Char.MIN_LOW_SURROGATE, unpairedSurrogates.codePointAt(3).toSingleChar())
}
run {
val c2 = string.codePointBefore(3)
val c3 = string.codePointBefore(4)
val c4 = string.codePointBefore(5)
assertEquals('c', c2.toSingleChar())
assertEquals(0x1F600, c4.code)
val [high, low] = c4.toSurrogatePair()
assertEquals(high, c3.toSingleChar())
assertEquals('f', string.codePointBefore(string.size()).toSingleChar())
assertFailsWith<IndexOutOfBoundsException> { string.codePointBefore(0) }
assertFailsWith<IndexOutOfBoundsException> { string.codePointBefore(string.size() + 1) }
assertEquals(Char.MIN_HIGH_SURROGATE, unpairedSurrogates.codePointBefore(2).toSingleChar())
assertEquals(Char.MIN_LOW_SURROGATE, unpairedSurrogates.codePointBefore(4).toSingleChar())
}
}
testCodePointAtBefore({ it }, String::length, String::codePointAt, String::codePointBefore)
testCodePointAtBefore({ it as CharSequence }, CharSequence::length, CharSequence::codePointAt, CharSequence::codePointBefore)
testCodePointAtBefore({ StringBuilder(it) }, CharSequence::length, CharSequence::codePointAt, CharSequence::codePointBefore)
testCodePointAtBefore({ it.toCharArray() }, CharArray::size, CharArray::codePointAt, CharArray::codePointBefore)
run {
val charArray = "abc😀".toCharArray()
val [high, low] = charArray.codePointAt(3, 5).toSurrogatePair()
assertEquals(high, charArray.codePointAt(3, 4).toSingleChar())
assertEquals(low, charArray.codePointBefore(5, 4).toSingleChar())
assertEquals(CodePoint.fromSurrogatePair(high, low), charArray.codePointBefore(5, 3))
assertFailsWith<IndexOutOfBoundsException> { charArray.codePointAt(3, 3) }
assertFailsWith<IndexOutOfBoundsException> { charArray.codePointAt(3, charArray.size + 1) }
assertFailsWith<IndexOutOfBoundsException> { charArray.codePointAt(-1, -1) }
assertFailsWith<IndexOutOfBoundsException> { charArray.codePointBefore(1, 1) }
assertFailsWith<IndexOutOfBoundsException> { charArray.codePointBefore(1, -1) }
assertFailsWith<IndexOutOfBoundsException> { charArray.codePointBefore(charArray.size + 1, charArray.size) }
}
}
@Test
fun codePointCount() {
val string = "abc😀def"
assertEquals(7, string.codePointCount())
assertEquals(4, string.codePointCount(startIndex = 3))
assertEquals(4, string.codePointCount(startIndex = 4))
assertEquals(4, string.codePointCount(endIndex = 4))
assertEquals(4, string.codePointCount(endIndex = 5))
assertFailsWith<IndexOutOfBoundsException> { string.codePointCount(startIndex = -1) }
assertFailsWith<IndexOutOfBoundsException> { string.codePointCount(endIndex = string.length + 1) }
assertFailsWith<IndexOutOfBoundsException> { string.codePointCount(startIndex = 3, endIndex = 2) }
val charSeq = StringBuilder(string)
assertEquals(7, charSeq.codePointCount())
assertEquals(4, charSeq.codePointCount(startIndex = 3))
assertEquals(4, charSeq.codePointCount(startIndex = 4))
assertEquals(4, charSeq.codePointCount(endIndex = 4))
assertEquals(4, charSeq.codePointCount(endIndex = 5))
assertFailsWith<IndexOutOfBoundsException> { charSeq.codePointCount(startIndex = -1) }
assertFailsWith<IndexOutOfBoundsException> { charSeq.codePointCount(endIndex = string.length + 1) }
assertFailsWith<IndexOutOfBoundsException> { charSeq.codePointCount(startIndex = 3, endIndex = 2) }
val charArray = string.toCharArray()
assertEquals(7, charArray.codePointCount())
assertEquals(4, charArray.codePointCount(startIndex = 3))
assertEquals(4, charArray.codePointCount(startIndex = 4))
assertEquals(4, charArray.codePointCount(endIndex = 4))
assertEquals(4, charArray.codePointCount(endIndex = 5))
assertFailsWith<IndexOutOfBoundsException> { charArray.codePointCount(startIndex = -1) }
assertFailsWith<IndexOutOfBoundsException> { charArray.codePointCount(endIndex = string.length + 1) }
assertFailsWith<IndexOutOfBoundsException> { charArray.codePointCount(startIndex = 3, endIndex = 2) }
}
@Test
fun codePointOffset() {
val string = "abc😀def${Char.MIN_LOW_SURROGATE}-${Char.MAX_HIGH_SURROGATE}"
fun <T> testOffset(s: (String) -> T, offsetByCodePoints: T.(Int, Int) -> Int) {
val source = s(string)
assertEquals(5, source.offsetByCodePoints(0, 4))
assertEquals(3, source.offsetByCodePoints(5, -1))
assertEquals(3, source.offsetByCodePoints(4, -1))
assertEquals(string.length - 1, source.offsetByCodePoints(string.length, -1))
assertFailsWith<IndexOutOfBoundsException> { source.offsetByCodePoints(-1, 0) }
assertFailsWith<IndexOutOfBoundsException> { source.offsetByCodePoints(0, -1) }
assertFailsWith<IndexOutOfBoundsException> { source.offsetByCodePoints(string.length + 1, 0) }
assertFailsWith<IndexOutOfBoundsException> { source.offsetByCodePoints(string.length, 1) }
assertFailsWith<IndexOutOfBoundsException> { source.offsetByCodePoints(string.length, Int.MIN_VALUE) }
}
testOffset({ it }, String::offsetByCodePoints)
testOffset({ it as CharSequence }, CharSequence::offsetByCodePoints)
testOffset({ StringBuilder(it) }, CharSequence::offsetByCodePoints)
testOffset({ it.toCharArray() }, CharArray::offsetByCodePoints)
val charArray = string.toCharArray()
assertEquals(4, charArray.offsetByCodePoints(0, 4, endIndex = 4))
assertEquals(4, charArray.offsetByCodePoints(5, -1, startIndex = 4, endIndex = 5))
assertFailsWith<IndexOutOfBoundsException> { charArray.offsetByCodePoints(1, 0, startIndex = 2) }
assertFailsWith<IndexOutOfBoundsException> { charArray.offsetByCodePoints(1, -1, startIndex = 1) }
assertFailsWith<IndexOutOfBoundsException> { charArray.offsetByCodePoints(6, 0, endIndex = 5) }
assertFailsWith<IndexOutOfBoundsException> { charArray.offsetByCodePoints(5, 1, endIndex = 5) }
assertFailsWith<IndexOutOfBoundsException> { charArray.offsetByCodePoints(5, 1, startIndex = -1) }
assertFailsWith<IndexOutOfBoundsException> { charArray.offsetByCodePoints(5, 1, endIndex = charArray.size + 1) }
assertFailsWith<IndexOutOfBoundsException> { charArray.offsetByCodePoints(4, 0, startIndex = 4, endIndex = 3) }
}
@Test
fun forEachIteration() {
val string = "abc😀def${Char.MIN_LOW_SURROGATE}-${Char.MAX_HIGH_SURROGATE}"
fun <T> testIteration(s: (String) -> T, forEach: (T, (CodePoint)-> Unit) -> Unit) {
val source = s(string)
val result = StringBuilder()
forEach(source) { c -> result.appendCodePoint(c) }
assertEquals(string, result.toString())
}
testIteration({ it }, { s, action -> s.forEachCodePoint(action) })
testIteration({ it as CharSequence }, { s, action -> s.forEachCodePoint(action) })
testIteration({ it.toCharArray() }, { s, action -> s.forEachCodePoint(0, s.size, action = action) })
testIteration({ "${Char.MIN_HIGH_SURROGATE}$string${Char.MIN_LOW_SURROGATE}".toCharArray() }, { s, action -> s.forEachCodePoint(1, s.size - 1, action) })
val charArray = string.toCharArray()
assertFailsWith<IndexOutOfBoundsException> { charArray.forEachCodePoint(startIndex = -1) {} }
assertFailsWith<IndexOutOfBoundsException> { charArray.forEachCodePoint(endIndex = charArray.size + 1) {} }
assertFailsWith<IndexOutOfBoundsException> { charArray.forEachCodePoint(startIndex = 1, endIndex = 0) {} }
}
@Test
fun iteration() {
fun String.codePointSequences(): List<CodePointSequence> = listOf(
this.asCodePointSequence(),
(this as CharSequence).asCodePointSequence(),
StringBuilder(this).asCodePointSequence(),
this.toCharArray().asCodePointSequence(),
"${Char.MIN_HIGH_SURROGATE}$this${Char.MIN_LOW_SURROGATE}".toCharArray().asCodePointSequence(1, 1 + this.length)
)
val string = "abc😀def"
for (seq in string.codePointSequences()) {
assertEquals(listOf("a", "b", "c", "😀", "d", "e", "f"), seq.map { it.toString() }.toList())
}
val unpairedSurrogates = charArrayOf('a', Char.MIN_HIGH_SURROGATE, 'b', Char.MIN_LOW_SURROGATE).concatToString()
for (seq in unpairedSurrogates.codePointSequences()) {
assertEquals(unpairedSurrogates.toList(), seq.map { it.toSingleChar() }.toList())
}
val charArray = "abc".toCharArray()
assertFailsWith<IndexOutOfBoundsException> { charArray.asCodePointSequence(startIndex = -1) }
assertFailsWith<IndexOutOfBoundsException> { charArray.asCodePointSequence(endIndex = 4) }
assertFailsWith<IllegalArgumentException> { charArray.asCodePointSequence(startIndex = 2, endIndex = 1) }
}
@Test
fun indexedIteration() {
val string = "abc😀def"
fun <T> testIterator(s: (String) -> T, codePointIterator: T.(Int) -> CodePointIndexedIterator) {
val s = s(string)
val iterator = s.codePointIterator(3)
assertTrue(iterator.hasNext())
assertTrue(iterator.hasPrevious())
assertEquals(2, iterator.previousIndex())
assertEquals(3, iterator.nextIndex())
assertEquals("😀".codePointAt(0), iterator.next())
assertEquals(3, iterator.previousIndex())
assertEquals(5, iterator.nextIndex())
iterator.advanceByCodePoints(-1)
assertEquals(3, iterator.nextIndex())
assertEquals(2, iterator.previousIndex())
assertEquals('c'.toCodePoint(), iterator.previous())
assertEquals(string.substring(2).asCodePointSequence().toList(), iterator.asSequence().toList())
assertFalse(iterator.hasNext())
assertFailsWith<NoSuchElementException> { iterator.next() }
assertEquals(string.length, iterator.nextIndex())
assertTrue(iterator.hasPrevious())
val reverseResult = buildList {
while(iterator.hasPrevious()) add(iterator.previous())
}
assertFailsWith<NoSuchElementException> { iterator.previous() }
assertEquals(-1, iterator.previousIndex())
assertEquals(string.reversed().asCodePointSequence().toList(), reverseResult)
assertFailsWith<IndexOutOfBoundsException> { iterator.advanceByCodePoints(-1) }
assertFailsWith<IndexOutOfBoundsException> { iterator.advanceByCodePoints(string.codePointCount() + 1) }
s.codePointIterator(4).let {
assertEquals(4, it.nextIndex())
assertEquals(3, it.previousIndex())
it.advanceByCodePoints(0)
assertEquals(4, it.nextIndex())
assertEquals(3, it.previousIndex())
assertEquals("😀".codePointAt(0).lowSurrogate(), it.next().toSingleChar())
assertEquals(5, it.nextIndex())
assertEquals(3, it.previousIndex()) // remains the same after calling next
it.advanceByCodePoints(0)
assertEquals(5, it.nextIndex())
assertEquals(3, it.previousIndex())
assertEquals("😀".codePointAt(0), it.previous())
}
s.codePointIterator(4).let {
assertEquals(3, it.previousIndex())
assertEquals("😀".codePointAt(0).highSurrogate(), it.previous().toSingleChar())
assertEquals(3, it.nextIndex())
assertEquals("😀".codePointAt(0), it.next())
}
assertFailsWith<IndexOutOfBoundsException> { s.codePointIterator(-1) }
assertFailsWith<IndexOutOfBoundsException> { s.codePointIterator(string.length + 1) }
}
testIterator({ it }, String::codePointIterator)
testIterator({ StringBuilder(it) }, CharSequence::codePointIterator)
testIterator({ it.toCharArray() }, CharArray::codePointIterator)
fun CodePointIterator.collectToString() = buildString { forEach(::appendCodePoint) }
run {
val charArray = string.toCharArray()
val c = "😀".codePointAt(0)
assertEquals(c, charArray.codePointIterator(charArray.size, startIndex = 0, endIndex = charArray.size).apply { advanceByCodePoints(-4) }.next())
assertEquals(c, charArray.codePointIterator(charArray.size, startIndex = 0, endIndex = charArray.size).apply { advanceByCodePoints(-3) }.previous())
assertEquals(c.lowSurrogate(), charArray.codePointIterator(index = 5, startIndex = 4).previous().toSingleChar())
assertEquals(c.highSurrogate(), charArray.codePointIterator(index = 3, endIndex = 4).next().toSingleChar())
assertEquals(string, charArray.codePointIterator().collectToString())
assertEquals("😀def", charArray.codePointIterator(startIndex = 3).collectToString())
assertTrue(charArray.codePointIterator(index = 3, endIndex = 4).hasPrevious())
assertFalse(charArray.codePointIterator(3, endIndex = 4).hasPrevious())
assertFailsWith<IndexOutOfBoundsException> { charArray.codePointIterator(index = 0, startIndex = 1) }
assertFailsWith<IndexOutOfBoundsException> { charArray.codePointIterator(index = 2, endIndex = 1) }
assertFailsWith<IndexOutOfBoundsException> { charArray.codePointIterator(index = 1, startIndex = -1) }
assertFailsWith<IndexOutOfBoundsException> { charArray.codePointIterator(index = 1, endIndex = charArray.size + 1) }
assertFailsWith<IllegalArgumentException> { charArray.codePointIterator(1, startIndex = 1, endIndex = 0) }
}
}
}