rustemo/
location.rs

1use std::{
2    fmt::{Debug, Display},
3    ops::Deref,
4};
5
6/// A line-column based location for use where applicable (e.g. plain text).
7#[derive(PartialEq, Eq, Debug, Copy, Clone)]
8pub struct LineColumn {
9    pub line: usize,
10    pub column: usize,
11}
12
13/// A position in the input file.
14#[derive(PartialEq, Eq, Debug, Copy, Clone)]
15pub enum Position {
16    Position(usize),
17    LineBased(LineColumn),
18}
19
20impl Position {
21    pub fn from_lc(line: usize, column: usize) -> Self {
22        Self::LineBased(LineColumn { line, column })
23    }
24
25    pub fn from_pos(pos: usize) -> Self {
26        Self::Position(pos)
27    }
28
29    #[inline]
30    pub fn line(&self) -> usize {
31        match self {
32            Position::Position(pos) => *pos,
33            Position::LineBased(lb) => lb.line,
34        }
35    }
36
37    #[inline]
38    pub fn column(&self) -> usize {
39        match self {
40            Position::Position(_) => 0,
41            Position::LineBased(lb) => lb.column,
42        }
43    }
44
45    #[inline]
46    pub fn position(&self) -> usize {
47        match self {
48            Position::Position(pos) => *pos,
49            Position::LineBased(lb) => lb.line,
50        }
51    }
52}
53
54impl Default for Position {
55    fn default() -> Self {
56        Position::Position(0)
57    }
58}
59
60impl Display for Position {
61    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62        match self {
63            Position::Position(pos) => write!(f, "{pos}"),
64            Position::LineBased(lb) => write!(f, "{},{}", lb.line, lb.column),
65        }
66    }
67}
68
69impl From<Location> for Position {
70    fn from(value: Location) -> Self {
71        value.end.unwrap_or(value.start)
72    }
73}
74
75/// Describes a span from start till end in the parsed input.
76///
77/// Start is mandatory while the end is not.
78///
79/// The location doesn't keep the path of the parsed file on purpose as it will
80/// be the same for the single parse tree so it would either unnccessary waste
81/// memory, in case of owned strings, or propagate lifetime information throught
82/// the API in case of borrowed string slice.
83///
84/// The path is kept on the parsing context and there is the method on the
85/// context to produce the display of the location with the full file path.
86#[derive(PartialEq, Eq, Clone, Copy, Default)]
87pub struct Location {
88    /// The start position of the range.
89    pub start: Position,
90    /// The end position. Sometimes it is not known or relevant.
91    pub end: Option<Position>,
92}
93
94impl Location {
95    pub fn new(start: Position, end: Position) -> Self {
96        Self {
97            start,
98            end: Some(end),
99        }
100    }
101    pub fn from_start(start: Position) -> Self {
102        Self { start, end: None }
103    }
104
105    /// Creates a new location starting from the current location ending with
106    /// loc_to location.
107    pub fn to(&self, loc_to: Self) -> Self {
108        Self {
109            start: self.start,
110            end: loc_to.end.or(Some(loc_to.start)),
111        }
112    }
113}
114
115impl Debug for Location {
116    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
117        match self.end {
118            Some(ref end) => write!(f, "[{}-{}]", self.start, end),
119            None => write!(f, "[{}]", self.start),
120        }
121    }
122}
123
124// impl<'i, I, S, TK, C> From<C> for Location
125// where
126//     I: Input + ?Sized,
127//     C: Context<'i, I, S, TK>
128// {
129//     fn from(context: &mut C) -> Self {
130//         context.location()
131//     }
132// }
133
134/// Value with location. Used in place of parsed values which need locations to
135/// report errors during semantic analysis.
136#[derive(Debug, Clone)]
137pub struct ValLoc<T> {
138    value: T,
139    pub location: Option<Location>,
140}
141
142impl<T: Display> Display for ValLoc<T> {
143    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
144        write!(f, "{}", self.value)
145    }
146}
147
148impl<T> AsRef<T> for ValLoc<T> {
149    fn as_ref(&self) -> &T {
150        &self.value
151    }
152}
153
154impl<T> From<T> for ValLoc<T> {
155    fn from(value: T) -> Self {
156        Self {
157            value,
158            location: None,
159        }
160    }
161}
162
163impl<T> Deref for ValLoc<T> {
164    type Target = T;
165
166    fn deref(&self) -> &Self::Target {
167        &self.value
168    }
169}
170
171impl From<ValLoc<String>> for String {
172    fn from(value: ValLoc<String>) -> Self {
173        value.value
174    }
175}
176impl<T> ValLoc<T> {
177    pub fn new(value: T, location: Option<Location>) -> Self {
178        Self { value, location }
179    }
180}
181macro_rules! from_valloc {
182    ($type:ty) => {
183        impl From<$crate::location::ValLoc<$type>> for $type {
184            fn from(value: $crate::location::ValLoc<Self>) -> Self {
185                value.value
186            }
187        }
188        impl From<&$crate::location::ValLoc<$type>> for $type
189          where $type: Copy
190          {
191            fn from(value: &$crate::location::ValLoc<Self>) -> Self {
192                value.value
193            }
194        }
195    };
196    ($($type:ty),+) => {
197        $(from_valloc!($type);)+
198    };
199}
200// Implement value with location support for all primitive types.
201from_valloc!(i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, f32, f64, bool, char);
202
203#[cfg(test)]
204mod tests {
205    use super::{Location, Position};
206
207    #[test]
208    pub fn test_position_linebased() {
209        let p = Position::from_lc(2, 4);
210
211        assert_eq!(p.line(), 2);
212        assert_eq!(p.column(), 4);
213        assert_eq!(format!("{p}"), "2,4");
214    }
215
216    #[test]
217    pub fn test_position() {
218        let p = Position::from_pos(5);
219
220        assert_eq!(p.line(), 5);
221        assert_eq!(p.column(), 0);
222        assert_eq!(p.position(), 5);
223        assert_eq!(format!("{p}"), "5");
224    }
225
226    #[test]
227    pub fn test_location() {
228        let r = Location::new(Position::from_lc(5, 15), Position::from_lc(13, 27));
229
230        assert_eq!(format!("{r:?}"), "[5,15-13,27]");
231
232        let r = Location::new(Position::from_lc(5, 15), Position::from_pos(49));
233
234        assert_eq!(format!("{r:?}"), "[5,15-49]");
235    }
236}