1pub use error::{Error, ErrorKind};
4pub use item::{Item, ItemC, ItemG};
5use winnow::{
6 ModalResult, Parser,
7 ascii::take_escaped,
8 combinator::{alt, cut_err, delimited, eof, repeat, repeat_till, separated, terminated},
9 error::ErrMode,
10 stream::{ContainsToken, LocatingSlice, Stream},
11 token::{one_of, take_till, take_while},
12};
13
14use crate::parser::ErrorKind::UnescapedChar;
15
16mod error;
17mod item;
18
19#[cfg(test)]
20mod test;
21
22pub type LSpan<'a> = LocatingSlice<&'a str>;
24
25type ParseResult<'a, O = &'a str> = ModalResult<O, Error<'a>>;
26
27fn alpha_numeric_set() -> impl ContainsToken<char> {
28 ('a'..='z', 'A'..='Z', '0'..='9')
29}
30
31fn one_tag<'i>(i: &mut LSpan<'i>) -> ParseResult<'i> {
32 take_while(1.., (alpha_numeric_set(), ':', '+', '-')).parse_next(i)
33}
34
35fn tag_list<'i>(i: &mut LSpan<'i>) -> ParseResult<'i, Vec<&'i str>> {
36 separated(1.., one_tag, ',').parse_next(i)
37}
38
39fn element<'i>(i: &mut LSpan<'i>) -> ParseResult<'i, Item<'i>> {
40 let start = *i;
41
42 let tags = delimited('<', tag_list, ' ').parse_next(i)?;
43 let parts = cut_err(terminated(items, ">".context(ErrorKind::ElementNotClose)))
44 .parse_next(i)
45 .map_err(|e: ErrMode<Error<'_>>| {
46 e.map(|e| {
47 if e.kind() == Some(ErrorKind::ElementNotClose) {
48 e.with_input(&start)
49 } else {
50 e
51 }
52 })
53 })?;
54
55 Ok(Item::Element(tags, parts))
56}
57
58fn special_chars() -> impl ContainsToken<char> {
59 ('<', '>', '\\')
60}
61
62fn plain_text_normal<'i>(i: &mut LSpan<'i>) -> ParseResult<'i> {
63 take_till(1.., special_chars()).parse_next(i)
64}
65
66fn escapable_char<'i>(i: &mut LSpan<'i>) -> ParseResult<'i, char> {
67 one_of(special_chars()).parse_next(i)
68}
69
70fn plain_text<'i>(i: &mut LSpan<'i>) -> ParseResult<'i> {
71 let mut start = *i;
72
73 take_escaped(plain_text_normal, '\\', escapable_char.context(ErrorKind::UnescapableChar)
74 )
75 .verify(|s: &str| !s.is_empty())
77 .map_err(|e: ErrMode<Error<'_>>| {
78 e.map(|mut e| {
79 if e.kind() == Some(ErrorKind::UnescapableChar) && e.input.is_empty() {
82 start.next_slice(start.eof_offset() - 1);
83 e = e.with_input(&start).with_kind(ErrorKind::UnescapedChar);
84 }
85
86 if e.kind().is_none() {
94 e = e.with_kind(UnescapedChar)
95 }
96
97 e
98 })
99 })
100 .parse_next(i)
101}
102
103fn item<'i>(i: &mut LSpan<'i>) -> ParseResult<'i, Item<'i>> {
104 alt((element, plain_text.map(Item::PlainText))).parse_next(i)
105}
106
107fn items<'i>(i: &mut LSpan<'i>) -> ParseResult<'i, Vec<Item<'i>>> {
108 repeat(0.., item).parse_next(i)
109}
110
111fn output<'i>(i: &mut LSpan<'i>) -> ParseResult<'i, Vec<Item<'i>>> {
112 let (result, _) = repeat_till(0.., item, eof).parse_next(i)?;
113 Ok(result)
114}
115
116fn parse_line(line: usize, i: &str) -> Result<Vec<Item<'_>>, Error<'_>> {
117 let mut located = LSpan::new(i);
118
119 output
120 .map_err(|e| {
121 e.into_inner()
122 .expect("incomplete should never happens")
123 .with_line(line)
124 })
125 .parse_next(&mut located)
126}
127
128pub fn parse(s: &str) -> Result<Vec<Vec<Item<'_>>>, Error<'_>> {
134 s.lines()
135 .enumerate()
136 .map(|(i, line)| parse_line(i, line))
137 .collect()
138}
139
140fn hex_digit() -> impl ContainsToken<char> {
141 ('0'..='9', 'A'..='F', 'a'..='f')
142}
143
144fn hex_color_part(i: &mut &str) -> ModalResult<u8> {
145 repeat(2..=2, one_of(hex_digit()))
146 .map(|_: ()| ())
147 .take()
148 .try_map(|x| u8::from_str_radix(x, 16))
149 .parse_next(i)
150}
151
152pub fn hex_rgb(s: &str) -> Option<(u8, u8, u8)> {
154 let (r, g, b) = (hex_color_part, hex_color_part, hex_color_part)
155 .parse(s)
156 .ok()?;
157
158 Some((r, g, b))
162}