[K/N][stdlib] Support \V, \v, \H, \h, \R in regex engine
Issue #KT-50742 Fixed
This commit is contained in:
@@ -300,6 +300,46 @@ internal abstract class AbstractCharClass : SpecialToken() {
|
||||
override fun computeValue(): AbstractCharClass = CharClass().add('0', '9').add('a', 'f').add('A', 'F')
|
||||
}
|
||||
|
||||
// From Java 8+ `Pattern` doc: \v - A vertical whitespace character: [\n\x0B\f\r\x85\u2028\u2029]
|
||||
internal class CachedVerticalWhitespace : CachedCharClass() {
|
||||
init {
|
||||
initValues()
|
||||
}
|
||||
override fun computeValue(): AbstractCharClass =
|
||||
CharClass().addAll(listOf('\n', '\u000B', '\u000C' /* aka \f */, '\r', '\u0085', '\u2028', '\u2029'))
|
||||
}
|
||||
|
||||
// From Java 8+ `Pattern` doc: \V - A non-vertical whitespace character: [^\v]
|
||||
internal class CachedNonVerticalWhitespace: CachedCharClass() {
|
||||
init {
|
||||
initValues()
|
||||
}
|
||||
override fun computeValue(): AbstractCharClass =
|
||||
CachedVerticalWhitespace().getValue(negative = true).apply { mayContainSupplCodepoints = true }
|
||||
// TODO: Does it match a pair of surrogates? Do we really need mayContainSupplCodepoints?
|
||||
}
|
||||
|
||||
// From Java 8+ `Pattern` doc:
|
||||
// \h - A horizontal whitespace character: [ \t\xA0\u1680\u180e\u2000-\u200a\u202f\u205f\u3000]
|
||||
internal class CachedHorizontalWhitespace: CachedCharClass() {
|
||||
init {
|
||||
initValues()
|
||||
}
|
||||
override fun computeValue(): AbstractCharClass =
|
||||
CharClass().addAll(listOf(' ', '\t', '\u00A0', '\u1680', '\u180e', '\u202f', '\u205f', '\u3000'))
|
||||
.add('\u2000', '\u200a')
|
||||
}
|
||||
|
||||
// From Java 8+ `Pattern` doc:
|
||||
// \H - A non-horizontal whitespace character: [^\h]
|
||||
internal class CachedNonHorizontalWhitespace: CachedCharClass() {
|
||||
init {
|
||||
initValues()
|
||||
}
|
||||
override fun computeValue(): AbstractCharClass =
|
||||
CachedHorizontalWhitespace().getValue(negative = true).apply { mayContainSupplCodepoints = true }
|
||||
}
|
||||
|
||||
internal class CachedRange(var start: Int, var end: Int) : CachedCharClass() {
|
||||
init {
|
||||
initValues()
|
||||
@@ -392,6 +432,10 @@ internal abstract class AbstractCharClass : SpecialToken() {
|
||||
NON_SPACE("S", ::CachedNonSpace),
|
||||
DIGIT_SHORT("d", ::CachedDigit),
|
||||
NON_DIGIT("D", ::CachedNonDigit),
|
||||
VERTICAL_WHITESPACE("v", ::CachedVerticalWhitespace),
|
||||
NON_VERTICAL_WHITESPACE("V", ::CachedNonVerticalWhitespace),
|
||||
HORIZONTAL_WHITESPACE("h", ::CachedHorizontalWhitespace),
|
||||
NON_HORIZONTAL_WHITESPACE("H", ::CachedNonHorizontalWhitespace),
|
||||
BASIC_LATIN("BasicLatin", { CachedRange(0x0000, 0x007F) }),
|
||||
LATIN1_SUPPLEMENT("Latin-1Supplement", { CachedRange(0x0080, 0x00FF) }),
|
||||
LATIN_EXTENDED_A("LatinExtended-A", { CachedRange(0x0100, 0x017F) }),
|
||||
|
||||
@@ -244,6 +244,16 @@ internal class CharClass(val ignoreCase: Boolean = false, negative: Boolean = fa
|
||||
|
||||
fun add(start: Char, end: Char): CharClass = add(start.toInt(), end.toInt())
|
||||
|
||||
fun addAll(chars: Iterable<Char>): CharClass {
|
||||
chars.forEach { add(it) }
|
||||
return this
|
||||
}
|
||||
|
||||
fun addAll(chars: Iterable<Int>): CharClass {
|
||||
chars.forEach { add(it) }
|
||||
return this
|
||||
}
|
||||
|
||||
// OR operation
|
||||
fun union(another: AbstractCharClass) {
|
||||
if (!mayContainSupplCodepoints && another.mayContainSupplCodepoints) {
|
||||
|
||||
@@ -435,7 +435,7 @@ internal class Lexer(val patternString: String, flags: Int) {
|
||||
}
|
||||
|
||||
// Word/whitespace/digit.
|
||||
'w', 's', 'd', 'W', 'S', 'D' -> {
|
||||
'w', 's', 'd', 'W', 'S', 'D', 'v', 'V', 'h', 'H' -> {
|
||||
lookAheadSpecialToken = AbstractCharClass.getPredefinedClass(
|
||||
pattern.concatToString(prevNonWhitespaceIndex, prevNonWhitespaceIndex + 1),
|
||||
false
|
||||
@@ -477,6 +477,7 @@ internal class Lexer(val patternString: String, flags: Int) {
|
||||
'G' -> lookAhead = CHAR_PREVIOUS_MATCH
|
||||
'Z' -> lookAhead = CHAR_END_OF_LINE
|
||||
'z' -> lookAhead = CHAR_END_OF_INPUT
|
||||
'R' -> lookAhead = CHAR_LINEBREAK
|
||||
|
||||
// \cx - A control character corresponding to x.
|
||||
'c' -> {
|
||||
@@ -488,7 +489,7 @@ internal class Lexer(val patternString: String, flags: Int) {
|
||||
}
|
||||
}
|
||||
|
||||
'C', 'E', 'F', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'R', 'T', 'U', 'V', 'X', 'Y', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'o', 'q', 'y' ->
|
||||
'C', 'E', 'F', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'T', 'U', 'X', 'Y', 'g', 'i', 'j', 'k', 'l', 'm', 'o', 'q', 'y' ->
|
||||
throw PatternSyntaxException("Illegal escape sequence", patternString, curTokenIndex)
|
||||
}
|
||||
return false
|
||||
@@ -704,6 +705,7 @@ internal class Lexer(val patternString: String, flags: Int) {
|
||||
val CHAR_PREVIOUS_MATCH = 0x80000000.toInt() or 'G'.toInt()
|
||||
val CHAR_END_OF_INPUT = 0x80000000.toInt() or 'z'.toInt()
|
||||
val CHAR_END_OF_LINE = 0x80000000.toInt() or 'Z'.toInt()
|
||||
val CHAR_LINEBREAK = 0x80000000.toInt() or 'R'.toInt()
|
||||
|
||||
// Quantifier modes.
|
||||
val QMOD_GREEDY = 0xe0000000.toInt()
|
||||
|
||||
@@ -533,11 +533,27 @@ internal class Pattern(val pattern: String, flags: Int = 0) {
|
||||
term = EOLSet(consumersCount++, AbstractLineTerminator.getInstance(flags))
|
||||
}
|
||||
|
||||
Lexer.CHAR_START_OF_INPUT -> { // Start if an input: \A
|
||||
Lexer.CHAR_START_OF_INPUT -> { // Start of an input: \A
|
||||
lexemes.next()
|
||||
term = SOLSet(AbstractLineTerminator.getInstance(flags))
|
||||
}
|
||||
|
||||
Lexer.CHAR_LINEBREAK -> {
|
||||
// Any unicode linebreak sequence:
|
||||
// \u000D\u000A|[\u000A\u000B\u000C\u000D\u0085\u2028\u2029]
|
||||
lexemes.next()
|
||||
val fSet = NonCapFSet(consumersCount++)
|
||||
val lineBreakSequence = SequenceSet("\u000D\u000A").apply {
|
||||
next = fSet
|
||||
}
|
||||
val lineBreakChars = RangeSet(
|
||||
CharClass().addAll(listOf('\u000A', '\u000B', '\u000C', '\u000D', '\u0085', '\u2028', '\u2029'))
|
||||
).apply {
|
||||
next = fSet
|
||||
}
|
||||
term = NonCapturingJointSet(listOf(lineBreakSequence, lineBreakChars), fSet)
|
||||
}
|
||||
|
||||
Lexer.CHAR_PREVIOUS_MATCH -> { // A previous match: \G
|
||||
lexemes.next()
|
||||
term = PreviousMatchSet()
|
||||
|
||||
Reference in New Issue
Block a user