parser_test/
highlighter.rs

1use std::str::{CharIndices};
2use std::iter::Peekable;
3
4/// Helper type that create (start, end) iterator over strings like "| ^--^ ^^".
5/// Allows to write parser tests as follows:
6/// test(
7///      "struct X { field: u32 }",
8///      "       |   ^---^  ^-^  ",
9///      type_name("X"),
10///      ident_name("field"),
11///      any_ty("u32")
12/// );
13///
14/// There is also an alternate space symbol: I that can be helpful for visual alignment.
15pub struct Highlighter<'a> {
16    spans: Peekable<CharIndices<'a>>,
17}
18impl<'a> Highlighter<'a> {
19    pub fn new(spans: &'a str) -> Highlighter<'a> {
20        Self {
21            spans: spans.char_indices().peekable(),
22        }
23    }
24}
25
26impl<'a> Iterator for Highlighter<'a> {
27    type Item = (usize, usize);
28
29    fn next(&mut self) -> Option<Self::Item> {
30        match self.spans.next() {
31            Some((mut start, mut c)) => {
32                while c.is_whitespace() || c == 'I' {
33                    start += 1;
34                    c = match self.spans.next() {
35                        Some((_, c)) => c,
36                        None => return None,
37                    };
38                }
39
40                match c {
41                    '^' => {
42                        while let Some((pos, c)) = self.spans.next() {
43                            match c {
44                                '-' => {
45                                    continue;
46                                }
47                                '^' => {
48                                    return Some((start, pos));
49                                }
50                                _ => panic!("Unexpected symbol in ^ span: {} at: {}", c, pos)
51                            }
52                        }
53                        panic!("Unterminated ^ span at {}", start);
54
55                    }
56                    '|' => {
57                        return Some((start, start));
58
59                    }
60                    _ => {
61                        panic!("Unexpected character: {}, ^, | or whitespace is allowed", c);
62                    }
63                }
64            }
65            None => None
66        }
67    }
68}
69
70#[cfg(test)]
71mod tests {
72    use super::Highlighter;
73
74    #[test]
75    fn single_span() {
76        let mut hl = Highlighter::new("|");
77        assert_eq!(hl.next(), Some((0, 0)));
78        assert_eq!(hl.next(), None);
79    }
80
81    #[test]
82    fn many_single_spans() {
83        let mut hl = Highlighter::new("|||");
84        assert_eq!(hl.next(), Some((0, 0)));
85        assert_eq!(hl.next(), Some((1, 1)));
86        assert_eq!(hl.next(), Some((2, 2)));
87        assert_eq!(hl.next(), None);
88    }
89
90    #[test]
91    fn single_span_after_whitespace() {
92        let mut hl = Highlighter::new("   |");
93        assert_eq!(hl.next(), Some((3, 3)));
94        assert_eq!(hl.next(), None);
95    }
96
97    #[test]
98    fn span_after_whitespace() {
99        let mut hl = Highlighter::new(" ^---^  ");
100        assert_eq!(hl.next(), Some((1, 5)));
101        assert_eq!(hl.next(), None);
102    }
103
104    #[test]
105    fn two_single_bars() {
106        let mut hl = Highlighter::new("| |");
107        assert_eq!(hl.next(), Some((0, 0)));
108        assert_eq!(hl.next(), Some((2, 2)));
109        assert_eq!(hl.next(), None);
110    }
111
112    #[test]
113    fn double_caret_single_bar() {
114        let mut hl = Highlighter::new("^^|");
115        assert_eq!(hl.next(), Some((0, 1)));
116        assert_eq!(hl.next(), Some((2, 2)));
117        assert_eq!(hl.next(), None);
118    }
119
120    #[test]
121    fn two_double_carets() {
122        let mut hl = Highlighter::new("^^^^");
123        assert_eq!(hl.next(), Some((0, 1)));
124        assert_eq!(hl.next(), Some((2, 3)));
125        assert_eq!(hl.next(), None);
126    }
127
128    #[test]
129    fn long_span_single_span() {
130        let mut hl = Highlighter::new("^--^|");
131        assert_eq!(hl.next(), Some((0, 3)));
132        assert_eq!(hl.next(), Some((4, 4)));
133        assert_eq!(hl.next(), None);
134    }
135
136    #[test]
137    fn long_span_double_caret() {
138        let mut hl = Highlighter::new("^--^^^");
139        assert_eq!(hl.next(), Some((0, 3)));
140        assert_eq!(hl.next(), Some((4, 5)));
141        assert_eq!(hl.next(), None);
142    }
143
144    #[test]
145    fn many_spans() {
146        let mut hl = Highlighter::new("^-^ ^^ ^--^ | ^----^  ");
147        assert_eq!(hl.next(), Some((0, 2)));
148        assert_eq!(hl.next(), Some((4, 5)));
149        assert_eq!(hl.next(), Some((7, 10)));
150        assert_eq!(hl.next(), Some((12, 12)));
151        assert_eq!(hl.next(), Some((14, 19)));
152        assert_eq!(hl.next(), None);
153    }
154
155    #[test]
156    fn alternate_space() {
157        let mut hl = Highlighter::new("II^^^^");
158        assert_eq!(hl.next(), Some((2, 3)));
159        assert_eq!(hl.next(), Some((4, 5)));
160        assert_eq!(hl.next(), None);
161    }
162
163
164    #[test]
165    fn unterminated_span() {
166        let mut hl = Highlighter::new("^--");
167        let r = std::panic::catch_unwind(move || hl.next());
168        assert!(r.is_err());
169    }
170
171    #[test]
172    fn unterminated_span2() {
173        let mut hl = Highlighter::new("^-- ");
174        let r = std::panic::catch_unwind(move || hl.next());
175        assert!(r.is_err());
176    }
177
178    #[test]
179    fn unterminated_span3() {
180        let mut hl = Highlighter::new("^^^");
181        let _ = hl.next();
182        let r = std::panic::catch_unwind(move || hl.next());
183        assert!(r.is_err());
184    }
185
186    #[test]
187    fn unterminated_span4() {
188        let mut hl = Highlighter::new("^");
189        let r = std::panic::catch_unwind(move || hl.next());
190        assert!(r.is_err());
191    }
192
193    #[test]
194    fn space_after_caret() {
195        let mut hl = Highlighter::new("^ ");
196        let r = std::panic::catch_unwind(move || hl.next());
197        assert!(r.is_err());
198    }
199
200    #[test]
201    fn bar_after_caret() {
202        let mut hl = Highlighter::new("^|");
203        let r = std::panic::catch_unwind(move || hl.next());
204        assert!(r.is_err());
205    }
206}