From c582def0d924e4174408a05c11e7581010b4b946 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 1 May 2018 12:04:57 +1000 Subject: [PATCH] Fix alternations with empty sub-expressions with regex >= 0.2.7 With regex 0.2.7 and higher, the trick to rewrite `(a|b|)` to `(a|b|.{0})` doesn't work anymore, it fails with this error: > alternations cannot currently contain empty sub-expressions So delegate it like this instead: `((?:a|b)?)` --- src/lib.rs | 23 +++++++++++++++++++---- tests/matching.rs | 6 ++++++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 711df3f..e17d779 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -448,7 +448,7 @@ impl Expr { pub fn to_str(&self, buf: &mut String, precedence: u8) { match *self { - Expr::Empty => buf.push_str(".{0}"), + Expr::Empty => (), Expr::Any { newline } => buf.push_str( if newline { "(?s:.)" } else { "." } ), @@ -476,11 +476,26 @@ impl Expr { if precedence > 0 { buf.push_str("(?:"); } - children[0].to_str(buf, 1); - for child in &children[1..] { - buf.push('|'); + + let is_empty = |e| match e { + &Expr::Empty => true, + _ => false, + }; + let contains_empty = children.iter().any(&is_empty); + if contains_empty { + buf.push_str("(?:"); + } + for (i, child) in children.iter().filter(|&c| !is_empty(c)).enumerate() { + if i != 0 { + buf.push('|'); + } child.to_str(buf, 1); } + if contains_empty { + // regex fails with `(a|b|)`, so transform to `((?:a|b)?)` + buf.push_str(")?"); + } + if precedence > 0 { buf.push(')'); } diff --git a/tests/matching.rs b/tests/matching.rs index 4e0bf14..fef8d67 100644 --- a/tests/matching.rs +++ b/tests/matching.rs @@ -57,6 +57,12 @@ fn character_class_intersection() { fn alternation_with_empty_arm() { assert_match(r"^(a|)$", "a"); assert_match(r"^(a|)$", ""); + assert_match(r"^(|a)$", "a"); + assert_match(r"^(|a)$", ""); + assert_match(r"a|", "a"); + assert_match(r"a|", ""); + assert_match(r"|a", "a"); + assert_match(r"|a", ""); assert_no_match(r"^(a|)$", "b"); }