orgize/parse/
combinators.rs

1//! Parsers combinators
2
3use memchr::memchr;
4use nom::{
5    bytes::complete::take_while1,
6    combinator::verify,
7    error::{make_error, ErrorKind},
8    Err, IResult,
9};
10
11// read until the first line_ending, if line_ending is not present, return the input directly
12pub fn line(input: &str) -> IResult<&str, &str, ()> {
13    if let Some(i) = memchr(b'\n', input.as_bytes()) {
14        if i > 0 && input.as_bytes()[i - 1] == b'\r' {
15            Ok((&input[i + 1..], &input[0..i - 1]))
16        } else {
17            Ok((&input[i + 1..], &input[0..i]))
18        }
19    } else {
20        Ok(("", input))
21    }
22}
23
24pub fn lines_till<F>(predicate: F) -> impl Fn(&str) -> IResult<&str, &str, ()>
25where
26    F: Fn(&str) -> bool,
27{
28    move |i| {
29        let mut input = i;
30
31        loop {
32            // TODO: better error kind
33            if input.is_empty() {
34                return Err(Err::Error(make_error(input, ErrorKind::Many0)));
35            }
36
37            let (input_, line_) = line(input)?;
38
39            debug_assert_ne!(input, input_);
40
41            if predicate(line_) {
42                let offset = i.len() - input.len();
43                return Ok((input_, &i[0..offset]));
44            }
45
46            input = input_;
47        }
48    }
49}
50
51pub fn lines_while<F>(predicate: F) -> impl Fn(&str) -> IResult<&str, &str, ()>
52where
53    F: Fn(&str) -> bool,
54{
55    move |i| {
56        let mut input = i;
57
58        loop {
59            // unlike lines_till, line_while won't return error
60            if input.is_empty() {
61                return Ok(("", i));
62            }
63
64            let (input_, line_) = line(input)?;
65
66            debug_assert_ne!(input, input_);
67
68            if !predicate(line_) {
69                let offset = i.len() - input.len();
70                return Ok((input, &i[0..offset]));
71            }
72
73            input = input_;
74        }
75    }
76}
77
78#[test]
79fn test_lines_while() {
80    assert_eq!(lines_while(|line| line == "foo")("foo"), Ok(("", "foo")));
81    assert_eq!(lines_while(|line| line == "foo")("bar"), Ok(("bar", "")));
82    assert_eq!(
83        lines_while(|line| line == "foo")("foo\n\n"),
84        Ok(("\n", "foo\n"))
85    );
86    assert_eq!(
87        lines_while(|line| line.trim().is_empty())("\n\n\n"),
88        Ok(("", "\n\n\n"))
89    );
90}
91
92pub fn eol(input: &str) -> IResult<&str, &str, ()> {
93    verify(line, |s: &str| {
94        s.as_bytes().iter().all(u8::is_ascii_whitespace)
95    })(input)
96}
97
98pub fn one_word(input: &str) -> IResult<&str, &str, ()> {
99    take_while1(|c: char| !c.is_ascii_whitespace())(input)
100}
101
102pub fn blank_lines_count(input: &str) -> IResult<&str, usize, ()> {
103    let mut count = 0;
104    let mut input = input;
105
106    loop {
107        if input.is_empty() {
108            return Ok(("", count));
109        }
110
111        let (input_, line_) = line(input)?;
112
113        debug_assert_ne!(input, input_);
114
115        if !line_.chars().all(char::is_whitespace) {
116            return Ok((input, count));
117        }
118
119        count += 1;
120
121        input = input_;
122    }
123}
124
125#[test]
126fn test_blank_lines_count() {
127    assert_eq!(blank_lines_count("foo"), Ok(("foo", 0)));
128    assert_eq!(blank_lines_count(" foo"), Ok((" foo", 0)));
129    assert_eq!(blank_lines_count("  \t\nfoo\n"), Ok(("foo\n", 1)));
130    assert_eq!(blank_lines_count("\n    \r\n\nfoo\n"), Ok(("foo\n", 3)));
131    assert_eq!(
132        blank_lines_count("\r\n   \n  \r\n   foo\n"),
133        Ok(("   foo\n", 3))
134    );
135    assert_eq!(blank_lines_count("\r\n   \n  \r\n   \n"), Ok(("", 4)));
136}