1use std::fmt;
13
14#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
16pub struct Span {
17 pub start: usize,
18 pub end: usize,
19}
20
21impl Span {
22 pub const fn new(start: usize, end: usize) -> Self {
23 Self { start, end }
24 }
25
26 pub const fn synthetic() -> Self {
29 Self {
30 start: usize::MAX,
31 end: usize::MAX,
32 }
33 }
34
35 pub const fn is_synthetic(&self) -> bool {
36 self.start == usize::MAX && self.end == usize::MAX
37 }
38
39 pub fn merge(self, other: Span) -> Span {
43 if self.is_synthetic() {
44 return other;
45 }
46 if other.is_synthetic() {
47 return self;
48 }
49 Span {
50 start: self.start.min(other.start),
51 end: self.end.max(other.end),
52 }
53 }
54
55 pub fn line_col(src: &str, byte_offset: usize) -> (usize, usize) {
59 let mut line = 1usize;
60 let mut col = 1usize;
61 for (i, ch) in src.char_indices() {
62 if i >= byte_offset {
63 return (line, col);
64 }
65 if ch == '\n' {
66 line += 1;
67 col = 1;
68 } else {
69 col += 1;
70 }
71 }
72 (line, col)
73 }
74}
75
76impl fmt::Display for Span {
77 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78 if self.is_synthetic() {
79 f.write_str("<synthetic>")
80 } else {
81 write!(f, "{}..{}", self.start, self.end)
82 }
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89
90 #[test]
91 fn merge_real_spans() {
92 let a = Span::new(5, 10);
93 let b = Span::new(8, 20);
94 assert_eq!(a.merge(b), Span::new(5, 20));
95 assert_eq!(b.merge(a), Span::new(5, 20));
96 }
97
98 #[test]
99 fn merge_with_synthetic_preserves_real() {
100 let a = Span::new(5, 10);
101 assert_eq!(a.merge(Span::synthetic()), a);
102 assert_eq!(Span::synthetic().merge(a), a);
103 }
104
105 #[test]
106 fn merge_two_synthetics_is_synthetic() {
107 let s = Span::synthetic();
108 assert!(s.merge(s).is_synthetic());
109 }
110
111 #[test]
112 fn line_col_counts_newlines() {
113 let src = "abc\nde\nfghi";
114 assert_eq!(Span::line_col(src, 0), (1, 1));
115 assert_eq!(Span::line_col(src, 2), (1, 3));
116 assert_eq!(Span::line_col(src, 4), (2, 1));
117 assert_eq!(Span::line_col(src, 7), (3, 1));
118 assert_eq!(Span::line_col(src, 10), (3, 4));
119 }
120
121 #[test]
122 fn display_synthetic() {
123 assert_eq!(Span::synthetic().to_string(), "<synthetic>");
124 }
125
126 #[test]
127 fn display_real() {
128 assert_eq!(Span::new(4, 9).to_string(), "4..9");
129 }
130}