xswag_base/code/
pos.rs

1//! Types and functions dealing with positions within the source code
2//!
3
4use std::ops::{self, Add, Sub};
5use std::cmp::{min, max};
6use std::fmt;
7
8// Helps implementing basic operators, like `Add` and `Sub`
9macro_rules! impl_math {
10    ($ty_name:ident, $trait_name:ident, $fun_name:ident) => {
11        impl $trait_name for $ty_name {
12            type Output = $ty_name;
13
14            fn $fun_name(self, rhs: $ty_name) -> $ty_name {
15                $ty_name($trait_name::$fun_name(self.0, rhs.0))
16            }
17        }
18    }
19}
20
21// ----------------------------------------------------------------------------
22/// Type do index one byte in a source code. It should be rather small, since
23/// it's used a lot.
24pub type SrcOffset = u32;
25
26/// Position within source specified by byte offset. This is not equal to
27/// `CharPos` thanks to UTF-8 and multibyte chars. This type always represents
28/// positions relative to the whole codemap.
29#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Default)]
30pub struct BytePos(pub SrcOffset);
31
32impl_math!(BytePos, Add, add);
33impl_math!(BytePos, Sub, sub);
34
35
36// ----------------------------------------------------------------------------
37/// A region within the source specified by first and last byte offset. `lo`
38/// byte is included in the span, `hi` byte is excluded.
39#[derive(Clone, Copy, PartialEq, Eq)]
40pub struct Span {
41    /// Low byte, inclusive
42    pub lo: BytePos,
43    /// High byte, exclusive
44    pub hi: BytePos,
45}
46
47impl Span {
48    /// Creates a span that points to a single char
49    pub fn single(pos: BytePos) -> Span {
50        Span { lo: pos, hi: pos + BytePos(1) }
51    }
52
53    /// Crates an empty span (points between to chars)
54    pub fn empty_at(pos: BytePos) -> Span {
55        Span { lo: pos, hi: pos }
56    }
57
58    /// Creates a span from a lo and hi (shorter than struct constructor
59    /// syntax)
60    pub fn new(lo: BytePos, hi: BytePos) -> Span {
61        Span { lo: lo, hi: hi }
62    }
63
64    /// Creates a span from a tuple
65    pub fn from_pair((lo, hi): (BytePos, BytePos)) -> Span {
66        Span { lo: lo, hi: hi }
67    }
68
69    /// Creates a dummy span. Should be used with caution.
70    pub fn dummy() -> Span {
71        Span { lo: BytePos(1), hi: BytePos(0) }
72    }
73
74    /// Checks if the this span is a dummy span
75    pub fn is_dummy(&self) -> bool {
76        self.lo.0 == 1 && self.hi.0 == 0
77    }
78
79    /// Checks if the span is empty
80    pub fn is_empty(&self) -> bool {
81        self.lo == self.hi
82    }
83
84    /// Returns the length (number of bytes) of the span or 0 if it's a dummy
85    /// span
86    pub fn len(&self) -> SrcOffset {
87        if self.is_dummy() {
88            0
89        } else {
90            (self.hi - self.lo).0
91        }
92    }
93
94    /// Returns the smallest span which encloses both given spans
95    ///
96    /// If one of given spans is a dummy span, it is ignored and the other span
97    /// is returned. If both spans are dummy spans, a dummy span is returned.
98    pub fn hull(&self, other: &Self) -> Span {
99        if self.is_dummy() {
100            *other
101        } else if other.is_dummy() {
102            *self
103        } else {
104            Span {
105                lo: min(self.lo, other.lo),
106                hi: max(self.hi, other.hi),
107            }
108        }
109    }
110
111    /// Checks if this span contains another span. A dummy span never contains
112    /// any other span and is never contained in another span.
113    pub fn contains(&self, other: Self) -> bool {
114        !self.is_dummy()
115            && !other.is_dummy()
116            && self.lo <= other.lo
117            && self.hi >= other.hi
118    }
119
120    /// Returns a range for indexing strings and vectors
121    pub fn into_range(self) -> ops::Range<usize> {
122        ops::Range {
123            start: self.lo.0 as usize,
124            end: self.hi.0 as usize,
125        }
126    }
127}
128
129// custom `Debug` impl to shorten debug output and improve readability
130impl fmt::Debug for Span {
131    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
132        write!(f, "@({}, {})", self.lo.0, self.hi.0)
133    }
134}
135
136
137// ----------------------------------------------------------------------------
138/// Represents a line index.
139#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Default)]
140pub struct LineIdx(pub SrcOffset);
141
142impl fmt::Display for LineIdx {
143    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
144        (self.0 + 1).fmt(f)
145    }
146}
147
148/// Represents a column index.
149#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Default)]
150pub struct ColIdx(pub SrcOffset);
151
152impl_math!(LineIdx, Add, add);
153impl_math!(LineIdx, Sub, sub);
154impl_math!(ColIdx, Add, add);
155impl_math!(ColIdx, Sub, sub);
156
157
158/// Location within one file specified by line and column.
159#[derive(Clone, Copy, PartialEq, Eq, Debug)]
160pub struct Loc {
161    pub line: LineIdx,
162    pub col: ColIdx,
163}
164
165// --- tests ---
166#[test]
167fn basic_spans() {
168    use super::Span;
169
170    let s = Span::new(BytePos(3), BytePos(10));
171    assert_eq!(s, Span { lo: BytePos(3), hi: BytePos(10) });
172    assert_eq!(s, Span::from_pair((BytePos(3), BytePos(10))));
173    assert_eq!(s.len(), 7);
174    assert!(!s.is_dummy());
175
176    assert!(s.contains(Span::new(BytePos(4), BytePos(9))));
177    assert!(s.contains(Span::new(BytePos(3), BytePos(10))));
178    assert!(s.contains(Span::new(BytePos(5), BytePos(10))));
179    assert!(s.contains(Span::new(BytePos(3), BytePos(8))));
180    assert!(!s.contains(Span::new(BytePos(2), BytePos(8))));
181    assert!(!s.contains(Span::new(BytePos(3), BytePos(11))));
182    assert!(!s.contains(Span::new(BytePos(1), BytePos(12))));
183    assert!(!s.contains(Span::dummy()));
184
185    assert_eq!(Span::single(BytePos(15)), Span::new(BytePos(15), BytePos(16)));
186}
187
188#[test]
189fn dummy_spans() {
190    use super::Span;
191
192    let d = Span::dummy();
193    assert_eq!(d, Span::dummy());
194    assert_eq!(d.len(), 0);
195    assert!(d.is_dummy());
196
197    assert!(!d.contains(Span::new(BytePos(4), BytePos(9))));
198    assert!(!d.contains(Span::new(BytePos(3), BytePos(0))));
199    assert!(!d.contains(Span::dummy()));
200}
201
202#[test]
203fn span_hulls() {
204    use super::Span;
205
206    let d = Span::dummy();
207    let a = Span::new(BytePos(1), BytePos(5));
208    let b = Span::new(BytePos(3), BytePos(7));
209    let c = Span::new(BytePos(7), BytePos(9));
210
211    assert_eq!(a.hull(&d), a);
212    assert_eq!(d.hull(&a), a);
213    assert_eq!(d.hull(&d), d);
214
215    assert_eq!(a.hull(&b), Span::new(BytePos(1), BytePos(7)));
216    assert_eq!(a.hull(&c), Span::new(BytePos(1), BytePos(9)));
217    assert_eq!(b.hull(&c), Span::new(BytePos(3), BytePos(9)));
218}