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