tui_markup/parser/
mod.rs

1//! Parsing stage of the compilation process.
2
3mod error;
4mod item;
5
6#[cfg(test)]
7mod test;
8
9use nom::{
10    branch::alt,
11    bytes::complete::{escaped, is_not, tag, take_while1, take_while_m_n},
12    character::complete::{char, one_of},
13    combinator::{eof, map, map_res, verify},
14    multi::{many0, many_till, separated_list1},
15    sequence::tuple,
16    Err as NomErr, IResult,
17};
18use nom_locate::LocatedSpan;
19
20pub use error::{Error, ErrorKind};
21pub use item::{Item, ItemC, ItemG};
22
23/// Span with location info.
24pub type LSpan<'a> = LocatedSpan<&'a str, usize>;
25
26type ParseResult<'a, O = LSpan<'a>> = IResult<LSpan<'a>, O, Error<'a>>;
27
28fn force_failure<E>(err: NomErr<E>) -> NomErr<E> {
29    match err {
30        e @ (NomErr::Incomplete(_) | NomErr::Failure(_)) => e,
31        NomErr::Error(e) => NomErr::Failure(e),
32    }
33}
34
35fn one_tag(s: LSpan) -> ParseResult {
36    take_while1(|c: char| c.is_alphanumeric() || c == ':' || c == '+' || c == '-')(s)
37}
38
39fn tag_list(s: LSpan) -> ParseResult<Vec<LSpan>> {
40    separated_list1(char(','), one_tag)(s)
41}
42
43fn element_inner_space(s: LSpan) -> ParseResult<char> {
44    char(' ')(s)
45}
46
47fn element_end(s: LSpan) -> ParseResult {
48    tag(">")(s).map_err(|e: NomErr<Error>| e.map(|e| e.attach(ErrorKind::ElementNotClose)))
49}
50
51fn element(s: LSpan) -> ParseResult<Item> {
52    let input = s;
53
54    let (s, (_, tags, _)) = tuple((char('<'), tag_list, element_inner_space))(s)?;
55    let (s, (parts, _)) = tuple((items, element_end))(s)
56        .map_err(|e: NomErr<Error>| {
57            e.map(|mut e| {
58                if e.kind() == Some(ErrorKind::ElementNotClose) {
59                    e.span = input;
60                }
61                e
62            })
63        })
64        .map_err(force_failure)?;
65
66    Ok((s, Item::Element(tags, parts)))
67}
68
69fn plain_text_normal(s: LSpan) -> ParseResult {
70    is_not("<>\\")(s)
71}
72
73fn escapable_char(s: LSpan) -> ParseResult<char> {
74    one_of("<>\\")(s).map_err(|e: NomErr<Error>| e.map(|e| e.attach(ErrorKind::UnescapableChar)))
75}
76
77fn plain_text(s: LSpan) -> ParseResult {
78    verify(escaped(plain_text_normal, '\\', escapable_char), |ls| !ls.is_empty())(s).map_err(|e| {
79        e.map(|mut e| {
80            use nom::Slice;
81
82            if !s.is_empty()
83                && e.kind().map_or(true, |x| x == ErrorKind::UnescapedChar)
84                && !s.starts_with(['\\', '<', '>'])
85            {
86                e.span = e.span.slice(e.span.len() - 1..);
87            }
88
89            e.attach(ErrorKind::UnescapedChar)
90        })
91    })
92}
93
94fn item(s: LSpan) -> ParseResult<Item> {
95    alt((element, map(plain_text, Item::PlainText)))(s)
96}
97
98fn items(s: LSpan) -> ParseResult<Vec<Item>> {
99    many0(item)(s)
100}
101
102fn output(s: LSpan) -> ParseResult<Vec<Item>> {
103    let (s, result) = many_till(item, eof)(s)?;
104    Ok((s, result.0))
105}
106
107fn parse_line((line, s): (usize, &str)) -> Result<Vec<Item>, Error> {
108    let located = LSpan::new_extra(s, line + 1);
109
110    match output(located) {
111        Ok((_, result)) => Ok(result),
112        Err(NomErr::Error(e) | NomErr::Failure(e)) => Err(e),
113        Err(NomErr::Incomplete(_)) => unreachable!("Parser is not streaming, so incomplete state shouldn't appear"),
114    }
115}
116
117/// Parse tui markup source into ast.
118///
119/// ## Errors
120///
121/// If input source has invalid syntax.
122pub fn parse(s: &str) -> Result<Vec<Vec<Item>>, Error> {
123    s.lines()
124        .enumerate()
125        .map(parse_line)
126        .collect::<Result<Vec<Vec<Item>>, Error>>()
127}
128
129fn hex_color_part(s: &str) -> IResult<&str, u8> {
130    map_res(take_while_m_n(2, 2, |c: char| c.is_ascii_hexdigit()), |x| {
131        u8::from_str_radix(x, 16)
132    })(s)
133}
134
135/// Parse string of 6 hex digit into r, g, b value.
136#[must_use]
137pub fn hex_rgb(s: &str) -> Option<(u8, u8, u8)> {
138    let (s, (r, g, b)) = tuple((hex_color_part, hex_color_part, hex_color_part))(s).ok()?;
139
140    if !s.is_empty() {
141        return None;
142    }
143
144    Some((r, g, b))
145}