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