Skip to main content

bynk_syntax/
span.rs

1//! Source position spans.
2
3/// A byte range in the source. Half-open: `[start, end)`.
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
5pub struct Span {
6    pub start: usize,
7    pub end: usize,
8}
9
10impl Span {
11    pub fn new(start: usize, end: usize) -> Self {
12        Self { start, end }
13    }
14
15    pub fn range(&self) -> std::ops::Range<usize> {
16        self.start..self.end
17    }
18
19    /// Span covering both `self` and `other` (the smallest enclosing range).
20    pub fn merge(self, other: Span) -> Span {
21        Span {
22            start: self.start.min(other.start),
23            end: self.end.max(other.end),
24        }
25    }
26}
27
28impl From<std::ops::Range<usize>> for Span {
29    fn from(r: std::ops::Range<usize>) -> Self {
30        Span {
31            start: r.start,
32            end: r.end,
33        }
34    }
35}
36
37/// 1-indexed (line, column) of a byte offset in `source`. Columns count
38/// characters, not bytes. Lives in the syntax leaf so every layer that maps a
39/// span to a position — the emitter's assertion locations, `bynkc`'s `short`
40/// rendering, and (slice 6) `bynk-render` — shares one implementation.
41pub fn line_col(source: &str, offset: usize) -> (usize, usize) {
42    let mut line = 1;
43    let mut col = 1;
44    for (i, ch) in source.char_indices() {
45        if i >= offset {
46            break;
47        }
48        if ch == '\n' {
49            line += 1;
50            col = 1;
51        } else {
52            col += 1;
53        }
54    }
55    (line, col)
56}