From 8d0d6d1bf3601af7b197ebafd2a8a31a7ceb75c3 Mon Sep 17 00:00:00 2001 From: Ilya Gorbunov Date: Thu, 15 Nov 2018 17:47:06 +0300 Subject: [PATCH] Do not mutate Matcher in MatchResult.next() Instead of mutating the matcher create a new one when `next()` is called. This allows getting named groups from that matcher later. Add lookbehind in matchSequence test to ensure this change doesn't alter the existing behavior. Also fixes #KT-20865 --- libraries/stdlib/jdk8/test/text/RegexTest.kt | 14 ++++++++--- .../stdlib/jvm/src/kotlin/text/regex/Regex.kt | 4 +-- .../stdlib/jvm/test/text/RegexJVMTest.kt | 25 ++++++++++++++++--- 3 files changed, 33 insertions(+), 10 deletions(-) diff --git a/libraries/stdlib/jdk8/test/text/RegexTest.kt b/libraries/stdlib/jdk8/test/text/RegexTest.kt index f3d13fc556a..f8dc62defd3 100644 --- a/libraries/stdlib/jdk8/test/text/RegexTest.kt +++ b/libraries/stdlib/jdk8/test/text/RegexTest.kt @@ -21,22 +21,28 @@ import kotlin.test.* class RegexTest { @Test fun namedGroups() { - val input = "1a 2b 3c" - val regex = "(?\\d)(?\\w)".toRegex() + val input = "1a 2b 3" + val regex = "(?\\d)(?\\w)?".toRegex() val matches = regex.findAll(input).toList() assertTrue(matches.all { it.groups.size == 3 }) - val m1 = matches[0] + val (m1, m2, m3) = matches assertEquals("1", m1.groups["num"]?.value) assertEquals(0..0, m1.groups["num"]?.range) assertEquals("a", m1.groups["liter"]?.value) assertEquals(1..1, m1.groups["liter"]?.range) - val m2 = matches[1] assertEquals("2", m2.groups["num"]?.value) assertEquals(3..3, m2.groups["num"]?.range) assertEquals("b", m2.groups["liter"]?.value) assertEquals(4..4, m2.groups["liter"]?.range) + + assertEquals("3", m3.groups["num"]?.value) + assertNull(m3.groups["liter"]) + + assertFailsWith { m2.groups["unknown_group"] }.let { e -> + assertTrue("unknown_group" in e.message!!) + } } } diff --git a/libraries/stdlib/jvm/src/kotlin/text/regex/Regex.kt b/libraries/stdlib/jvm/src/kotlin/text/regex/Regex.kt index 7e17864a886..0ed232674ac 100644 --- a/libraries/stdlib/jvm/src/kotlin/text/regex/Regex.kt +++ b/libraries/stdlib/jvm/src/kotlin/text/regex/Regex.kt @@ -263,7 +263,7 @@ private fun Matcher.matchEntire(input: CharSequence): MatchResult? { } private class MatcherMatchResult(private val matcher: Matcher, private val input: CharSequence) : MatchResult { - private val matchResult = matcher.toMatchResult() + private val matchResult: java.util.regex.MatchResult get() = matcher override val range: IntRange get() = matchResult.range() override val value: String @@ -302,7 +302,7 @@ private class MatcherMatchResult(private val matcher: Matcher, private val input override fun next(): MatchResult? { val nextIndex = matchResult.end() + if (matchResult.end() == matchResult.start()) 1 else 0 - return if (nextIndex <= input.length) matcher.findNext(nextIndex, input) else null + return if (nextIndex <= input.length) matcher.pattern().matcher(input).findNext(nextIndex, input) else null } } diff --git a/libraries/stdlib/jvm/test/text/RegexJVMTest.kt b/libraries/stdlib/jvm/test/text/RegexJVMTest.kt index eb4a81b0565..24485c6b733 100644 --- a/libraries/stdlib/jvm/test/text/RegexJVMTest.kt +++ b/libraries/stdlib/jvm/test/text/RegexJVMTest.kt @@ -14,12 +14,14 @@ import java.util.regex.Pattern class RegexJVMTest { @Test fun matchGroups() { - val input = "1a 2b 3c" - val regex = "(\\d)(\\w)".toRegex() + val input = "1a 2b 3" + val regex = "(\\d)(\\w)?".toRegex() val matches = regex.findAll(input).toList() assertTrue(matches.all { it.groups.size == 3 }) - val m1 = matches[0] + + val (m1, m2, m3) = matches + assertEquals("1a", m1.groups[0]?.value) assertEquals(0..1, m1.groups[0]?.range) assertEquals("1", m1.groups[1]?.value) @@ -27,13 +29,28 @@ class RegexJVMTest { assertEquals("a", m1.groups[2]?.value) assertEquals(1..1, m1.groups[2]?.range) - val m2 = matches[1] assertEquals("2", m2.groups[1]?.value) assertEquals(3..3, m2.groups[1]?.range) assertEquals("b", m2.groups[2]?.value) assertEquals(4..4, m2.groups[2]?.range) + + assertEquals("3", m3.groups[1]?.value) + assertNull(m3.groups[2]) } + @Test fun matchSequenceWithLookbehind() { + val input = "123_000 456 789" + val pattern = "(?<=^|\\s)\\d+_?".toRegex() + + val matches = pattern.findAll(input) + val values = matches.map { it.value } + val expected = listOf("123_", "456", "789") + assertEquals(expected, values.toList()) + assertEquals(expected, values.toList(), "running match sequence second time") + assertEquals(expected.drop(1), pattern.findAll(input, startIndex = 3).map { it.value }.toList()) + + assertEquals(listOf(0..3, 8..10, 12..14), matches.map { it.range }.toList()) + } private fun compareRegex(expected: Regex, actual: Regex) = compare(expected, actual) { propertyEquals(Regex::pattern)