Skip to content

Commit 37dfdf1

Browse files
committed
Added regex preprocessor to handle java COMMENTS flag behavior.
1 parent 5470ba4 commit 37dfdf1

2 files changed

Lines changed: 105 additions & 2 deletions

File tree

core/src/main/kotlin/org/evomaster/core/parser/RegexHandler.kt

Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package org.evomaster.core.parser
33
import org.antlr.v4.runtime.*
44
import org.antlr.v4.runtime.misc.ParseCancellationException
55
import org.evomaster.core.search.gene.regex.RegexGene
6+
import org.evomaster.core.utils.ParsedFlagExpression
67
import org.evomaster.core.utils.RegexFlags
78
import org.evomaster.core.utils.RegexWithExternalFlags
89

@@ -33,7 +34,9 @@ object RegexHandler {
3334
return cacheJVM[key]!!.copy() as RegexGene
3435
}
3536

36-
val stream = CharStreams.fromString(regex)
37+
val preprocessedRegex = preprocessCommentsForJavaRegex(regex, externalRegexFlags)
38+
39+
val stream = CharStreams.fromString(preprocessedRegex)
3740
val lexer = RegexJavaLexer(stream)
3841
val tokenStream = prepareLexer(lexer)
3942
val parser = RegexJavaParser(tokenStream)
@@ -48,6 +51,107 @@ object RegexHandler {
4851
return gene
4952
}
5053

54+
/**
55+
* This function handles comments and whitespace for Java regex, striping them when the "x" flag is on.
56+
*/
57+
private fun preprocessCommentsForJavaRegex(regex: String, externalRegexFlags: RegexFlags): String {
58+
val result = StringBuilder(regex.length)
59+
val scopeStack = ArrayDeque<RegexFlags>() // stack of flags per level
60+
var currentFlags = externalRegexFlags
61+
var i = 0
62+
63+
while (i < regex.length) {
64+
val c = regex[i]
65+
when {
66+
// backslash escape
67+
c == '\\' && i + 1 < regex.length -> {
68+
when {
69+
regex[i+1] == 'Q' -> {
70+
// \Q...\E quote block, copy everything
71+
result.append('\\'); result.append('Q')
72+
i += 2
73+
while (i < regex.length) {
74+
if (regex[i] == '\\' && i+1 < regex.length && regex[i+1] == 'E') {
75+
result.append('\\'); result.append('E')
76+
i += 2; break
77+
}
78+
result.append(regex[i++])
79+
}
80+
}
81+
else -> {
82+
// regular escape, copy both
83+
result.append(c); result.append(regex[i+1])
84+
i += 2
85+
}
86+
}
87+
}
88+
89+
// opening paren: check for flag group or scope
90+
c == '(' && i+1 < regex.length && regex[i+1] == '?' -> {
91+
// scan forward to find the flag content
92+
val flagStart = i + 2
93+
var j = flagStart
94+
// lookahead to end of group/scope/other, set j to that position
95+
while (j < regex.length && regex[j] != ':' && regex[j] != ')' && regex[j] != '(') j++
96+
97+
// check if regex[i..j] forms valid flag scope/group
98+
if (j < regex.length && (regex[j] == ':' || regex[j] == ')') && j > i+2
99+
&& regex.substring(i+2, j).all{ it in RegexFlags.validFlagCharacters || it == '-' }) {
100+
// valid flag group/scope
101+
if(regex[j] == ':') {
102+
// flag group (?flags:...): parse flags and push scope
103+
val flagToken = regex.substring(i, j+1) // e.g. "(?iu:"
104+
val newFlags = currentFlags.merge(ParsedFlagExpression.fromFlagToken(flagToken))
105+
scopeStack.addLast(currentFlags)
106+
currentFlags = newFlags
107+
result.append(regex.substring(i, j+1))
108+
i = j + 1
109+
} else {
110+
// flag scope (?flags), update currentFlags
111+
val flagToken = regex.substring(i, j+1) // e.g. "(?iu)"
112+
currentFlags = currentFlags.merge(ParsedFlagExpression.fromFlagToken(flagToken))
113+
result.append(regex.substring(i, j+1))
114+
i = j + 1
115+
}
116+
} else {
117+
// not a flag group/scope: push current flags unchanged
118+
scopeStack.addLast(currentFlags)
119+
result.append(c); i++
120+
}
121+
}
122+
123+
c == '(' -> {
124+
scopeStack.addLast(currentFlags)
125+
result.append(c); i++
126+
}
127+
128+
c == ')' -> {
129+
currentFlags = scopeStack.removeLastOrNull() ?: externalRegexFlags
130+
result.append(c); i++
131+
}
132+
133+
// comment
134+
c == '#' && currentFlags.comments -> {
135+
i++
136+
while (i < regex.length && !currentFlags.isLineTerminator(regex[i])) i++
137+
// consume line terminator
138+
if (i < regex.length) {
139+
// \r\n is a 2-character line terminator
140+
if (regex[i] == '\r' && i+1 < regex.length && regex[i+1] == '\n') i += 2
141+
else i++
142+
}
143+
}
144+
145+
// whitespace, skip when comments flag is on
146+
c.isWhitespace() && currentFlags.comments -> i++
147+
148+
// else copy
149+
else -> { result.append(c); i++ }
150+
}
151+
}
152+
return result.toString()
153+
}
154+
51155
/**
52156
* Given a ECMA262 regex string, generate RegexGene for it.
53157
* Based on RegexEcma262.g4 file.

core/src/main/kotlin/org/evomaster/core/utils/RegexFlags.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,6 @@ data class RegexFlags(
170170
if (multiline) throw IllegalStateException("Regex flag 'm' (MULTILINE) is not yet supported")
171171
if (unixLines) throw IllegalStateException("Regex flag 'd' (UNIX_LINES) is not yet supported")
172172
if (unicodeCharacterClass) throw IllegalStateException("Regex flag 'U' (UNICODE_CHARACTER_CLASS) is not yet supported")
173-
if (comments) throw IllegalStateException("Regex flag 'x' (COMMENTS) is not yet supported")
174173
}
175174

176175
/**

0 commit comments

Comments
 (0)