Skip to main content

m2_syn/
span.rs

1use std::cmp::Ordering;
2use std::error::Error;
3use std::fmt::{Display, Formatter, Result as FmtResult};
4use std::ops::{BitAnd, BitOr, Bound, RangeBounds};
5use std::sync::atomic::{AtomicU64, Ordering as AtomicOrdering};
6
7const FIRST_SOURCE_ID: u64 = 1;
8static NEXT_SOURCE_ID: AtomicU64 = AtomicU64::new(FIRST_SOURCE_ID);
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11pub struct SourceId(pub u64);
12
13impl SourceId {
14    /// Allocates a process-unique identity for one parsed source.
15    pub fn fresh() -> Self {
16        let id = NEXT_SOURCE_ID
17            .fetch_update(
18                AtomicOrdering::Relaxed,
19                AtomicOrdering::Relaxed,
20                |current| current.checked_add(1),
21            )
22            .expect("source id counter exhausted");
23        Self(id)
24    }
25}
26
27/// Complete describption of position in a text document.
28#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
29pub struct TextPoint {
30    pub line: u32,
31    pub column: u32,
32    pub byte: usize,
33}
34
35impl TextPoint {
36    pub fn new(line: u32, column: u32, byte: usize) -> Self {
37        Self { line, column, byte }
38    }
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
42pub enum TextRange {
43    Range {
44        start: TextPoint,
45        end: TextPoint,
46    },
47    #[default]
48    Empty,
49}
50
51impl TextRange {
52    pub fn from_point(point: TextPoint) -> Self {
53        Self::Range {
54            start: point,
55            end: point,
56        }
57    }
58
59    pub fn new(start: TextPoint, end: TextPoint) -> Self {
60        match start.cmp(&end) {
61            Ordering::Less => Self::Range { start, end },
62            Ordering::Equal => Self::from_point(start),
63            Ordering::Greater => Self::Empty,
64        }
65    }
66
67    pub fn join(self, other: Self) -> Self {
68        match (self, other) {
69            (Self::Empty, _) => other,
70            (_, Self::Empty) => self,
71            (
72                Self::Range { start, end },
73                Self::Range {
74                    start: start2,
75                    end: end2,
76                },
77            ) => Self::new(start.min(start2), end.max(end2)),
78        }
79    }
80
81    pub fn meet(self, other: Self) -> Self {
82        match (self, other) {
83            (Self::Empty, _) => Self::Empty,
84            (_, Self::Empty) => Self::Empty,
85            (
86                Self::Range { start, end },
87                Self::Range {
88                    start: start2,
89                    end: end2,
90                },
91            ) => Self::new(start.max(start2), end.min(end2)),
92        }
93    }
94
95    pub fn start(&self) -> Option<TextPoint> {
96        match self {
97            Self::Empty => None,
98            Self::Range { start, .. } => Some(*start),
99        }
100    }
101
102    pub fn end(&self) -> Option<TextPoint> {
103        match self {
104            Self::Empty => None,
105            Self::Range { end, .. } => Some(*end),
106        }
107    }
108
109    pub fn is_empty(&self) -> bool {
110        matches!(self, Self::Empty)
111    }
112
113    pub fn is_point(&self) -> bool {
114        matches!(self, Self::Range { start, end } if start == end)
115    }
116
117    pub fn size(&self) -> Option<usize> {
118        self.end()?.byte.checked_sub(self.start()?.byte)
119    }
120}
121
122impl BitOr for TextRange {
123    type Output = Self;
124
125    fn bitor(self, other: Self) -> Self {
126        self.join(other)
127    }
128}
129
130impl BitAnd for TextRange {
131    type Output = Self;
132
133    fn bitand(self, other: Self) -> Self {
134        self.meet(other)
135    }
136}
137
138/// transforming range a..b of two text points interpretable as
139/// text range into the two endpoints a, b  
140impl RangeBounds<TextPoint> for TextRange {
141    fn start_bound(&self) -> Bound<&TextPoint> {
142        match self {
143            Self::Empty => Bound::Unbounded,
144            Self::Range { start, .. } => Bound::Included(start),
145        }
146    }
147
148    fn end_bound(&self) -> Bound<&TextPoint> {
149        match self {
150            Self::Empty => Bound::Unbounded,
151            Self::Range { end, .. } => Bound::Included(end),
152        }
153    }
154}
155
156#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
157pub enum Span {
158    #[default]
159    Detached,
160    FileLocated {
161        source: SourceId,
162        range: TextRange,
163    },
164    LocalRefFrame {
165        range: TextRange,
166    },
167}
168
169impl Span {
170    pub fn detached() -> Self {
171        Self::default()
172    }
173
174    pub fn new(source: SourceId, range: TextRange) -> Self {
175        Self::FileLocated { source, range }
176    }
177
178    pub fn in_tmp_file(range: TextRange) -> Self {
179        Self::LocalRefFrame { range }
180    }
181
182    pub fn source(self) -> Result<SourceId, MissingData> {
183        match self {
184            Self::FileLocated { source, .. } => Ok(source),
185            _ => Err(MissingData),
186        }
187    }
188
189    pub fn range(self) -> Result<TextRange, MissingData> {
190        match self {
191            Self::Detached => Err(MissingData),
192            Self::FileLocated { range, .. } => Ok(range),
193            Self::LocalRefFrame { range } => Ok(range),
194        }
195    }
196
197    pub fn start_point(self) -> Result<TextPoint, MissingData> {
198        self.range()?.start().ok_or(MissingData)
199    }
200
201    pub fn end_point(self) -> Result<TextPoint, MissingData> {
202        self.range()?.end().ok_or(MissingData)
203    }
204
205    pub fn byte_size(self) -> Result<usize, MissingData> {
206        self.range()?.size().ok_or(MissingData)
207    }
208
209    pub fn join(self, other: Self) -> Self {
210        match (self, other) {
211            (Self::Detached, span) | (span, Self::Detached) => span,
212            (
213                Self::FileLocated { source, range },
214                Self::FileLocated {
215                    source: source2,
216                    range: range2,
217                },
218            ) => {
219                if source == source2 {
220                    Self::new(source, range | range2)
221                } else {
222                    Self::Detached
223                }
224            }
225
226            (Self::LocalRefFrame { range }, Self::LocalRefFrame { range: range2 }) => {
227                Self::in_tmp_file(range | range2)
228            }
229            (_, _) => Self::Detached,
230        }
231    }
232
233    pub fn join_all(spans: impl IntoIterator<Item = Span>) -> Self {
234        spans.into_iter().reduce(Span::join).unwrap_or_default()
235    }
236}
237
238#[derive(Debug, Clone, Copy, PartialEq, Eq)]
239pub struct MissingData;
240
241impl Display for MissingData {
242    fn fmt(&self, formatter: &mut Formatter<'_>) -> FmtResult {
243        formatter.write_str("syntax has no source location")
244    }
245}
246
247impl Error for MissingData {}
248
249pub trait Spanned {
250    fn span(&self) -> Span;
251}
252
253impl Spanned for Span {
254    fn span(&self) -> Span {
255        *self
256    }
257}
258
259impl<T: Spanned + ?Sized> Spanned for &T {
260    fn span(&self) -> Span {
261        (*self).span()
262    }
263}
264
265impl<T: Spanned + ?Sized> Spanned for Box<T> {
266    fn span(&self) -> Span {
267        self.as_ref().span()
268    }
269}
270
271impl<T: Spanned> Spanned for Option<T> {
272    fn span(&self) -> Span {
273        self.as_ref().map_or_else(Span::detached, Spanned::span)
274    }
275}
276
277impl<T: Spanned> Spanned for Vec<T> {
278    fn span(&self) -> Span {
279        Span::join_all(self.iter().map(Spanned::span))
280    }
281}
282
283impl<A: Spanned, B: Spanned> Spanned for (A, B) {
284    fn span(&self) -> Span {
285        self.0.span().join(self.1.span())
286    }
287}
288
289impl<T: Spanned> Spanned for &[T] {
290    fn span(&self) -> Span {
291        Span::join_all(self.iter().map(Spanned::span))
292    }
293}