fob_graph/
span.rs

1use std::fmt;
2use std::path::{Path, PathBuf};
3
4use serde::{Deserialize, Serialize};
5
6/// Byte range within a source file used for diagnostics and tooling.
7#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
8pub struct SourceSpan {
9    pub file: PathBuf,
10    pub start: u32,
11    pub end: u32,
12}
13
14impl SourceSpan {
15    /// Construct a new span from a path and byte offsets.
16    pub fn new(file: impl AsRef<Path>, start: u32, end: u32) -> Self {
17        debug_assert!(start <= end, "span start must not exceed end");
18
19        Self {
20            file: file.as_ref().to_path_buf(),
21            start,
22            end,
23        }
24    }
25
26    /// Length of the span in bytes.
27    pub fn len(&self) -> u32 {
28        self.end.saturating_sub(self.start)
29    }
30
31    /// Returns true when the span has zero width.
32    pub fn is_empty(&self) -> bool {
33        self.start == self.end
34    }
35
36    /// Merge two spans that reference the same file.
37    pub fn merge(&self, other: &Self) -> Option<Self> {
38        if self.file != other.file {
39            return None;
40        }
41
42        Some(Self {
43            file: self.file.clone(),
44            start: self.start.min(other.start),
45            end: self.end.max(other.end),
46        })
47    }
48
49    /// Convert the byte offset to a 1-indexed `(line, column)` pair.
50    pub fn to_line_col(&self, source: &str) -> (usize, usize) {
51        let mut line = 1usize;
52        let mut col = 1usize;
53
54        for (idx, ch) in source.chars().enumerate() {
55            if idx >= self.start as usize {
56                break;
57            }
58
59            if ch == '\n' {
60                line += 1;
61                col = 1;
62            } else {
63                col += 1;
64            }
65        }
66
67        (line, col)
68    }
69
70    /// Check whether the span contains a byte offset.
71    pub fn contains(&self, offset: u32) -> bool {
72        offset >= self.start && offset < self.end
73    }
74}
75
76impl fmt::Display for SourceSpan {
77    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78        write!(f, "{}:{}-{}", self.file.display(), self.start, self.end)
79    }
80}