blob: c8c0f20b09acdfac1ee07ad92689a9766a677243 [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.regex.sets
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.text.regex.SetProperties
import kotlin.text.regex.SingleSet
class SetPropertiesTest {
private fun assertProperties(
pattern: String,
capturesGroups: Boolean,
tracksConsumption: Boolean,
nonTrivialBacktracking: Boolean,
requireCheckpointing: Boolean
) {
val re = Regex(pattern)
val p = re.nativePattern
val properties = SetProperties()
p.startNode.collectProperties(properties)
assertEquals(
SetProperties(
capturesGroups = capturesGroups, tracksConsumption = tracksConsumption,
nonTrivialBacktracking = nonTrivialBacktracking, requiresCheckpointing = requireCheckpointing
),
properties,
"For a regular expression \"$pattern\""
)
}
private fun assertInnerSetProperties(
pattern: String,
capturesGroups: Boolean,
tracksConsumption: Boolean,
nonTrivialBacktracking: Boolean,
requireCheckpointing: Boolean
) {
val re = Regex(pattern)
val p = re.nativePattern
val properties = SetProperties()
val set = p.startNode as SingleSet
set.kid.collectProperties(properties, set.fSet)
assertEquals(
SetProperties(
capturesGroups = capturesGroups, tracksConsumption = tracksConsumption,
nonTrivialBacktracking = nonTrivialBacktracking, requiresCheckpointing = requireCheckpointing
),
properties,
"For a regular expression \"$pattern\""
)
}
@Test
fun testProperties() {
assertProperties("abc", capturesGroups = true, false, false, false)
assertProperties("(abc)", capturesGroups = true, false, false, false)
assertProperties("(abc*)", capturesGroups = true, false, nonTrivialBacktracking = true, false)
assertProperties("(?:a|aa)", capturesGroups = true, tracksConsumption = true, nonTrivialBacktracking = true, false)
assertProperties("(?<=a)b", capturesGroups = true, true, nonTrivialBacktracking = true, requireCheckpointing = true)
}
// Every regex has a top-level group corresponding to a regular expression as a whole, so capturesGroups is always true
// This test inspects its inner set for exactly the same REs as testProperties, to ensure capturesGroups is captured correctly
@Test
fun testInnerSetProperties() {
assertInnerSetProperties("abc", capturesGroups = false, false, false, false)
assertInnerSetProperties("(abc)", capturesGroups = true, false, false, false)
assertInnerSetProperties("(abc*)", capturesGroups = true, false, nonTrivialBacktracking = true, false)
assertInnerSetProperties("(?:a|aa)", capturesGroups = false, tracksConsumption = true, nonTrivialBacktracking = true, false)
assertInnerSetProperties("(?<=a)b", capturesGroups = false, tracksConsumption = true, nonTrivialBacktracking = true, requireCheckpointing = true)
}
@Test
fun testNonTrivialRegularExpression() {
assertInnerSetProperties("a(?:b*)*c", capturesGroups = false, tracksConsumption = true, nonTrivialBacktracking = true, requireCheckpointing = false)
}
@Test
fun testTrivialRegularExpressions() {
// EOL tracks consumption, others - don't
assertInnerSetProperties("abc$", false, true, false, false)
assertInnerSetProperties("^abc", false, false, false, false)
assertInnerSetProperties("abc\\z", false, false, false, false)
assertInnerSetProperties("\\B", false, false, false, false)
assertInnerSetProperties("\\b", false, false, false, false)
assertInnerSetProperties("\\G", false, false, false, false)
assertInnerSetProperties("[a-z]", false, false, false, false)
assertInnerSetProperties("\\P{Nd}", false, false, false, false)
assertInnerSetProperties("\\p{Nd}", false, false, false, false)
assertInnerSetProperties(".", false, false, false, false)
assertInnerSetProperties("", false, false, false, false)
}
@Test
fun testQuantifiers() {
fun checkEagerLazyAndPossessive(
pattern: String,
capturesGroups: Boolean,
tracksConsumption: Boolean,
nonTrivialBacktracking: Boolean,
requireCheckpointing: Boolean
) {
for (q in listOf("", "?", "+")) {
assertInnerSetProperties(pattern + q, capturesGroups, tracksConsumption, nonTrivialBacktracking, requireCheckpointing)
}
}
checkEagerLazyAndPossessive("a*", false, false, true, false)
checkEagerLazyAndPossessive("a+", false, false, true, false)
checkEagerLazyAndPossessive("a?", false, false, true, false)
checkEagerLazyAndPossessive("a{1,}", false, false, true, false)
checkEagerLazyAndPossessive("a{1,3}", false, false, true, false)
// that's the only one that does not require complex backtracking
checkEagerLazyAndPossessive("a{3}", false, false, false, false)
assertInnerSetProperties(".*", false, false, true, false)
}
@Test
fun testGroups() {
assertInnerSetProperties("(a)", true, false, false, false)
assertInnerSetProperties("((a))", true, false, false, false)
assertInnerSetProperties("(?:a)", false, true, false, false)
// look behinds
assertInnerSetProperties("(?<=a)a", false, true, true, true)
assertInnerSetProperties("(?<!a)a", false, true, true, true)
// look ahead
assertInnerSetProperties("(?=a)a", false, false, true, true)
assertInnerSetProperties("(?!a)a", false, false, true, true)
// joint sets
assertInnerSetProperties("(a|aa)", true, false, true, false)
assertInnerSetProperties("(?:a|aa)", false, true, true, false)
}
@Test
fun testBackreference() {
assertInnerSetProperties("([ab])\\1\\1", true, true, true, false)
}
}