parse_pos/
lib.rs

1use std::{
2    fmt::{Debug, Display, Write},
3    hash::Hash,
4    ops::Range,
5    path::Path,
6};
7
8/// position span
9#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
10pub struct Position {
11    pub ln: Range<usize>,
12    pub col: Range<usize>,
13}
14/// `T` with a `Position` which is transparent in most cases
15pub struct Located<T> {
16    pub value: T,
17    pub pos: Position,
18}
19/// `T` with a `Position` and a `Path`
20pub struct PathLocated<T> {
21    pub value: T,
22    pub pos: Position,
23    pub path: Box<Path>,
24}
25
26impl Position {
27    pub fn new(ln: Range<usize>, col: Range<usize>) -> Self {
28        Self { ln, col }
29    }
30    /// extends it's span by another span
31    pub fn extend(&mut self, other: &Self) {
32        if self.ln.start > other.ln.start {
33            self.ln.start = other.ln.start;
34        }
35        if self.ln.end < other.ln.end {
36            self.ln.end = other.ln.end;
37        }
38        if self.col.start > other.col.start {
39            self.col.start = other.col.start;
40        }
41        if self.col.end < other.col.end {
42            self.col.end = other.col.end;
43        }
44    }
45}
46impl<T> Located<T> {
47    pub fn new(value: T, pos: Position) -> Self {
48        Self { value, pos }
49    }
50    /// creates `Located<T>` with `Position::default()``
51    pub fn new_default(value: T) -> Self {
52        Self {
53            value,
54            pos: Position::default(),
55        }
56    }
57    /// maps the inner value to a different value
58    pub fn map<U, F: Fn(T) -> U>(self, f: F) -> Located<U> {
59        Located {
60            value: f(self.value),
61            pos: self.pos,
62        }
63    }
64    pub fn with_path(self, path: Box<Path>) -> PathLocated<T> {
65        PathLocated {
66            value: self.value,
67            pos: self.pos,
68            path,
69        }
70    }
71}
72impl<T: Default> Located<T> {
73    /// only position and `T::default()`
74    pub fn default_pos(pos: Position) -> Self {
75        Self {
76            value: T::default(),
77            pos,
78        }
79    }
80}
81impl<T: Default> Default for Located<T> {
82    fn default() -> Self {
83        Self {
84            value: T::default(),
85            pos: Position::default(),
86        }
87    }
88}
89impl<T: Debug> Debug for Located<T> {
90    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91        self.value.fmt(f)
92    }
93}
94impl<T: Display> Display for Located<T> {
95    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96        self.value.fmt(f)
97    }
98}
99impl<T: Clone> Clone for Located<T> {
100    fn clone(&self) -> Self {
101        Self {
102            value: self.value.clone(),
103            pos: self.pos.clone(),
104        }
105    }
106}
107impl<T: PartialEq> PartialEq for Located<T> {
108    /// only the inner values get compared
109    fn eq(&self, other: &Self) -> bool {
110        self.value == other.value
111    }
112}
113impl<T: Eq> Eq for Located<T> {}
114impl<T: Hash> Hash for Located<T> {
115    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
116        self.value.hash(state);
117        self.pos.hash(state);
118    }
119}
120impl<T> PathLocated<T> {
121    pub fn new(value: T, pos: Position, path: Box<Path>) -> Self {
122        Self { value, pos, path }
123    }
124    /// maps the inner value to a different value
125    pub fn map<U, F: Fn(T) -> U>(self, f: F) -> PathLocated<U> {
126        PathLocated {
127            value: f(self.value),
128            pos: self.pos,
129            path: self.path,
130        }
131    }
132}
133impl<T: Debug> Debug for PathLocated<T> {
134    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135        self.value.fmt(f)
136    }
137}
138impl<T: Display> Display for PathLocated<T> {
139    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
140        self.value.fmt(f)
141    }
142}
143impl<T: Clone> Clone for PathLocated<T> {
144    fn clone(&self) -> Self {
145        Self {
146            value: self.value.clone(),
147            pos: self.pos.clone(),
148            path: self.path.clone(),
149        }
150    }
151}
152impl<T: PartialEq> PartialEq for PathLocated<T> {
153    /// only the inner values get compared
154    fn eq(&self, other: &Self) -> bool {
155        self.value == other.value
156    }
157}
158impl<T: Eq> Eq for PathLocated<T> {}
159impl<T: Hash> Hash for PathLocated<T> {
160    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
161        self.value.hash(state);
162        self.pos.hash(state);
163        self.path.hash(state);
164    }
165}
166
167impl Position {
168    pub fn display(&self, f: &mut String, content: &str) -> std::fmt::Result {
169        let lines = content.lines().collect::<Vec<&str>>();
170        let Some(lines) = lines.get(self.ln.start..=self.ln.end) else {
171            writeln!(f, "... code snippet unavailable ...")?;
172            return Ok(());
173        };
174        if lines.is_empty() {
175            writeln!(f, "... code snippet unavailable ...")?;
176            return Ok(());
177        }
178        let tab = 4;
179        if lines.len() == 1 {
180            let line = lines[0];
181            let ln = self.ln.start;
182            writeln!(f, "{:>tab$}| {line}", ln + 1)?;
183            writeln!(
184                f,
185                "{:>tab$}  {}",
186                "",
187                line.char_indices()
188                    .map(|(col, _)| if self.col.start <= col && self.col.end > col {
189                        '~'
190                    } else {
191                        ' '
192                    })
193                    .collect::<String>(),
194            )?;
195        } else {
196            let last_ln = lines.len() - 1;
197            for (ln, line) in lines.iter().copied().enumerate() {
198                writeln!(f, "{:>tab$}| {line}", ln + 1)?;
199                if ln == 0 {
200                    writeln!(
201                        f,
202                        "{:>tab$}  {}",
203                        "",
204                        line.char_indices()
205                            .map(|(col, _)| if self.col.start <= col { '~' } else { ' ' })
206                            .collect::<String>(),
207                    )?;
208                } else if ln == last_ln {
209                    writeln!(
210                        f,
211                        "{:>tab$}  {}",
212                        "",
213                        line.char_indices()
214                            .map(|(col, _)| if self.col.end > col { '~' } else { ' ' })
215                            .collect::<String>(),
216                    )?;
217                } else {
218                    writeln!(f, "{:>tab$}  {}", "", "~".repeat(line.len()),)?;
219                }
220            }
221        }
222        Ok(())
223    }
224}
225
226#[cfg(test)]
227mod tests {
228    use super::*;
229    #[test]
230    fn test() {
231        let text = "hello man\n  i like pizza";
232        let mut display = String::new();
233        Position::new(0..1, 1..5)
234            .display(&mut display, text)
235            .unwrap();
236        println!("{display}");
237        panic!();
238    }
239}