use super::*;
#[test]
fn empty_regex_is_accepted() {
let nfa = regex_to_nfa("").expect("empty regex");
assert!(nfa.accept.iter().any(|b| *b));
}
#[test]
fn single_literal() {
let nfa = regex_to_nfa("a").expect("literal");
assert!(nfa.edges.iter().any(|e| e.byte == Some(b'a')));
}
#[test]
fn concatenation() {
let nfa = regex_to_nfa("abc").expect("abc");
assert_eq!(
nfa.edges
.iter()
.filter(|e| e.byte.is_some_and(|b| matches!(b, b'a' | b'b' | b'c')))
.count(),
3
);
}
#[test]
fn alternation() {
let nfa = regex_to_nfa("a|b").expect("a|b");
assert!(nfa.edges.iter().any(|e| e.byte == Some(b'a')));
assert!(nfa.edges.iter().any(|e| e.byte == Some(b'b')));
}
#[test]
fn kleene_star() {
let nfa = regex_to_nfa("a*").expect("a*");
assert!(nfa.edges.iter().any(|e| e.byte == Some(b'a')));
}
#[test]
fn character_class() {
let nfa = regex_to_nfa("[abc]").expect("[abc]");
for b in [b'a', b'b', b'c'] {
assert!(nfa.edges.iter().any(|e| e.byte == Some(b)));
}
}
#[test]
fn character_class_range() {
let nfa = regex_to_nfa("[a-f]").expect("[a-f]");
for b in b'a'..=b'f' {
assert!(nfa.edges.iter().any(|e| e.byte == Some(b)));
}
}
#[test]
fn negated_class() {
let nfa = regex_to_nfa("[^a]").expect("[^a]");
assert!(nfa.edges.iter().any(|e| e.byte == Some(b'b')));
assert!(nfa.edges.iter().all(|e| e.byte != Some(b'a')));
}
#[test]
fn escape_metachar() {
let nfa = regex_to_nfa(r"\*").expect(r"\*");
assert!(nfa.edges.iter().any(|e| e.byte == Some(b'*')));
}
#[test]
fn hex_escape_matches_single_byte() {
let nfa = regex_to_nfa(r"[\xE9]").expect(r"[\xE9]");
assert!(nfa.edges.iter().any(|e| e.byte == Some(0xE9)));
assert!(!nfa.edges.iter().any(|e| e.byte == Some(0xC3)));
assert!(!nfa.edges.iter().any(|e| e.byte == Some(0xA9)));
}
#[test]
fn shorthand_escapes_are_explicitly_unsupported() {
for source in [r"\d", r"\w", r"\s", r"\A", r"\z"] {
assert!(
matches!(
regex_to_nfa(source),
Err(PatternError::UnsupportedEscape { .. })
),
"{source} must not compile as a literal"
);
}
}
#[test]
fn unsupported_escape_message_is_actionable() {
let err = regex_to_nfa(r"\d").unwrap_err();
assert!(err.to_string().contains("Fix: unsupported escape `\\d`"));
}
#[test]
fn bare_alternation_operands_are_rejected() {
for source in ["|a", "a|", "a||b"] {
let err = regex_to_nfa(source).unwrap_err();
assert!(
err.to_string()
.contains("Fix: alternation `|` needs a non-empty expression"),
"{source} returned {err}"
);
}
}
#[test]
fn excessive_group_nesting_hits_recursion_limit() {
let source = format!("{}a{}", "(".repeat(129), ")".repeat(129));
assert!(matches!(
regex_to_nfa(&source),
Err(PatternError::RecursionLimit { limit: 128 })
));
}
#[test]
fn reverse_ranges_empty_groups_and_empty_negated_classes_error() {
for source in ["[z-a]", "()", "[^]"] {
let err = regex_to_nfa(source).unwrap_err();
assert!(err.to_string().contains("Fix:"), "{source} returned {err}");
}
}
#[test]
fn unmatched_paren_errors() {
assert!(matches!(
regex_to_nfa("(a"),
Err(PatternError::ParseError { .. })
));
}
#[test]
fn unmatched_bracket_errors() {
assert!(matches!(
regex_to_nfa("[abc"),
Err(PatternError::ParseError { .. })
));
}
#[test]
fn stray_metachar_errors() {
assert!(matches!(
regex_to_nfa("*"),
Err(PatternError::ParseError { .. })
));
}