Skip to main content

rantlr_core/
span.rs

1//! Source locations used by every stage (lexer → parser → linter → LSP).
2
3/// Zero-based UTF-8 byte offset into the source file.
4#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
5pub struct BytePos(pub u32);
6
7impl BytePos {
8    pub const fn new(offset: u32) -> Self {
9        Self(offset)
10    }
11
12    pub const fn as_usize(self) -> usize {
13        self.0 as usize
14    }
15}
16
17/// Half-open byte range `[start, end)` in the original source.
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
19pub struct Span {
20    pub start: BytePos,
21    pub end: BytePos,
22}
23
24impl Span {
25    pub const fn new(start: BytePos, end: BytePos) -> Self {
26        Self { start, end }
27    }
28
29    pub fn from_offsets(start: usize, end: usize) -> Self {
30        Self {
31            start: BytePos(start as u32),
32            end: BytePos(end as u32),
33        }
34    }
35
36    pub const fn len(self) -> u32 {
37        self.end.0.saturating_sub(self.start.0)
38    }
39
40    pub fn merge(self, other: Span) -> Span {
41        Span {
42            start: self.start.min(other.start),
43            end: self.end.max(other.end),
44        }
45    }
46
47    /// 1-based line/column for human diagnostics.
48    pub fn line_col(self, source: &str) -> (usize, usize) {
49        let mut line = 1usize;
50        let mut col = 1usize;
51        for (i, ch) in source.char_indices() {
52            if i >= self.start.as_usize() {
53                break;
54            }
55            if ch == '\n' {
56                line += 1;
57                col = 1;
58            } else {
59                col += 1;
60            }
61        }
62        (line, col)
63    }
64}