error_enum_core/
indexer.rs

1use std::{rc::Rc, sync::Arc};
2use stringzilla::sz::find_newline_utf8;
3
4/// A indexable string.
5pub trait Indexer {
6    /// Returns the line and column number of this `Position`.
7    fn line_col_at(&self, pos: usize) -> (usize, usize);
8
9    /// Returns the start and the end of the line that contains the position at `pos`.
10    fn line_span_at(&self, pos: usize) -> (usize, usize);
11
12    /// Returns the start and the end of the `(context_lines_before + 1 + context_lines_after)`
13    /// lines that contains the position at `pos`.
14    fn span_with_context_lines(
15        &self,
16        start: usize,
17        end: usize,
18        context_lines_before: usize,
19        context_lines_after: usize,
20    ) -> (usize, usize);
21}
22
23macro_rules! impl_indexable {
24    ($T:ty) => {
25        impl<T: Indexer + ?Sized> Indexer for $T {
26            fn line_col_at(&self, pos: usize) -> (usize, usize) {
27                T::line_col_at(self, pos)
28            }
29
30            fn line_span_at(&self, pos: usize) -> (usize, usize) {
31                T::line_span_at(self, pos)
32            }
33
34            fn span_with_context_lines(
35                &self,
36                start: usize,
37                end: usize,
38                context_lines_before: usize,
39                context_lines_after: usize,
40            ) -> (usize, usize) {
41                T::span_with_context_lines(
42                    self,
43                    start,
44                    end,
45                    context_lines_before,
46                    context_lines_after,
47                )
48            }
49        }
50    };
51}
52
53impl_indexable!(Box<T>);
54impl_indexable!(Rc<T>);
55impl_indexable!(Arc<T>);
56
57/// An [`Indexer`] that stores ending positions of every line (including trailing newlines).
58#[derive(Debug, PartialEq, Eq)]
59#[repr(transparent)]
60pub struct LineIndexer([usize]);
61
62impl LineIndexer {
63    /// Create an [`LineIndexer`].
64    pub fn new(s: &str) -> Box<Self> {
65        let mut line_starts = Vec::new();
66        let mut cur = 0usize;
67        let mut slice = s.as_bytes();
68        while let Some(index) = find_newline_utf8(slice) {
69            line_starts.push(cur + index.end());
70            cur += index.end();
71            slice = &slice[index.end()..]
72        }
73        line_starts.push(s.len());
74        let line_starts = line_starts.into_boxed_slice();
75        unsafe { std::mem::transmute(line_starts) }
76    }
77}
78
79impl LineIndexer {
80    fn line_start_at(&self, pos: usize) -> usize {
81        match self.0.binary_search(&pos) {
82            Ok(i) => self.0[i],
83            Err(0) => 0,
84            Err(i) => self.0[i.saturating_sub(1)],
85        }
86    }
87    fn line_at(&self, pos: usize) -> usize {
88        match self.0.binary_search(&pos) {
89            Ok(i) => i + 1,
90            Err(i) => i,
91        }
92    }
93}
94
95impl Indexer for LineIndexer {
96    fn line_col_at(&self, pos: usize) -> (usize, usize) {
97        let line_start = self.line_start_at(pos);
98        debug_assert!(pos >= line_start);
99        (line_start, pos - line_start)
100    }
101
102    fn line_span_at(&self, pos: usize) -> (usize, usize) {
103        match self.0.binary_search(&pos) {
104            Ok(i) if i + 1 == self.0.len() => (self.0[i], self.0[i]),
105            Ok(i) => (self.0[i], self.0[i + 1]),
106            Err(0) => (0, self.0[0]),
107            Err(i) if i == self.0.len() => {
108                let j = i.saturating_sub(1);
109                (self.0[j], self.0[j])
110            }
111            Err(i) => (self.0[i.saturating_sub(1)], self.0[i]),
112        }
113    }
114
115    fn span_with_context_lines(
116        &self,
117        start: usize,
118        end: usize,
119        context_lines_before: usize,
120        context_lines_after: usize,
121    ) -> (usize, usize) {
122        let start = if context_lines_before == 0 {
123            start
124        } else {
125            self.line_at(start)
126                .saturating_sub(context_lines_before)
127                .checked_sub(1)
128                .map_or_else(|| 0, |i| self.0[i])
129        };
130        let end = if context_lines_after == 0 {
131            end
132        } else {
133            self.0[self
134                .line_at(end)
135                .saturating_add(context_lines_after)
136                .min(self.0.len() - 1)]
137        };
138        (start, end)
139    }
140}