diff --git a/kotlin-native/backend.native/tests/harmony_regex/FindAllTest.kt b/kotlin-native/backend.native/tests/harmony_regex/FindAllTest.kt new file mode 100644 index 00000000000..ca4c9062599 --- /dev/null +++ b/kotlin-native/backend.native/tests/harmony_regex/FindAllTest.kt @@ -0,0 +1,70 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the LICENSE file. + */ + +package test.text.harmony_regex + +import kotlin.text.* +import kotlin.test.* + +class FindAllTest { + + internal fun Regex.allGroups(text: String) = + findAll(text).map { + it.groups.mapIndexed { index, it -> + "$index => ${it?.value}" + }.joinToString("; ") + }.toList() + + /** + * Tests regular expressions with lookbehind asserts. + */ + @Test fun testLookBehind() { + var regex: Regex + var result: List + + regex = "(?<=^/nl(?:/nl)?/\\d{1,600}[\\d+]{0,600}/[\\d+]{0,600})(\\d+)".toRegex() + result = regex.allGroups("/nl/nl/1+2/3+4/") + assertEquals(2, result.count()) + assertEquals("0 => 3; 1 => 3", result[0]) + assertEquals("0 => 4; 1 => 4", result[1]) + + regex = "abe(?<=[ab][!be](.|\\b))(=|t)".toRegex() + result = regex.allGroups("abet abe=") + assertEquals(2, result.count()) + assertEquals("0 => abet; 1 => e; 2 => t", result[0]) + assertEquals("0 => abe=; 1 => ; 2 => =", result[1]) + } + + /** + * Tests regular expressions with lookahead asserts. + */ + @Test fun testLookAheadBehind() { + var regex: Regex + var result: List + + regex = "a(?=b?)(\\w|)c".toRegex() + result = regex.allGroups("abcfgac") + assertEquals(2, result.count()) + assertEquals("0 => abc; 1 => b", result[0]) + assertEquals("0 => ac; 1 => ", result[1]) + + regex = "[a!](?=d|&)\\b[&d]".toRegex() + result = regex.allGroups("ada& !d!&") + assertEquals(2, result.count()) + assertEquals("0 => a&", result[0]) + assertEquals("0 => !d", result[1]) + + regex = "(?=ab)(a|^)b".toRegex() + result = regex.allGroups("abcab") + assertEquals(2, result.count()) + assertEquals("0 => ab; 1 => a", result[0]) + assertEquals("0 => ab; 1 => a", result[1]) + + regex = "(?=[a-k][a-z])(?=[a-d][c-x])[d-y][x-z]".toRegex() + result = regex.allGroups("abdydx") + assertEquals(1, result.count()) + assertEquals("0 => dx", result[0]) + } +} diff --git a/kotlin-native/runtime/src/main/kotlin/kotlin/text/regex/MatchResultImpl.kt b/kotlin-native/runtime/src/main/kotlin/kotlin/text/regex/MatchResultImpl.kt index 91d29ec75e3..4ee08d3eb56 100644 --- a/kotlin-native/runtime/src/main/kotlin/kotlin/text/regex/MatchResultImpl.kt +++ b/kotlin-native/runtime/src/main/kotlin/kotlin/text/regex/MatchResultImpl.kt @@ -56,6 +56,26 @@ constructor (internal val input: CharSequence, var previousMatch = -1 var mode = Regex.Mode.MATCH + private data class MatchResultState(val groupBounds: IntArray, val consumers: IntArray, val enterCounters: IntArray, + val startIndex: Int, val previousMatch: Int) + + private var state: MatchResultState? = null + + internal fun saveState() { + state = MatchResultState(groupBounds.copyOf(), consumers.copyOf(), enterCounters.copyOf(), startIndex, previousMatch) + } + + internal fun rollbackState(): Boolean { + return state?.let { + it.groupBounds.copyInto(groupBounds) + it.consumers.copyInto(consumers) + it.enterCounters.copyInto(enterCounters) + startIndex = it.startIndex + previousMatch = it.previousMatch + true + } ?: false + } + // MatchResult interface =========================================================================================== /** The range of indices in the original string where match was captured. */ override val range: IntRange diff --git a/kotlin-native/runtime/src/main/kotlin/kotlin/text/regex/sets/LookAheadSets.kt b/kotlin-native/runtime/src/main/kotlin/kotlin/text/regex/sets/LookAheadSets.kt index 4b26a0bb0b3..13b5ea2306a 100644 --- a/kotlin-native/runtime/src/main/kotlin/kotlin/text/regex/sets/LookAheadSets.kt +++ b/kotlin-native/runtime/src/main/kotlin/kotlin/text/regex/sets/LookAheadSets.kt @@ -25,10 +25,10 @@ package kotlin.text.regex /** * Positive lookahead node. */ -internal class PositiveLookAheadSet(children: List, fSet: FSet) : AtomicJointSet(children, fSet) { +internal class PositiveLookAheadSet(children: List, fSet: FSet) : LookAroundSet(children, fSet) { /** Returns startIndex+shift, the next position to match */ - override fun matches(startIndex: Int, testString: CharSequence, matchResult: MatchResultImpl): Int { + override fun tryToMatch(startIndex: Int, testString: CharSequence, matchResult: MatchResultImpl): Int { children.forEach { val shift = it.matches(startIndex, testString, matchResult) if (shift >= 0) { @@ -39,7 +39,6 @@ internal class PositiveLookAheadSet(children: List, fSet: FSet) : A return -1 } - override fun hasConsumed(matchResult: MatchResultImpl): Boolean = true override val name: String get() = "PositiveLookaheadJointSet" } @@ -47,10 +46,10 @@ internal class PositiveLookAheadSet(children: List, fSet: FSet) : A /** * Negative look ahead node. */ -internal class NegativeLookAheadSet(children: List, fSet: FSet) : AtomicJointSet(children, fSet) { +internal class NegativeLookAheadSet(children: List, fSet: FSet) : LookAroundSet(children, fSet) { /** Returns startIndex+shift, the next position to match */ - override fun matches(startIndex: Int, testString: CharSequence, matchResult: MatchResultImpl): Int { + override fun tryToMatch(startIndex: Int, testString: CharSequence, matchResult: MatchResultImpl): Int { children.forEach { if (it.matches(startIndex, testString, matchResult) >= 0) { return -1 @@ -60,7 +59,6 @@ internal class NegativeLookAheadSet(children: List, fSet: FSet) : A return next.matches(startIndex, testString, matchResult) } - override fun hasConsumed(matchResult: MatchResultImpl): Boolean = true override val name: String get() = "NegativeLookaheadJointSet" } diff --git a/kotlin-native/runtime/src/main/kotlin/kotlin/text/regex/sets/LookAroundSet.kt b/kotlin-native/runtime/src/main/kotlin/kotlin/text/regex/sets/LookAroundSet.kt new file mode 100644 index 00000000000..7316d6689df --- /dev/null +++ b/kotlin-native/runtime/src/main/kotlin/kotlin/text/regex/sets/LookAroundSet.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the LICENSE file. + */ + + +package kotlin.text.regex + +/** + * Abstract class for lookahead and lookbehind nodes. + */ +internal abstract class LookAroundSet(children: List, fSet: FSet) : AtomicJointSet(children, fSet) { + protected abstract fun tryToMatch(startIndex: Int, testString: CharSequence, matchResult: MatchResultImpl): Int + + /** Returns startIndex+shift, the next position to match */ + override fun matches(startIndex: Int, testString: CharSequence, matchResult: MatchResultImpl): Int { + matchResult.saveState() + return tryToMatch(startIndex, testString, matchResult).also { if (it < 0) matchResult.rollbackState() } + } + + override fun hasConsumed(matchResult: MatchResultImpl): Boolean = true +} \ No newline at end of file diff --git a/kotlin-native/runtime/src/main/kotlin/kotlin/text/regex/sets/LookBehindSets.kt b/kotlin-native/runtime/src/main/kotlin/kotlin/text/regex/sets/LookBehindSets.kt index 47da005ecb5..6611e8c6682 100644 --- a/kotlin-native/runtime/src/main/kotlin/kotlin/text/regex/sets/LookBehindSets.kt +++ b/kotlin-native/runtime/src/main/kotlin/kotlin/text/regex/sets/LookBehindSets.kt @@ -25,11 +25,10 @@ package kotlin.text.regex /** * Positive lookbehind node. */ -internal class PositiveLookBehindSet(children: List, fSet: FSet) : AtomicJointSet(children, fSet) { +internal class PositiveLookBehindSet(children: List, fSet: FSet) : LookAroundSet(children, fSet) { /** Returns startIndex+shift, the next position to match */ - override fun matches(startIndex: Int, testString: CharSequence, matchResult: MatchResultImpl): Int { - + override fun tryToMatch(startIndex: Int, testString: CharSequence, matchResult: MatchResultImpl): Int { matchResult.setConsumed(groupIndex, startIndex) children.forEach { if (it.findBack(0, startIndex, testString, matchResult) >= 0) { @@ -41,7 +40,6 @@ internal class PositiveLookBehindSet(children: List, fSet: FSet) : return -1 } - override fun hasConsumed(matchResult: MatchResultImpl): Boolean = true override val name: String get() = "PositiveBehindJointSet" } @@ -49,10 +47,10 @@ internal class PositiveLookBehindSet(children: List, fSet: FSet) : /** * Negative look behind node. */ -internal class NegativeLookBehindSet(children: List, fSet: FSet) : AtomicJointSet(children, fSet) { +internal class NegativeLookBehindSet(children: List, fSet: FSet) : LookAroundSet(children, fSet) { /** Returns startIndex+shift, the next position to match */ - override fun matches(startIndex: Int, testString: CharSequence, matchResult: MatchResultImpl): Int { + override fun tryToMatch(startIndex: Int, testString: CharSequence, matchResult: MatchResultImpl): Int { matchResult.setConsumed(groupIndex, startIndex) children.forEach { @@ -65,7 +63,6 @@ internal class NegativeLookBehindSet(children: List, fSet: FSet) : return next.matches(startIndex, testString, matchResult) } - override fun hasConsumed(matchResult: MatchResultImpl): Boolean = true override val name: String get() = "NegativeBehindJointSet" }