Skip to content

Commit da096e3

Browse files
authored
Fix float parameter regex (#6, cucumber-rs/cucumber#197)
1 parent 0a42d62 commit da096e3

File tree

2 files changed

+111
-6
lines changed

2 files changed

+111
-6
lines changed

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,21 @@ All user visible changes to `cucumber-expressions` crate will be documented in t
66

77

88

9+
## [0.1.2] · 2022-01-??
10+
[0.1.2]: /../../tree/v0.1.2
11+
12+
[Diff](/../../compare/v0.1.1...v0.1.2) | [Milestone](/../../milestone/3)
13+
14+
### Fixed
15+
16+
- Unsupported lookaheads in `float` parameter's [`Regex`]. ([#6], [cucumber-rs/cucumber#197])
17+
18+
[#6]: /../../pull/6
19+
[cucumber-rs/cucumber#197]: https://github.com/cucumber-rs/cucumber/issues/197
20+
21+
22+
23+
924
## [0.1.1] · 2021-11-29
1025
[0.1.1]: /../../tree/v0.1.1
1126

src/expand/mod.rs

Lines changed: 96 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -416,10 +416,20 @@ where
416416
if eq(&self.0, "int") {
417417
Left(r#"((?:-?\d+)|(?:\d+))"#.chars().map(Ok))
418418
} else if eq(&self.0, "float") {
419+
// Regex in other implementations has lookaheads. As `regex` crate
420+
// doesn't support them, we use `f32`/`f64` grammar instead:
421+
// https://doc.rust-lang.org/stable/std/primitive.f64.html#grammar
422+
// Provided grammar is a superset of the original one:
423+
// - supports `e` as exponent in addition to `E`
424+
// - supports trailing comma: `1.`
425+
// - supports `inf` and `NaN`
419426
Left(
420-
r#"((?=.*\d.*)[-+]?\d*(?:\.(?=\d.*))?\d*(?:\d+[E][+-]?\d+)?)"#
421-
.chars()
422-
.map(Ok),
427+
"([+-]?(?:inf\
428+
|NaN\
429+
|(?:\\d+|\\d+\\.\\d*|\\d*\\.\\d+)(?:[eE][+-]?\\d+)?\
430+
))"
431+
.chars()
432+
.map(Ok),
423433
)
424434
} else if eq(&self.0, "word") {
425435
Left(r#"([^\s]+)"#.chars().map(Ok))
@@ -592,6 +602,11 @@ mod spec {
592602
.unwrap_or_else(|e| panic!("failed: {}", e));
593603

594604
assert_eq!(expr.as_str(), "^(?:a|b) (?:c|d|e)$");
605+
assert!(expr.is_match("a c"));
606+
assert!(expr.is_match("b e"));
607+
assert!(!expr.is_match("c e"));
608+
assert!(!expr.is_match("a"));
609+
assert!(!expr.is_match("a "));
595610
}
596611

597612
#[test]
@@ -600,14 +615,17 @@ mod spec {
600615
Expression::regex("").unwrap_or_else(|e| panic!("failed: {}", e));
601616

602617
assert_eq!(expr.as_str(), "^$");
618+
assert!(expr.is_match(""));
619+
assert!(!expr.is_match("a"));
603620
}
604621

605622
#[test]
606623
fn escape_regex_characters() {
607-
let expr = Expression::regex(r"^$[]\(\){}\\.|?*+")
624+
let expr = Expression::regex(r"^$[]\()\{}\\.|?*+")
608625
.unwrap_or_else(|e| panic!("failed: {}", e));
609626

610-
assert_eq!(expr.as_str(), r"^\^\$\[\]\(\)(.*)\\\.\|\?\*\+$");
627+
assert_eq!(expr.as_str(), r"^\^\$\[\]\(\)\{\}\\\.\|\?\*\+$");
628+
assert!(expr.is_match("^$[](){}\\.|?*+"));
611629
}
612630

613631
#[test]
@@ -616,14 +634,80 @@ mod spec {
616634
.unwrap_or_else(|e| panic!("failed: {}", e));
617635

618636
assert_eq!(expr.as_str(), "^(?:a)?$");
637+
assert!(expr.is_match(""));
638+
assert!(expr.is_match("a"));
639+
assert!(!expr.is_match("b"));
619640
}
620641

621642
#[test]
622-
fn parameter() {
643+
fn parameter_int() {
623644
let expr = Expression::regex("{int}")
624645
.unwrap_or_else(|e| panic!("failed: {}", e));
625646

626647
assert_eq!(expr.as_str(), "^((?:-?\\d+)|(?:\\d+))$");
648+
assert!(expr.is_match("123"));
649+
assert!(expr.is_match("-123"));
650+
assert!(!expr.is_match("+123"));
651+
assert!(!expr.is_match("123."));
652+
}
653+
654+
#[test]
655+
fn parameter_float() {
656+
let expr = Expression::regex("{float}")
657+
.unwrap_or_else(|e| panic!("failed: {}", e));
658+
659+
assert_eq!(
660+
expr.as_str(),
661+
"^([+-]?(?:inf\
662+
|NaN\
663+
|(?:\\d+|\\d+\\.\\d*|\\d*\\.\\d+)(?:[eE][+-]?\\d+)?\
664+
))$",
665+
);
666+
assert!(expr.is_match("+1"));
667+
assert!(expr.is_match(".1"));
668+
assert!(expr.is_match("-.1"));
669+
assert!(expr.is_match("-1."));
670+
assert!(expr.is_match("-1.1E+1"));
671+
assert!(expr.is_match("-inf"));
672+
assert!(expr.is_match("NaN"));
673+
}
674+
675+
#[test]
676+
fn parameter_word() {
677+
let expr = Expression::regex("{word}")
678+
.unwrap_or_else(|e| panic!("failed: {}", e));
679+
680+
assert_eq!(expr.as_str(), "^([^\\s]+)$");
681+
assert!(expr.is_match("test"));
682+
assert!(expr.is_match("\"test\""));
683+
assert!(!expr.is_match("with space"));
684+
}
685+
686+
#[test]
687+
fn parameter_string() {
688+
let expr = Expression::regex("{string}")
689+
.unwrap_or_else(|e| panic!("failed: {}", e));
690+
691+
assert_eq!(
692+
expr.as_str(),
693+
r#"^("(?:[^"\\]*(?:\\.[^"\\]*)*)"|'(?:[^'\\]*(?:\\.[^'\\]*)*)')$"#,
694+
);
695+
assert!(expr.is_match("\"\""));
696+
assert!(expr.is_match("''"));
697+
assert!(expr.is_match("'with \"'"));
698+
assert!(expr.is_match("\"with '\""));
699+
assert!(expr.is_match("\"with \\\" escaped\""));
700+
assert!(expr.is_match("'with \\' escaped'"));
701+
assert!(!expr.is_match("word"));
702+
}
703+
704+
#[test]
705+
fn parameter_all() {
706+
let expr =
707+
Expression::regex("{}").unwrap_or_else(|e| panic!("failed: {}", e));
708+
709+
assert_eq!(expr.as_str(), "^(.*)$");
710+
assert!(expr.is_match("anything matches"));
627711
}
628712

629713
#[test]
@@ -632,6 +716,9 @@ mod spec {
632716
Expression::regex("a").unwrap_or_else(|e| panic!("failed: {}", e));
633717

634718
assert_eq!(expr.as_str(), "^a$");
719+
assert!(expr.is_match("a"));
720+
assert!(!expr.is_match("b"));
721+
assert!(!expr.is_match("ab"));
635722
}
636723

637724
#[allow(clippy::non_ascii_literal)]
@@ -641,6 +728,9 @@ mod spec {
641728
.unwrap_or_else(|e| panic!("failed: {}", e));
642729

643730
assert_eq!(expr.as_str(), "^Привет, Мир(?:ы)?!$");
731+
assert!(expr.is_match("Привет, Мир!"));
732+
assert!(expr.is_match("Привет, Миры!"));
733+
assert!(!expr.is_match("Hello world"));
644734
}
645735

646736
#[test]

0 commit comments

Comments
 (0)