@@ -36,14 +36,22 @@ class GeneRegexJavaVisitor(externalRegexFlags: RegexFlags = RegexFlags()) : Rege
3636 * Capture groups in order of appearance (1-based index -> list index 0).
3737 * Populated as the tree is walked. A backreference is only valid if it
3838 * appears after the group it references, which Java regex requires anyway.
39+ * The value is nullable to represent a captured group that is unsatisfiable,
40+ * for example when the group contains an empty character class like `([a&&b])`.
41+ * In that case the map holds null instead of a DisjunctionListRxGene.
42+ * @see buildDisjunctionList
3943 */
4044 private val captureGroups = mutableListOf<DisjunctionListRxGene ?>()
4145
4246 /* *
4347 * Same as [captureGroups] but for named backreferences, which can be accessed
4448 * with their name or number.
49+ * The value is nullable to represent a captured group that is unsatisfiable,
50+ * for example when the group contains an empty character class like `([a&&b])`.
51+ * In that case the map holds null instead of a DisjunctionListRxGene.
52+ * @see buildDisjunctionList
4553 */
46- private val namedCaptureGroups = mutableMapOf<String , DisjunctionListRxGene >()
54+ private val namedCaptureGroups = mutableMapOf<String , DisjunctionListRxGene ? >()
4755
4856 /* *
4957 * Tracks the flags active in the current lexical scope.
@@ -52,13 +60,48 @@ class GeneRegexJavaVisitor(externalRegexFlags: RegexFlags = RegexFlags()) : Rege
5260 */
5361 private var currentFlags = externalRegexFlags
5462
63+ /* *
64+ * Builds DisjunctionListRxGenes from a disjunction context, returns null if disjunction is unsatisfiable.
65+ */
66+ private fun buildDisjunctionList (ctx : RegexJavaParser .DisjunctionContext ): DisjunctionListRxGene ? {
67+ val res = ctx.accept(this )
68+ val validDisjunctions = res.genes.map { it as DisjunctionRxGene }
69+
70+ val satisfiableDisjunctions = validDisjunctions.filter{ ! it.isUnsatisfiable() }
71+
72+ if (satisfiableDisjunctions.isEmpty()){
73+ // As DisjunctionListRxGene extends CompositeFixedGene, its disjunctions list cannot be empty.
74+ // In this case we return null to represent an unsatisfiable DisjunctionListRxGene.
75+ return null
76+ }
77+
78+ val disjList = DisjunctionListRxGene (satisfiableDisjunctions)
79+
80+ // TODO tmp hack until full handling of ^$. Assume full match when nested disjunctions
81+ for (gene in disjList.disjunctions) {
82+ gene.extraPrefix = false
83+ gene.extraPostfix = false
84+ gene.matchStart = true
85+ gene.matchEnd = true
86+ }
87+ return disjList
88+ }
89+
5590 override fun visitPattern (ctx : RegexJavaParser .PatternContext ): VisitResult {
5691
5792 val res = ctx.disjunction().accept(this )
5893
5994 val text = RegexUtils .getRegexExpByParserRuleContext(ctx)
6095
61- val disjList = DisjunctionListRxGene (res.genes.map { it as DisjunctionRxGene })
96+ val satisfiableDisjunctions = res.genes
97+ .map { it as DisjunctionRxGene }
98+ .filter{ ! it.isUnsatisfiable() }
99+
100+ if (satisfiableDisjunctions.isEmpty()) {
101+ throw IllegalStateException (" Regex is unsatisfiable." )
102+ }
103+
104+ val disjList = DisjunctionListRxGene (satisfiableDisjunctions)
62105
63106 // we remove the <EOF> token from end of the string to store as sourceRegex
64107 val gene = RegexGene (
@@ -79,9 +122,19 @@ class GeneRegexJavaVisitor(externalRegexFlags: RegexFlags = RegexFlags()) : Rege
79122 val matchStart = assertionMatches.first
80123 val matchEnd = assertionMatches.second
81124
82- val disj = DisjunctionRxGene (" disj" , altRes.genes.map { it }, matchStart, matchEnd)
125+ val res = VisitResult ()
126+
127+ // add disjunction if it has genes, OR if the alternative was purely assertions (^$) or flag scopes
128+ // in that case altRes.genes is empty but the alternative is valid (matches "")
129+ val hasOnlyAssertionsOrFlagScopes = ctx.alternative().term().isNotEmpty() &&
130+ ctx.alternative().term().all { it.assertion() != null || it.FLAG_SCOPE_OPEN () != null }
83131
84- val res = VisitResult (disj)
132+ if (altRes.genes.isNotEmpty() || hasOnlyAssertionsOrFlagScopes || ctx.alternative().term().isEmpty()) {
133+ val disj = DisjunctionRxGene (" disj" , altRes.genes.map { it }, matchStart, matchEnd)
134+
135+ res.genes.add(disj)
136+ }
137+ // else: had non-assertion terms but all produced nothing (empty char class etc.), skip
85138
86139 if (ctx.disjunction() != null ){
87140 val disjRes = ctx.disjunction().accept(this )
@@ -147,7 +200,7 @@ class GeneRegexJavaVisitor(externalRegexFlags: RegexFlags = RegexFlags()) : Rege
147200 // term is not a back ref: we use the default behavior, term results may only have 0-1 genes
148201 // if there is a gene, we add it to result
149202 res.genes.add(gene)
150- } else {
203+ } else if (resTerm.data is String ) {
151204
152205 val assertion = resTerm.data as String
153206 if (i== 0 && assertion == " ^" ){
@@ -162,6 +215,9 @@ class GeneRegexJavaVisitor(externalRegexFlags: RegexFlags = RegexFlags()) : Rege
162215 */
163216 throw IllegalStateException (" Cannot support $assertion at position $i " )
164217 }
218+ } else {
219+ // unsatisfiable term, return with no genes
220+ return VisitResult (data= Pair (false , false ))
165221 }
166222 }
167223
@@ -181,12 +237,23 @@ class GeneRegexJavaVisitor(externalRegexFlags: RegexFlags = RegexFlags()) : Rege
181237
182238 val resAtom = ctx.atom().accept(this )
183239 val atom = resAtom.genes.firstOrNull()
184- ? : return res
185240
186241 if (ctx.quantifier() != null ){
187242
188243 val limits = ctx.quantifier().accept(this ).data as Pair <Int ,Int >
189244
245+ // if quantified atom is unsatisfiable we must then check the limits
246+ if (atom == null ||
247+ ((atom as ? RxTerm )?.isUnsatisfiable() == true ) && resAtom.genes.size == 1 ) {
248+ return if (limits.first == 0 ) {
249+ // if 0 appearances is allowed then the regex is satisfiable only with empty string
250+ VisitResult (PatternCharacterBlockGene (" 0_QuantifierOnEmptyRegex" , " " ))
251+ } else {
252+ // if not then unsatisfiable, return with no genes
253+ res
254+ }
255+ }
256+
190257 // if atom is not a back ref then we use the default behavior, results may only have one gene
191258 var template: Gene = atom
192259
@@ -211,10 +278,11 @@ class GeneRegexJavaVisitor(externalRegexFlags: RegexFlags = RegexFlags()) : Rege
211278 if (ctx.atom()?.atomEscape()?.BackReference () != null ){
212279 // if atom is a BackReference we addAll genes from result as there may be more than one if digits are dropped
213280 res.genes.addAll(resAtom.genes)
214- } else {
281+ } else if (atom != null ) {
215282 // if atom is not a back ref we fall back to the default behavior, results only have one gene
216283 res.genes.add(atom)
217284 }
285+ // else atom is unsatisfiable, return no genes
218286 }
219287
220288 return res
@@ -284,21 +352,16 @@ class GeneRegexJavaVisitor(externalRegexFlags: RegexFlags = RegexFlags()) : Rege
284352
285353 currentFlags = merged
286354
287- val res = ctx.disjunction().accept( this )
355+ val disjList = buildDisjunctionList( ctx.disjunction())
288356
289357 currentFlags = previous
290358
291- val disjList = DisjunctionListRxGene (res.genes.map { it as DisjunctionRxGene })
292-
293- // TODO tmp hack until full handling of ^$. Assume full match when nested disjunctions
294- for (gene in disjList.disjunctions) {
295- gene.extraPrefix = false
296- gene.extraPostfix = false
297- gene.matchStart = true
298- gene.matchEnd = true
359+ return if (disjList != null ) {
360+ VisitResult (disjList)
361+ } else {
362+ // unsatisfiable, return with no genes.
363+ VisitResult ()
299364 }
300-
301- return VisitResult (disjList)
302365 }
303366
304367 if (ctx.quote() != null ){
@@ -332,17 +395,7 @@ class GeneRegexJavaVisitor(externalRegexFlags: RegexFlags = RegexFlags()) : Rege
332395 val groupIndex = captureGroups.size
333396 captureGroups.add(null ) // add placeholder for the gene
334397
335- val res = ctx.disjunction().accept(this )
336-
337- val disjList = DisjunctionListRxGene (res.genes.map { it as DisjunctionRxGene })
338-
339- // TODO tmp hack until full handling of ^$. Assume full match when nested disjunctions
340- for (gene in disjList.disjunctions){
341- gene.extraPrefix = false
342- gene.extraPostfix = false
343- gene.matchStart = true
344- gene.matchEnd = true
345- }
398+ val disjList = buildDisjunctionList(ctx.disjunction())
346399
347400 val isCapturingGroup = ! ctx.text.startsWith(" (?:" )
348401 val isNamedCaptureGroup = ctx.NAMED_CAPTURE_GROUP_OPEN () != null
@@ -358,7 +411,12 @@ class GeneRegexJavaVisitor(externalRegexFlags: RegexFlags = RegexFlags()) : Rege
358411 namedCaptureGroups[name] = disjList
359412 }
360413
361- return VisitResult (disjList)
414+ return if (disjList != null ) {
415+ VisitResult (disjList)
416+ } else {
417+ // unsatisfiable, return with no genes.
418+ VisitResult ()
419+ }
362420 }
363421
364422 if (ctx.DOT () != null ){
@@ -577,15 +635,12 @@ class GeneRegexJavaVisitor(externalRegexFlags: RegexFlags = RegexFlags()) : Rege
577635 maxDigits > allDigits.length -> allDigits.length
578636 allDigits.take(maxDigits).toInt() <= captureGroups.size -> maxDigits
579637 maxDigits > 1 -> maxDigits - 1
580- else -> throw IllegalStateException (
581- " Backreference ${txt.take(2 )} refers to group ${allDigits[0 ]} but only ${captureGroups.size} " +
582- " capture group(s) have been defined so far"
583- )
638+ else -> 1
584639 }
585640
586641 val n = allDigits.take(backRefDigitCount).toInt()
587642
588- val result = VisitResult (BackReferenceRxGene (n, captureGroups[ n - 1 ] !! ))
643+ val result = VisitResult (BackReferenceRxGene (n, captureGroups.getOrNull( n - 1 ) ))
589644
590645 val remainingChars = allDigits.drop(backRefDigitCount)
591646
@@ -601,8 +656,10 @@ class GeneRegexJavaVisitor(externalRegexFlags: RegexFlags = RegexFlags()) : Rege
601656 if (ctx.NamedBackReference () != null ) {
602657 // strip "\k<" and ">"
603658 val name = txt.drop(3 ).dropLast(1 )
659+ if (name !in namedCaptureGroups){
660+ throw IllegalStateException (" Named backreference \\ k<$name > refers to unknown group '$name '" )
661+ }
604662 val group = namedCaptureGroups[name]
605- ? : throw IllegalStateException (" Named backreference \\ k<$name > refers to unknown group '$name '" )
606663 val groupIndex = captureGroups.indexOf(group) + 1 // 1-based, for the gene name
607664 return VisitResult (BackReferenceRxGene (groupIndex, group))
608665 }
0 commit comments