@@ -40,14 +40,22 @@ class GeneRegexJavaVisitor(externalRegexFlags: RegexFlags = RegexFlags()) : Rege
4040 * Capture groups in order of appearance (1-based index -> list index 0).
4141 * Populated as the tree is walked. A backreference is only valid if it
4242 * appears after the group it references, which Java regex requires anyway.
43+ * The value is nullable to represent a captured group that is unsatisfiable,
44+ * for example when the group contains an empty character class like `([a&&b])`.
45+ * In that case the map holds null instead of a DisjunctionListRxGene.
46+ * @see buildDisjunctionList
4347 */
4448 private val captureGroups = mutableListOf<DisjunctionListRxGene ?>()
4549
4650 /* *
4751 * Same as [captureGroups] but for named backreferences, which can be accessed
4852 * with their name or number.
53+ * The value is nullable to represent a captured group that is unsatisfiable,
54+ * for example when the group contains an empty character class like `([a&&b])`.
55+ * In that case the map holds null instead of a DisjunctionListRxGene.
56+ * @see buildDisjunctionList
4957 */
50- private val namedCaptureGroups = mutableMapOf<String , DisjunctionListRxGene >()
58+ private val namedCaptureGroups = mutableMapOf<String , DisjunctionListRxGene ? >()
5159
5260 /* *
5361 * Tracks the flags active in the current lexical scope.
@@ -74,14 +82,48 @@ class GeneRegexJavaVisitor(externalRegexFlags: RegexFlags = RegexFlags()) : Rege
7482 )
7583 }
7684
85+ /* *
86+ * Builds DisjunctionListRxGenes from a disjunction context, returns null if disjunction is unsatisfiable.
87+ */
88+ private fun buildDisjunctionList (ctx : RegexJavaParser .DisjunctionContext ): DisjunctionListRxGene ? {
89+ val res = ctx.accept(this )
90+ val validDisjunctions = res.genes.map { it as DisjunctionRxGene }
91+
92+ val satisfiableDisjunctions = validDisjunctions.filter{ ! it.isUnsatisfiable() }
93+
94+ if (satisfiableDisjunctions.isEmpty()){
95+ // As DisjunctionListRxGene extends CompositeFixedGene, its disjunctions list cannot be empty.
96+ // In this case we return null to represent an unsatisfiable DisjunctionListRxGene.
97+ return null
98+ }
99+
100+ val disjList = DisjunctionListRxGene (satisfiableDisjunctions)
101+
102+ // TODO tmp hack until full handling of ^$. Assume full match when nested disjunctions
103+ for (gene in disjList.disjunctions) {
104+ gene.extraPrefix = false
105+ gene.extraPostfix = false
106+ gene.matchStart = true
107+ gene.matchEnd = true
108+ }
109+ return disjList
110+ }
77111
78112 override fun visitPattern (ctx : RegexJavaParser .PatternContext ): VisitResult {
79113
80114 val res = ctx.disjunction().accept(this )
81115
82116 val text = RegexUtils .getRegexExpByParserRuleContext(ctx)
83117
84- val disjList = DisjunctionListRxGene (res.genes.map { it as DisjunctionRxGene })
118+ val satisfiableDisjunctions = res.genes
119+ .map { it as DisjunctionRxGene }
120+ .filter{ ! it.isUnsatisfiable() }
121+
122+ if (satisfiableDisjunctions.isEmpty()) {
123+ throw IllegalStateException (" Regex is unsatisfiable." )
124+ }
125+
126+ val disjList = DisjunctionListRxGene (satisfiableDisjunctions)
85127
86128 // we remove the <EOF> token from end of the string to store as sourceRegex
87129 val gene = RegexGene (
@@ -102,9 +144,19 @@ class GeneRegexJavaVisitor(externalRegexFlags: RegexFlags = RegexFlags()) : Rege
102144 val matchStart = assertionMatches.first
103145 val matchEnd = assertionMatches.second
104146
105- val disj = DisjunctionRxGene (" disj" , altRes.genes.map { it }, matchStart, matchEnd)
147+ val res = VisitResult ()
148+
149+ // add disjunction if it has genes, OR if the alternative was purely assertions (^$) or flag scopes
150+ // in that case altRes.genes is empty but the alternative is valid (matches "")
151+ val hasOnlyAssertionsOrFlagScopes = ctx.alternative().term().isNotEmpty() &&
152+ ctx.alternative().term().all { it.assertion() != null || it.FLAG_SCOPE_OPEN () != null }
153+
154+ if (altRes.genes.isNotEmpty() || hasOnlyAssertionsOrFlagScopes || ctx.alternative().term().isEmpty()) {
155+ val disj = DisjunctionRxGene (" disj" , altRes.genes.map { it }, matchStart, matchEnd)
106156
107- val res = VisitResult (disj)
157+ res.genes.add(disj)
158+ }
159+ // else: had non-assertion terms but all produced nothing (empty char class etc.), skip
108160
109161 if (ctx.disjunction() != null ){
110162 val disjRes = ctx.disjunction().accept(this )
@@ -170,7 +222,7 @@ class GeneRegexJavaVisitor(externalRegexFlags: RegexFlags = RegexFlags()) : Rege
170222 // term is not a back ref: we use the default behavior, term results may only have 0-1 genes
171223 // if there is a gene, we add it to result
172224 res.genes.add(gene)
173- } else {
225+ } else if (resTerm.data is String ) {
174226
175227 val assertion = resTerm.data as String
176228 if (i== 0 && assertion == " ^" ){
@@ -185,6 +237,9 @@ class GeneRegexJavaVisitor(externalRegexFlags: RegexFlags = RegexFlags()) : Rege
185237 */
186238 throw IllegalStateException (" Cannot support $assertion at position $i " )
187239 }
240+ } else {
241+ // unsatisfiable term, return with no genes
242+ return VisitResult (data= Pair (false , false ))
188243 }
189244 }
190245
@@ -204,12 +259,23 @@ class GeneRegexJavaVisitor(externalRegexFlags: RegexFlags = RegexFlags()) : Rege
204259
205260 val resAtom = ctx.atom().accept(this )
206261 val atom = resAtom.genes.firstOrNull()
207- ? : return res
208262
209263 if (ctx.quantifier() != null ){
210264
211265 val limits = ctx.quantifier().accept(this ).data as Pair <Int ,Int >
212266
267+ // if quantified atom is unsatisfiable we must then check the limits
268+ if (atom == null ||
269+ ((atom as ? RxTerm )?.isUnsatisfiable() == true ) && resAtom.genes.size == 1 ) {
270+ return if (limits.first == 0 ) {
271+ // if 0 appearances is allowed then the regex is satisfiable only with empty string
272+ VisitResult (PatternCharacterBlockGene (" 0_QuantifierOnEmptyRegex" , " " ))
273+ } else {
274+ // if not then unsatisfiable, return with no genes
275+ res
276+ }
277+ }
278+
213279 // if atom is not a back ref then we use the default behavior, results may only have one gene
214280 var template: Gene = atom
215281
@@ -234,10 +300,11 @@ class GeneRegexJavaVisitor(externalRegexFlags: RegexFlags = RegexFlags()) : Rege
234300 if (ctx.atom()?.atomEscape()?.BackReference () != null ){
235301 // if atom is a BackReference we addAll genes from result as there may be more than one if digits are dropped
236302 res.genes.addAll(resAtom.genes)
237- } else {
303+ } else if (atom != null ) {
238304 // if atom is not a back ref we fall back to the default behavior, results only have one gene
239305 res.genes.add(atom)
240306 }
307+ // else atom is unsatisfiable, return no genes
241308 }
242309
243310 return res
@@ -307,21 +374,16 @@ class GeneRegexJavaVisitor(externalRegexFlags: RegexFlags = RegexFlags()) : Rege
307374
308375 currentFlags = merged
309376
310- val res = ctx.disjunction().accept( this )
377+ val disjList = buildDisjunctionList( ctx.disjunction())
311378
312379 currentFlags = previous
313380
314- val disjList = DisjunctionListRxGene (res.genes.map { it as DisjunctionRxGene })
315-
316- // TODO tmp hack until full handling of ^$. Assume full match when nested disjunctions
317- for (gene in disjList.disjunctions) {
318- gene.extraPrefix = false
319- gene.extraPostfix = false
320- gene.matchStart = true
321- gene.matchEnd = true
381+ return if (disjList != null ) {
382+ VisitResult (disjList)
383+ } else {
384+ // unsatisfiable, return with no genes.
385+ VisitResult ()
322386 }
323-
324- return VisitResult (disjList)
325387 }
326388
327389 if (ctx.quote() != null ){
@@ -355,17 +417,7 @@ class GeneRegexJavaVisitor(externalRegexFlags: RegexFlags = RegexFlags()) : Rege
355417 val groupIndex = captureGroups.size
356418 captureGroups.add(null ) // add placeholder for the gene
357419
358- val res = ctx.disjunction().accept(this )
359-
360- val disjList = DisjunctionListRxGene (res.genes.map { it as DisjunctionRxGene })
361-
362- // TODO tmp hack until full handling of ^$. Assume full match when nested disjunctions
363- for (gene in disjList.disjunctions){
364- gene.extraPrefix = false
365- gene.extraPostfix = false
366- gene.matchStart = true
367- gene.matchEnd = true
368- }
420+ val disjList = buildDisjunctionList(ctx.disjunction())
369421
370422 val isCapturingGroup = ! ctx.text.startsWith(" (?:" )
371423 val isNamedCaptureGroup = ctx.NAMED_CAPTURE_GROUP_OPEN () != null
@@ -381,7 +433,12 @@ class GeneRegexJavaVisitor(externalRegexFlags: RegexFlags = RegexFlags()) : Rege
381433 namedCaptureGroups[name] = disjList
382434 }
383435
384- return VisitResult (disjList)
436+ return if (disjList != null ) {
437+ VisitResult (disjList)
438+ } else {
439+ // unsatisfiable, return with no genes.
440+ VisitResult ()
441+ }
385442 }
386443
387444 if (ctx.DOT () != null ){
@@ -600,15 +657,12 @@ class GeneRegexJavaVisitor(externalRegexFlags: RegexFlags = RegexFlags()) : Rege
600657 maxDigits > allDigits.length -> allDigits.length
601658 allDigits.take(maxDigits).toInt() <= captureGroups.size -> maxDigits
602659 maxDigits > 1 -> maxDigits - 1
603- else -> throw IllegalStateException (
604- " Backreference ${txt.take(2 )} refers to group ${allDigits[0 ]} but only ${captureGroups.size} " +
605- " capture group(s) have been defined so far"
606- )
660+ else -> 1
607661 }
608662
609663 val n = allDigits.take(backRefDigitCount).toInt()
610664
611- val result = VisitResult (BackReferenceRxGene (n, captureGroups[ n - 1 ] !! ))
665+ val result = VisitResult (BackReferenceRxGene (n, captureGroups.getOrNull( n - 1 ) ))
612666
613667 val remainingChars = allDigits.drop(backRefDigitCount)
614668
@@ -624,8 +678,10 @@ class GeneRegexJavaVisitor(externalRegexFlags: RegexFlags = RegexFlags()) : Rege
624678 if (ctx.NamedBackReference () != null ) {
625679 // strip "\k<" and ">"
626680 val name = txt.drop(3 ).dropLast(1 )
681+ if (name !in namedCaptureGroups){
682+ throw IllegalStateException (" Named backreference \\ k<$name > refers to unknown group '$name '" )
683+ }
627684 val group = namedCaptureGroups[name]
628- ? : throw IllegalStateException (" Named backreference \\ k<$name > refers to unknown group '$name '" )
629685 val groupIndex = captureGroups.indexOf(group) + 1 // 1-based, for the gene name
630686 return VisitResult (BackReferenceRxGene (groupIndex, group))
631687 }
0 commit comments