bluejay_parser/error/
format_errors.rs

1use ariadne::Source;
2
3struct Index {
4    byte: usize,
5    char: usize,
6}
7
8/// `ariadne` uses char indices, but `logos` uses byte indices.
9/// This struct converts byte indices to char indices.
10pub(crate) struct ByteIndexToCharIndex<'a> {
11    document: &'a str,
12    mapping: Vec<Index>,
13}
14
15impl<'a> ByteIndexToCharIndex<'a> {
16    pub(crate) fn new(document: &'a str) -> Self {
17        Self {
18            document,
19            mapping: vec![Index { byte: 0, char: 0 }],
20        }
21    }
22
23    pub(crate) fn convert(&mut self, byte_idx: usize) -> usize {
24        match self
25            .mapping
26            .binary_search_by_key(&byte_idx, |index| index.byte)
27        {
28            Ok(idx) => self.mapping[idx].char,
29            Err(idx) => {
30                let char_idx = self.mapping[idx - 1].char
31                    + self.document[self.mapping[idx - 1].byte..byte_idx]
32                        .chars()
33                        .count();
34                self.mapping.insert(
35                    idx,
36                    Index {
37                        byte: byte_idx,
38                        char: char_idx,
39                    },
40                );
41                char_idx
42            }
43        }
44    }
45}
46
47pub struct SpanToLocation<'a> {
48    byte_idx_to_char_idx: ByteIndexToCharIndex<'a>,
49    source: Source<&'a str>,
50}
51
52impl<'a> SpanToLocation<'a> {
53    pub fn new(s: &'a str) -> Self {
54        Self {
55            byte_idx_to_char_idx: ByteIndexToCharIndex::new(s),
56            source: Source::from(s),
57        }
58    }
59
60    pub fn convert(&mut self, span: &crate::Span) -> Option<(usize, usize)> {
61        let span = span.byte_range();
62        let start = self.byte_idx_to_char_idx.convert(span.start);
63        self.source
64            .get_offset_line(start)
65            .map(|(_, line, col)| (line + 1, col + 1))
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    use super::SpanToLocation;
72    use crate::Span;
73
74    #[test]
75    fn test_span_to_location() {
76        let mut span_to_location = SpanToLocation::new("hello\r\nworld");
77        assert_eq!(span_to_location.convert(&Span::new(0..5)), Some((1, 1)));
78        assert_eq!(span_to_location.convert(&Span::new(7..12)), Some((2, 1)));
79    }
80}