vyre-std 0.1.0

Vyre standard library: GPU DFA assembly pipeline, Aho-Corasick construction, and compositional arithmetic helpers
Documentation
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 { .. })
    ));
}