chasa 0.5.0

A parser combinator focused on rollback/commit, streaming inputs, and composable method chains.
Documentation
use core::ops::ControlFlow;

use crate::error::LatestSink;
use crate::error::std::StdErr;
use crate::input::{In, IsCut, Offset};
use crate::parser::item::{item, satisfy_map};
use crate::parser::prim::from_fn_mut;
use crate::parser::then::to;

#[test]
fn many_collects_items_and_consumes() {
    let mut input = "aaab";
    let mut errors: LatestSink<Offset<char>, StdErr<char>> = LatestSink::new();
    let mut is_cut = false;
    {
        let mut i = In::new(&mut input, &mut errors, IsCut::new(&mut is_cut));
        let out: Vec<char> = i.many(item('a')).unwrap();
        assert_eq!(out, vec!['a', 'a', 'a']);
    }
    assert_eq!(input, "b");
}

#[test]
fn many1_requires_at_least_one() {
    let mut input = "bbb";
    let mut errors: LatestSink<Offset<char>, StdErr<char>> = LatestSink::new();
    let mut is_cut = false;
    {
        let mut i = In::new(&mut input, &mut errors, IsCut::new(&mut is_cut));
        let out: Option<Vec<char>> = i.many1(item('a'));
        assert!(out.is_none());
    }
    assert_eq!(input, "bbb");
}

#[test]
fn sep_use_trail_accepts_trailing_separator() {
    let mut input = "a,a,";
    let mut errors: LatestSink<Offset<char>, StdErr<char>> = LatestSink::new();
    let mut is_cut = false;
    {
        let mut i = In::new(&mut input, &mut errors, IsCut::new(&mut is_cut));
        let (out, did_trail): (Vec<char>, bool) =
            i.sep_use_trail(item('a'), to(item(','), ())).unwrap();
        assert_eq!(out, vec!['a', 'a']);
        assert!(did_trail);
    }
    assert_eq!(input, "");
}

#[test]
fn sep_map_sums_items() {
    let mut input = "1,2,3";
    let mut errors: LatestSink<Offset<char>, StdErr<char>> = LatestSink::new();
    let mut is_cut = false;
    {
        let mut i = In::new(&mut input, &mut errors, IsCut::new(&mut is_cut));
        let digit = satisfy_map(|c: char| c.to_digit(10).map(|v| v as u32));
        let out = i
            .sep_map(digit, to(item(','), ()), |iter| iter.sum::<u32>())
            .unwrap();
        assert_eq!(out, 6);
    }
    assert_eq!(input, "");
}

#[test]
fn sep1_map_requires_first_item() {
    let mut input = "";
    let mut errors: LatestSink<Offset<char>, StdErr<char>> = LatestSink::new();
    let mut is_cut = false;
    {
        let mut i = In::new(&mut input, &mut errors, IsCut::new(&mut is_cut));
        let digit = satisfy_map(|c: char| c.to_digit(10).map(|v| v as u32));
        let out = i.sep1_map(digit, to(item(','), ()), |iter| iter.sum::<u32>());
        assert!(out.is_none());
    }
    assert_eq!(input, "");
}

#[test]
fn sep_reduce_left_associates() {
    let mut input = "1+2+3";
    let mut errors: LatestSink<Offset<char>, StdErr<char>> = LatestSink::new();
    let mut is_cut = false;
    {
        let mut i = In::new(&mut input, &mut errors, IsCut::new(&mut is_cut));
        let digit = satisfy_map(|c: char| c.to_digit(10).map(|v| v as i32));
        let out = i
            .sep_reduce(digit, item('+'), |left, _op, right| left + right)
            .unwrap();
        assert_eq!(out, 6);
    }
    assert_eq!(input, "");
}

#[test]
fn flow_many_map_collects_until_break() {
    let mut input = "12x34";
    let mut errors: LatestSink<Offset<char>, StdErr<char>> = LatestSink::new();
    let mut is_cut = false;
    {
        let mut i = In::new(&mut input, &mut errors, IsCut::new(&mut is_cut));
        let parser = from_fn_mut(|i| {
            let ch: char = crate::parser::item::any(i)?;
            if ch.is_ascii_digit() {
                Some(ControlFlow::Continue(ch.to_digit(10).unwrap() as u32))
            } else {
                Some(ControlFlow::Break(ch))
            }
        });
        let (out, break_): (Vec<u32>, Option<char>) =
            i.flow_many_map(parser, |iter| iter.collect()).unwrap();
        assert_eq!(out, vec![1, 2]);
        assert_eq!(break_, Some('x'));
    }
    assert_eq!(input, "34");
}

#[test]
fn many_till_collects_until_end_parser() {
    let mut input = "ab]cd";
    let mut errors: LatestSink<Offset<char>, StdErr<char>> = LatestSink::new();
    let mut is_cut = false;
    {
        let mut i = In::new(&mut input, &mut errors, IsCut::new(&mut is_cut));
        let (out, end): (Vec<char>, char) =
            i.many_till(crate::parser::item::any, item(']')).unwrap();
        assert_eq!(out, vec!['a', 'b']);
        assert_eq!(end, ']');
    }
    assert_eq!(input, "cd");
}

#[test]
fn many_till_allows_empty_prefix() {
    let mut input = "]";
    let mut errors: LatestSink<Offset<char>, StdErr<char>> = LatestSink::new();
    let mut is_cut = false;
    {
        let mut i = In::new(&mut input, &mut errors, IsCut::new(&mut is_cut));
        let (out, end): (Vec<char>, char) =
            i.many_till(crate::parser::item::any, item(']')).unwrap();
        assert!(out.is_empty());
        assert_eq!(end, ']');
    }
    assert_eq!(input, "");
}

#[test]
fn many_till_fails_without_end_parser() {
    let mut input = "ab";
    let mut errors: LatestSink<Offset<char>, StdErr<char>> = LatestSink::new();
    let mut is_cut = false;
    {
        let mut i = In::new(&mut input, &mut errors, IsCut::new(&mut is_cut));
        let out: Option<(Vec<char>, char)> = i.many_till(crate::parser::item::any, item(']'));
        assert!(out.is_none());
    }
    assert_eq!(input, "");
}