#[test]
fn leftmost_match_wins_regardless_of_step_size() {
let re = regexr::Regex::new(r"\p{L}\s\S").unwrap();
let haystack = "x\r\u{4e2d}\te";
let found = re.find(haystack).map(|m| (m.start(), m.end()));
assert_eq!(found, Some((0, 5)), "leftmost match must win");
let re = regexr::Regex::new(r"\p{L}\s\S").unwrap();
assert_eq!(
re.find("x\r\u{4e2d}\t").map(|m| (m.start(), m.end())),
Some((0, 5))
);
}
#[test]
fn quantified_alternation_keeps_its_loop() {
for pattern in [r"\s+", r"\s+\s+", r"(?:a|b)+"] {
let interpreted = regexr::Regex::new(pattern).unwrap();
let jitted = regexr::RegexBuilder::new(pattern)
.jit(true)
.build()
.unwrap();
for haystack in [" \n", "aab", " ", "ab"] {
assert_eq!(
interpreted.find(haystack).map(|m| (m.start(), m.end())),
jitted.find(haystack).map(|m| (m.start(), m.end())),
"pattern {pattern:?} on {haystack:?}: JIT and interpreter disagree"
);
}
}
}
#[test]
fn small_unicode_classes_match_whole_characters() {
let re = regexr::Regex::new(r"^[^\s<>]+$").unwrap();
assert!(re.is_match("https://example.com/\u{4e2d}"));
assert!(!re.is_match("has space"));
assert!(!re.is_match("a\u{a0}b"), "U+00A0 is Unicode whitespace");
let re = regexr::Regex::new(r"\s+").unwrap();
let haystack = "\u{4e2d}\u{2003}\u{3000}\u{4e2d}";
let m = re.find(haystack).expect("should match the separators");
assert_eq!(m.as_str(), "\u{2003}\u{3000}");
}
#[test]
fn requesting_jit_never_downgrades_the_engine() {
const BACKREF_PATTERNS: &[&str] = &[r#"(['"])[^'"]*\1"#, r"(\w+)\s+\1", r"(a)\1"];
for pattern in BACKREF_PATTERNS {
let jitted = regexr::RegexBuilder::new(pattern)
.jit(true)
.build()
.unwrap();
assert_ne!(
jitted.engine_name(),
"PikeVm",
"{pattern}: backreferences must reach a backtracking engine"
);
}
const RUN_PATTERNS: &[&str] = &[r"\w+", r"\d+", r"[a-z]+", r"[0-9a-f]{2,}", r"\w{2,8}"];
const ALTERNATION_PATTERNS: &[&str] = &[
r#"[a-zA-Z_][a-zA-Z0-9_]*|[0-9]+(?:\.[0-9]+)?|[+\-*/=<>!&|^%]+|[(){}\[\];,.]|"[^"]*"|'[^']*'"#,
r"[^>]+",
r"<[^>]+>",
r"https?://[^\s<>]+",
r"error|warning|critical|fatal",
];
const BOUNDARY_PATTERNS: &[&str] = &[r"\bthe\b", r"\bword\b", r"\bfoo\b\s"];
let selection: &[(&str, &[&str])] = &[
("a repeated byte class", RUN_PATTERNS),
("an alternation", ALTERNATION_PATTERNS),
("a literal guarded by a word boundary", BOUNDARY_PATTERNS),
];
for (shape, patterns) in selection {
for pattern in *patterns {
let plain = regexr::Regex::new(pattern).unwrap();
let jitted = regexr::RegexBuilder::new(pattern)
.jit(true)
.build()
.unwrap();
assert_eq!(
plain.engine_name(),
jitted.engine_name(),
"{pattern}: {shape} must reach the same engine either way"
);
}
}
const PATTERNS: &[&str] = &[
r#"(['"])[^'"]*\1"#,
r"(\w+)\s+\1",
r"\s+",
r"\S+",
r"[^\s<>]+",
r"\w+(?=ing\b)",
r"(cat|dog)+",
r"\bword\b",
];
const HAYSTACKS: &[&str] = &[
r#"let x = "hello"; y = 'z';"#,
"the the word word",
"running and singing",
"cat dog catdog",
" \t\u{a0} ",
"https://example.com/a<b>",
"",
];
for pattern in PATTERNS {
let plain = regexr::Regex::new(pattern).unwrap();
let jitted = regexr::RegexBuilder::new(pattern)
.jit(true)
.build()
.unwrap();
for haystack in HAYSTACKS {
let a: Vec<_> = plain
.find_iter(haystack)
.map(|m| (m.start(), m.end()))
.collect();
let b: Vec<_> = jitted
.find_iter(haystack)
.map(|m| (m.start(), m.end()))
.collect();
assert_eq!(
a,
b,
"{pattern:?} on {haystack:?}: plain ({}) and jit(true) ({}) disagree",
plain.engine_name(),
jitted.engine_name()
);
}
}
}