regast 0.1.0

Regular expressions with first-class syntax and parse trees
Documentation
use proptest::prelude::*;
use regast::{PtKind, Regast};
use regex::Regex;

const PATTERNS: &[&str] = &[
    "",
    "a",
    "a|b",
    "ab",
    "a?",
    "a*",
    "a+",
    "a{0,3}",
    "a{2,}",
    "(a|b)*c?",
    "(?:ab|b)+",
    "[a-c]+",
    "[^a]*",
    r"\d{1,3}",
    r"[\w-]+",
    "(a?)*",
];

proptest! {
    #![proptest_config(ProptestConfig::with_cases(256))]

    #[test]
    fn agrees_with_regex_and_reconstructs_deterministically(
        pattern_index in 0..PATTERNS.len(),
        chars in prop::collection::vec(prop::sample::select(vec!['a', 'b', 'c', '0', '-', 'é']), 0..7),
    ) {
        let pattern = PATTERNS[pattern_index];
        let input: String = chars.into_iter().collect();
        let regast = Regast::new(pattern).unwrap();
        let reference = Regex::new(&format!(r"\A(?:{pattern})\z")).unwrap();

        prop_assert_eq!(regast.is_match(&input), reference.is_match(&input));
        if let Ok(first) = regast.parse(&input) {
            prop_assert_eq!(first.flatten(), input.as_str());
            let second = regast.parse(&input).unwrap();
            prop_assert_eq!(first.to_json(), second.to_json());
            for node in first.walk() {
                if let PtKind::Repeat { .. } = node.kind {
                    for iteration in &node.children {
                        prop_assert!(!first.flatten()[iteration.span.start as usize..iteration.span.end as usize].is_empty());
                    }
                }
            }
        }
    }
}

#[test]
fn unicode_spans_are_utf8_byte_offsets() {
    let regex = Regast::new("(?<letter>[é日])+").unwrap();
    let tree = regex.parse("é日").unwrap();
    let groups = tree.group_by_name_all("letter");
    assert_eq!(groups.len(), 2);
    assert_eq!(groups[0].span.start, 0);
    assert_eq!(groups[0].span.end, 2);
    assert_eq!(groups[1].span.start, 2);
    assert_eq!(groups[1].span.end, 5);
}