Skip to main content

hermes_support/
location.rs

1//! Offset-based source locations (Rust analog of `llvh::SMLoc`).
2
3use std::num::NonZeroU32;
4
5/// Opaque identifier of a source registered with the manager. 1-based: index 0
6/// maps to `NonZeroU32(1)`, so `Option<SourceId>` is the same size as `SourceId`.
7#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
8pub struct SourceId(NonZeroU32);
9
10impl SourceId {
11    pub fn from_index(index: u32) -> SourceId {
12        SourceId(NonZeroU32::new(index + 1).expect("index + 1 is nonzero"))
13    }
14    pub fn index(self) -> u32 {
15        self.0.get() - 1
16    }
17}
18
19/// A location in a source buffer. The "encoded" form: a buffer plus a byte
20/// offset into it. Rust analog of `llvh::SMLoc`, but it carries its buffer
21/// identity, so finding the containing buffer is trivial.
22#[derive(Copy, Clone, PartialEq, Eq, Debug)]
23pub struct SMLoc {
24    pub source: SourceId,
25    pub offset: u32,
26}
27
28/// A half-open range of source locations `[start, end)` within one buffer.
29#[derive(Copy, Clone, PartialEq, Eq, Debug)]
30pub struct SMRange {
31    pub start: SMLoc,
32    pub end: SMLoc,
33}
34
35impl SMRange {
36    /// Build the smallest range covering both `a` and `b` (both must be in the
37    /// same buffer). The end is exclusive, so it is `max(a,b).offset + 1`.
38    /// Port of `SourceErrorManager::combineIntoRange`.
39    pub fn combine(a: SMLoc, b: SMLoc) -> SMRange {
40        debug_assert_eq!(a.source, b.source);
41        let (lo, hi) = if a.offset <= b.offset { (a, b) } else { (b, a) };
42        SMRange {
43            start: lo,
44            end: SMLoc {
45                source: hi.source,
46                offset: hi.offset + 1,
47            },
48        }
49    }
50}
51
52/// The "decoded" form of an `SMLoc`: buffer id, 1-based line and column.
53/// Port of `SourceErrorManager::SourceCoords`.
54#[derive(Copy, Clone, PartialEq, Eq, Debug)]
55pub struct SourceCoords {
56    /// 1-based buffer id (i.e. `SourceId::index() + 1`).
57    pub buf: SourceId,
58    /// 1-based line number.
59    pub line: u32,
60    /// 1-based column.
61    pub col: u32,
62}
63
64impl SourceCoords {
65    pub fn is_same_source_line_as(&self, o: &SourceCoords) -> bool {
66        self.buf == o.buf && self.line == o.line
67    }
68    pub fn less(&self, o: &SourceCoords) -> bool {
69        (self.buf.index(), self.line, self.col) < (o.buf.index(), o.line, o.col)
70    }
71}
72
73/// Result of looking up a line: buffer, 1-based line number, and a reference to
74/// the line itself (including EOL if present). Port of `LineCoord`.
75#[allow(dead_code)]
76#[derive(Copy, Clone, Debug)]
77pub struct LineCoord<'a> {
78    pub buf: SourceId,
79    pub line: u32,
80    pub line_ref: &'a [u8],
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86
87    #[test]
88    fn source_id_niche() {
89        // Option<SourceId> must be the same size as SourceId (niche optimization).
90        assert_eq!(
91            std::mem::size_of::<Option<SourceId>>(),
92            std::mem::size_of::<SourceId>()
93        );
94        assert_eq!(std::mem::size_of::<SMLoc>(), 8);
95    }
96
97    #[test]
98    fn coords_ordering_and_same_line() {
99        let s = SourceId::from_index(0);
100        let a = SourceCoords {
101            buf: s,
102            line: 2,
103            col: 3,
104        };
105        let b = SourceCoords {
106            buf: s,
107            line: 2,
108            col: 5,
109        };
110        assert!(a.less(&b));
111        assert!(a.is_same_source_line_as(&b));
112    }
113
114    #[test]
115    fn combine_into_range_orders_endpoints() {
116        let s = SourceId::from_index(0);
117        let lo = SMLoc {
118            source: s,
119            offset: 4,
120        };
121        let hi = SMLoc {
122            source: s,
123            offset: 9,
124        };
125        let r = SMRange::combine(hi, lo);
126        assert_eq!(r.start.offset, 4);
127        assert_eq!(r.end.offset, 10); // end is exclusive: max + 1
128    }
129}