1mod line_col;
17mod refer;
18mod src_referrer;
19
20pub use line_col::*;
21pub use refer::*;
22pub use src_referrer::*;
23
24use derive_more::Deref;
25use miette::SourceSpan;
26
27#[derive(Clone, Default, Deref)]
31pub struct SrcRef(pub Option<Box<SrcRefInner>>);
32
33impl SrcRef {
34 pub fn new(
39 range: std::ops::Range<usize>,
40 line: usize,
41 col: usize,
42 source_file_hash: u64,
43 ) -> Self {
44 Self(Some(Box::new(SrcRefInner {
45 range,
46 at: LineCol { line, col },
47 source_file_hash,
48 })))
49 }
50
51 pub fn as_miette_span(&self) -> Option<SourceSpan> {
53 self.0.as_ref().map(|s| SourceSpan::new(s.range.start.into(), s.range.len()))
54 }
55
56 pub fn with_line_offset(self, offset: usize) -> Self {
58 Self(self.0.map(|inner| Box::new(inner.with_line_offset(offset))))
59 }
60}
61
62impl From<SrcRef> for SourceSpan {
63 fn from(value: SrcRef) -> Self {
64 value.as_miette_span().unwrap_or(SourceSpan::new(0.into(), 0))
65 }
66}
67
68#[derive(Clone, Default)]
70pub struct SrcRefInner {
71 pub range: std::ops::Range<usize>,
73 pub at: LineCol,
75 pub source_file_hash: u64,
77}
78
79impl SrcRefInner {
80 pub fn is_overlapping(&self, other: &Self) -> bool {
82 self.source_file_hash != 0
83 && other.source_file_hash != 0
84 && (self.range.start < other.range.end)
85 && (other.range.start < self.range.end)
86 }
87
88 pub fn with_line_offset(&self, line_offset: usize) -> Self {
90 let mut s = self.clone();
91 s.at.line += line_offset;
92 s
93 }
94}
95
96impl std::fmt::Display for SrcRef {
97 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
98 match &self.0 {
99 Some(s) => write!(f, "{}", s.at),
100 _ => write!(f, crate::invalid_no_ansi!(REF)),
101 }
102 }
103}
104
105impl std::fmt::Debug for SrcRef {
106 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107 match &self.0 {
108 Some(s) => write!(
109 f,
110 "{} ({}..{}) in {:#x}",
111 s.at, s.range.start, s.range.end, s.source_file_hash
112 ),
113 _ => write!(f, crate::invalid!(REF)),
114 }
115 }
116}
117
118impl PartialEq for SrcRef {
119 fn eq(&self, _: &Self) -> bool {
120 true
121 }
122}
123
124impl PartialOrd for SrcRef {
125 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
126 Some(self.cmp(other))
127 }
128}
129
130impl Eq for SrcRef {}
131
132impl Ord for SrcRef {
133 fn cmp(&self, _: &Self) -> std::cmp::Ordering {
134 std::cmp::Ordering::Equal
135 }
136}
137
138impl SrcRef {
139 pub fn len(&self) -> usize {
141 self.0.as_ref().map(|s| s.range.len()).unwrap_or(0)
142 }
143
144 #[must_use]
146 pub fn is_empty(&self) -> bool {
147 self.len() == 0
148 }
149
150 pub fn source_hash(&self) -> u64 {
156 self.0.as_ref().map(|s| s.source_file_hash).unwrap_or(0)
157 }
158
159 pub fn source_slice<'a>(&self, src: &'a str) -> &'a str {
161 &src[self.0.as_ref().expect("SrcRef").range.to_owned()]
162 }
163
164 pub fn merge(lhs: &impl SrcReferrer, rhs: &impl SrcReferrer) -> SrcRef {
171 match (lhs.src_ref(), rhs.src_ref()) {
172 (SrcRef(Some(lhs)), SrcRef(Some(rhs))) => {
173 if lhs.source_file_hash == rhs.source_file_hash {
174 let source_file_hash = lhs.source_file_hash;
175
176 if lhs.range.end > rhs.range.start || lhs.range.start > rhs.range.end {
177 log::warn!("ranges not in correct order");
178 SrcRef(None)
179 } else {
180 SrcRef(Some(Box::new(SrcRefInner {
181 range: {
182 assert!(lhs.range.end <= rhs.range.end);
184 assert!(lhs.range.start <= rhs.range.start);
185
186 lhs.range.start..rhs.range.end
187 },
188 at: lhs.at,
189 source_file_hash,
190 })))
191 }
192 } else {
193 log::warn!("references are not in the same file");
194 SrcRef(None)
195 }
196 }
197 (SrcRef(Some(hs)), SrcRef(None)) | (SrcRef(None), SrcRef(Some(hs))) => SrcRef(Some(hs)),
198 _ => SrcRef(None),
199 }
200 }
201
202 pub fn merge_all<S: SrcReferrer>(referrers: impl Iterator<Item = S>) -> SrcRef {
206 let mut result = SrcRef(None);
207 for referrer in referrers {
208 if let Some(src_ref) = referrer.src_ref().0 {
209 if let SrcRef(Some(result)) = &mut result {
210 if result.source_file_hash != src_ref.source_file_hash {
211 panic!("can only merge source references of the same file");
212 }
213 if src_ref.range.start < result.range.start {
214 result.range.start = src_ref.range.start;
215 result.at = src_ref.at;
216 }
217 result.range.end = std::cmp::max(src_ref.range.end, result.range.end);
218 } else {
219 result = SrcRef(Some(src_ref));
220 }
221 }
222 }
223 result
224 }
225
226 pub fn at(&self) -> Option<LineCol> {
228 self.0.as_ref().map(|s| s.at.clone())
229 }
230 pub fn is_overlapping(&self, other: &Self) -> bool {
234 match (&self.0, &other.0) {
235 (Some(a), Some(b)) => a.is_overlapping(b),
236 _ => false,
237 }
238 }
239}
240
241#[test]
242fn merge_all() {
243 use std::ops::Range;
244 assert_eq!(
245 SrcRef::merge_all(
246 [
247 SrcRef::new(Range { start: 5, end: 8 }, 1, 6, 123),
248 SrcRef::new(Range { start: 8, end: 10 }, 2, 1, 123),
249 SrcRef::new(Range { start: 12, end: 16 }, 3, 1, 123),
250 SrcRef::new(Range { start: 0, end: 10 }, 1, 1, 123),
251 ]
252 .iter(),
253 ),
254 SrcRef::new(Range { start: 0, end: 16 }, 1, 1, 123),
255 );
256}
257
258#[test]
259fn test_src_ref() {
260 let input = "geo3d::Cube(size_x = 3.0, size_y = 3.0, size_z = 3.0);";
261
262 let cube = 7..11;
263 let size_y = 26..32;
264
265 let cube = SrcRef::new(cube, 1, 0, 0);
266 let size_y = SrcRef::new(size_y, 1, 0, 0);
267
268 assert_eq!(cube.source_slice(input), "Cube");
269 assert_eq!(size_y.source_slice(input), "size_y");
270}