Skip to main content

rustyfi_syntax/
span.rs

1/// A source location. `line` is 1-based, `col` is a 0-based character column,
2/// `byte` is the byte offset into the source.
3#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
4pub struct Loc {
5    pub line: u32,
6    pub col: u32,
7    pub byte: usize,
8}
9
10/// A half-open source range `[start, end)`.
11#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
12pub struct Span {
13    pub start: Loc,
14    pub end: Loc,
15}
16
17impl Span {
18    pub(crate) fn new(start: Loc, end: Loc) -> Self {
19        Span { start, end }
20    }
21
22    /// The smallest span covering both `self` and `other` (Range.unite).
23    pub fn unite(self, other: Span) -> Span {
24        let dummy = Span::default();
25        if self == dummy {
26            return other;
27        }
28        if other == dummy {
29            return self;
30        }
31        let start = if self.start.byte <= other.start.byte {
32            self.start
33        } else {
34            other.start
35        };
36        let end = if self.end.byte >= other.end.byte {
37            self.end
38        } else {
39            other.end
40        };
41        Span { start, end }
42    }
43}
44
45impl syan::span::Span for Span {
46    fn migrate(self, other: Self) -> Self {
47        self.unite(other)
48    }
49}
50
51/// The largest `char` boundary at or below `byte`, clamped to `src.len()`.
52///
53/// Every consumer of a [`Span`] that wants to *slice* the source needs this:
54/// a span's byte offsets come from the lexer and are boundaries by
55/// construction, but a caller may have widened, clamped or defaulted one, and
56/// slicing a `str` off a boundary panics. Rounding down rather than panicking
57/// is the useful behaviour on both sides — a diagnostic covering one extra
58/// character beats no diagnostic at all.
59pub fn floor_char_boundary(src: &str, mut byte: usize) -> usize {
60    byte = byte.min(src.len());
61    while byte > 0 && !src.is_char_boundary(byte) {
62        byte -= 1;
63    }
64    byte
65}
66
67impl std::fmt::Display for Span {
68    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69        if self.start.line == self.end.line {
70            write!(
71                f,
72                "line {}, characters {}-{}",
73                self.start.line, self.start.col, self.end.col
74            )
75        } else {
76            write!(
77                f,
78                "line {}, character {} to line {}, character {}",
79                self.start.line, self.start.col, self.end.line, self.end.col
80            )
81        }
82    }
83}